diff --git a/packages/capabilities/src/index/index.js b/packages/capabilities/src/index/index.js index 44df4f10b..d11a81352 100644 --- a/packages/capabilities/src/index/index.js +++ b/packages/capabilities/src/index/index.js @@ -39,7 +39,7 @@ export const add = capability({ with: SpaceDID, nb: Schema.struct({ /** Content Archive (CAR) containing the `Index`. */ - index: Schema.link({ code: CAR.code }), + index: Schema.link({ code: CAR.code, version: 1 }), }), derives: (claimed, delegated) => and(equalWith(claimed, delegated)) || diff --git a/packages/upload-api/package.json b/packages/upload-api/package.json index 4b0eb92e0..56883b99e 100644 --- a/packages/upload-api/package.json +++ b/packages/upload-api/package.json @@ -201,7 +201,7 @@ "@web3-storage/access": "workspace:^", "@web3-storage/blob-index": "workspace:^", "@web3-storage/capabilities": "workspace:^", - "@web3-storage/content-claims": "^5.0.0", + "@web3-storage/content-claims": "^5.1.0", "@web3-storage/did-mailto": "workspace:^", "@web3-storage/filecoin-api": "workspace:^", "multiformats": "^12.1.2", diff --git a/packages/upload-api/src/index/add.js b/packages/upload-api/src/index/add.js index 9c0945fbd..2127f40fb 100644 --- a/packages/upload-api/src/index/add.js +++ b/packages/upload-api/src/index/add.js @@ -2,6 +2,7 @@ import * as Server from '@ucanto/server' import { ok, error } from '@ucanto/server' import * as Index from '@web3-storage/capabilities/index' import { ShardedDAGIndex } from '@web3-storage/blob-index' +import { Assert } from '@web3-storage/content-claims/capability' import { concat } from 'uint8arrays' import * as API from '../types.js' @@ -61,13 +62,21 @@ const add = async ({ capability }, context) => { shardDigests.map((s) => assertAllocated(context, space, s, 'ShardNotFound')) ) for (const res of shardAllocRes) { - if (!res.ok) return res + if (res.error) return res } // TODO: randomly validate slices in the index correspond to slices in the blob - // publish the index data to IPNI - return context.ipniService.publish(idxRes.ok) + const publishRes = await Promise.all([ + // publish the index data to IPNI + context.ipniService.publish(idxRes.ok), + // publish a content claim for the index + publishIndexClaim(context, { content: idxRes.ok.content, index: idxLink }) + ]) + for (const res of publishRes) { + if (res.error) return res + } + return ok({}) } /** @@ -87,3 +96,17 @@ const assertAllocated = async (context, space, digest, errorName) => { ) return ok({}) } + +/** + * @param {API.ClaimsClientContext} ctx + * @param {{ content: API.UnknownLink, index: API.CARLink }} params + */ +const publishIndexClaim = async (ctx, { content, index }) => { + const { invocationConfig, connection } = ctx.claimsService + const { issuer, audience, with: resource, proofs } = invocationConfig + const expiration = Infinity + const nb = { content, index } + const conf = { issuer, audience, with: resource, nb, expiration, proofs } + const res = await Assert.index.invoke(conf).execute(connection) + return res.out +} diff --git a/packages/upload-api/src/types.ts b/packages/upload-api/src/types.ts index 9395ebc7e..087ee1bbb 100644 --- a/packages/upload-api/src/types.ts +++ b/packages/upload-api/src/types.ts @@ -208,6 +208,11 @@ export type { BlobNotFound, ShardedDAGIndex, } from './types/index.js' +export type { +ClaimsInvocationConfig, +ClaimsClientContext, +Service as ClaimsService +} from './types/content-claims.js' export interface Service extends StorefrontService, W3sService { store: { diff --git a/packages/upload-api/src/types/content-claims.ts b/packages/upload-api/src/types/content-claims.ts new file mode 100644 index 000000000..a31089609 --- /dev/null +++ b/packages/upload-api/src/types/content-claims.ts @@ -0,0 +1,23 @@ +import { ConnectionView, DID, Principal, Proof, Signer } from '@ucanto/interface' +import { Service } from '@web3-storage/content-claims/server/service/api' + +export type { ConnectionView, DID, Principal, Proof, Signer } +export type { Service } + +export interface ClaimsInvocationConfig { + /** Signing authority issuing the UCAN invocation(s). */ + issuer: Signer + /** The principal delegated to in the current UCAN. */ + audience: Principal + /** The resource the invocation applies to. */ + with: DID + /** Proof(s) the issuer has the capability to perform the action. */ + proofs?: Proof[] +} + +export interface ClaimsClientContext { + claimsService: { + invocationConfig: ClaimsInvocationConfig + connection: ConnectionView + } +} diff --git a/packages/upload-api/src/types/index.ts b/packages/upload-api/src/types/index.ts index cd963971e..2aaf4c89a 100644 --- a/packages/upload-api/src/types/index.ts +++ b/packages/upload-api/src/types/index.ts @@ -2,8 +2,9 @@ import { MultihashDigest } from 'multiformats' import { Failure, Result, Unit } from '@ucanto/interface' import { ShardedDAGIndex } from '@web3-storage/blob-index/types' import { AllocationsStorage } from './blob.js' +import { ClaimsClientContext } from './content-claims.js' -export type { ShardedDAGIndex } +export type { ShardedDAGIndex, ClaimsClientContext } /** * Service that allows publishing a set of multihashes to IPNI for a @@ -26,7 +27,7 @@ export interface BlobRetriever { ): Promise, BlobNotFound>> } -export interface IndexServiceContext { +export interface IndexServiceContext extends ClaimsClientContext { allocationsStorage: AllocationsStorage blobRetriever: BlobRetriever ipniService: IPNIService diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index a63ac2398..fd298a83c 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -447,8 +447,8 @@ importers: specifier: workspace:^ version: link:../capabilities '@web3-storage/content-claims': - specifier: ^5.0.0 - version: 5.0.0 + specifier: ^5.1.0 + version: 5.1.0 '@web3-storage/did-mailto': specifier: workspace:^ version: link:../did-mailto @@ -4454,6 +4454,17 @@ packages: multiformats: 13.1.0 dev: false + /@web3-storage/content-claims@5.1.0: + resolution: {integrity: sha512-3VStFKoeieRpRU7brFjKTsAuAffQzYDIZ8F3Gh0+niw+MgzBK72osW+fftdquT8neWir34Ndu3mBUKKJ3ck1RQ==} + dependencies: + '@ucanto/client': 9.0.1 + '@ucanto/interface': 10.0.1 + '@ucanto/server': 10.0.0 + '@ucanto/transport': 9.1.1 + carstream: 2.1.0 + multiformats: 13.1.0 + dev: false + /@web3-storage/data-segment@3.2.0: resolution: {integrity: sha512-SM6eNumXzrXiQE2/J59+eEgCRZNYPxKhRoHX2QvV3/scD4qgcf4g+paWBc3UriLEY1rCboygGoPsnqYJNyZyfA==} dependencies: