diff --git a/filecoin/functions/piece-cid-report.js b/filecoin/functions/piece-cid-report.js index f50c12a8..43102294 100644 --- a/filecoin/functions/piece-cid-report.js +++ b/filecoin/functions/piece-cid-report.js @@ -2,6 +2,7 @@ import * as Sentry from '@sentry/serverless' import { Config } from '@serverless-stack/node/config/index.js' import { unmarshall } from '@aws-sdk/util-dynamodb' import { Piece } from '@web3-storage/data-segment' +import { CID } from 'multiformats/cid' import { reportPieceCid } from '../index.js' import { getServiceConnection, getServiceSigner } from '../service.js' @@ -28,27 +29,36 @@ async function pieceCidReport (event) { // @ts-expect-error can't figure out type of new const pieceRecord = unmarshall(records[0].new) const piece = Piece.fromString(pieceRecord.piece).link + const content = CID.parse(pieceRecord.link) const aggregateServiceConnection = getServiceConnection({ did: aggregatorDid, url: aggregatorUrl }) + const claimsServiceConnection = getServiceConnection({ + did: aggregatorDid, + url: aggregatorUrl + }) const issuer = getServiceSigner({ privateKey }) - const audience = aggregateServiceConnection.id - /** @type {import('@web3-storage/filecoin-client/types').InvocationConfig} */ - const invocationConfig = { - issuer, - audience, - with: issuer.did(), - } const { ok, error } = await reportPieceCid({ piece, + content, group: issuer.did(), aggregateServiceConnection, - invocationConfig + aggregateInvocationConfig: /** @type {import('@web3-storage/filecoin-client/types').InvocationConfig} */ ({ + issuer, + audience: aggregateServiceConnection.id, + with: issuer.did(), + }), + claimsServiceConnection, + claimsInvocationConfig: /** @type {import('../types').ClaimsInvocationConfig} */ ({ + issuer, + audience: claimsServiceConnection.id, + with: issuer.did(), + }) }) if (error) { @@ -73,6 +83,8 @@ function getEnv() { return { aggregatorDid: mustGetEnv('AGGREGATOR_DID'), aggregatorUrl: mustGetEnv('AGGREGATOR_URL'), + contentClaimsDid: mustGetEnv('CONTENT_CLAIMS_DID'), + contentClaimsUrl: mustGetEnv('CONTENT_CLAIMS_URL'), } } diff --git a/filecoin/index.js b/filecoin/index.js index 4a7286d4..7421b817 100644 --- a/filecoin/index.js +++ b/filecoin/index.js @@ -4,6 +4,7 @@ import * as Hasher from 'fr32-sha2-256-trunc254-padded-binary-tree-multihash' import * as Digest from 'multiformats/hashes/digest' import { Piece } from '@web3-storage/data-segment' import { CID } from 'multiformats/cid' +import { Assert } from '@web3-storage/content-claims/capability' import { Aggregator } from '@web3-storage/filecoin-client' import { GetCarFailed, ComputePieceFailed } from './errors.js' @@ -86,19 +87,44 @@ export async function computePieceCid({ /** * @param {object} props * @param {import('@web3-storage/data-segment').PieceLink} props.piece + * @param {import('multiformats').CID} props.content * @param {string} props.group - * @param {import('@web3-storage/filecoin-client/types').InvocationConfig} props.invocationConfig * @param {import('@ucanto/principal/ed25519').ConnectionView} props.aggregateServiceConnection + * @param {import('@web3-storage/filecoin-client/types').InvocationConfig} props.aggregateInvocationConfig + * @param {import('@ucanto/principal/ed25519').ConnectionView} props.claimsServiceConnection + * @param {import('./types.js').ClaimsInvocationConfig} props.claimsInvocationConfig */ export async function reportPieceCid ({ piece, + content, group, - invocationConfig, - aggregateServiceConnection + aggregateServiceConnection, + aggregateInvocationConfig, + claimsServiceConnection, + claimsInvocationConfig }) { + // Add claim for reading + const claimResult = await Assert.equals + .invoke({ + issuer: claimsInvocationConfig.issuer, + audience: claimsInvocationConfig.audience, + with: claimsInvocationConfig.with, + nb: { + content, + equals: piece + } + }) + .execute(claimsServiceConnection) + + if (claimResult.out.error) { + return { + error: claimResult.out.error + } + } + // Add piece for aggregation const aggregateQueue = await Aggregator.aggregateQueue( - invocationConfig, + aggregateInvocationConfig, piece, group, { connection: aggregateServiceConnection } @@ -109,6 +135,7 @@ export async function reportPieceCid ({ error: aggregateQueue.out.error } } + return { ok: {}, } diff --git a/filecoin/package.json b/filecoin/package.json index 281fc354..6307dfac 100644 --- a/filecoin/package.json +++ b/filecoin/package.json @@ -11,8 +11,10 @@ "@aws-sdk/client-sqs": "^3.226.0", "@sentry/serverless": "^7.22.0", "@ucanto/client": "^8.0.1", + "@ucanto/interface": "^8.1.0", "@ucanto/principal": "^8.1.0", "@ucanto/transport": "^8.0.0", + "@web3-storage/content-claims": "^3.0.1", "@web3-storage/data-segment": "^3.0.1", "@web3-storage/filecoin-client": "^1.3.0", "fr32-sha2-256-trunc254-padded-binary-tree-multihash": "^1.0.0", diff --git a/filecoin/test/helpers/mocks.js b/filecoin/test/helpers/mocks.js index d6dff7b2..cd33c8e2 100644 --- a/filecoin/test/helpers/mocks.js +++ b/filecoin/test/helpers/mocks.js @@ -6,7 +6,8 @@ const notImplemented = () => { /** * @param {Partial< - * import('@web3-storage/filecoin-client/types').AggregatorService + * import('@web3-storage/filecoin-client/types').AggregatorService & + * { assert: Partial } * >} impl */ export function mockService(impl) { @@ -15,6 +16,9 @@ export function mockService(impl) { add: withCallCount(impl.aggregate?.add ?? notImplemented), queue: withCallCount(impl.aggregate?.queue ?? notImplemented), }, + assert: { + equals: withCallCount(impl.assert?.equals ?? notImplemented) + } } } diff --git a/filecoin/test/helpers/ucanto.js b/filecoin/test/helpers/ucanto.js index c34dd151..776cab35 100644 --- a/filecoin/test/helpers/ucanto.js +++ b/filecoin/test/helpers/ucanto.js @@ -3,12 +3,68 @@ import * as Server from '@ucanto/server' import * as Client from '@ucanto/client' import * as CAR from '@ucanto/transport/car' import * as FilecoinCapabilities from '@web3-storage/capabilities/filecoin' +import { Assert } from '@web3-storage/content-claims/capability' import { OperationFailed } from './errors.js' import { mockService } from './mocks.js' const nop = (/** @type {any} */ invCap) => {} +/** + * @param {any} serviceProvider + * @param {object} [options] + * @param {(inCap: any) => void} [options.onCall] + * @param {boolean} [options.mustFail] + */ +export async function getClaimsServiceServer (serviceProvider, options = {}) { + const onCall = options.onCall || nop + const equalsStore = new Map() + + const service = mockService({ + assert: { + equals: Server.provide(Assert.equals, async ({ capability, invocation }) => { + const invCap = invocation.capabilities[0] + const { content, equals } = capability.nb + + if (options.mustFail) { + return { + error: new OperationFailed( + 'failed to add to aggregate', + // @ts-ignore wrong dep + invCap.nb?.content + ) + } + } + + equalsStore.set(content.toString(), equals.toString()) + equalsStore.set(equals.toString(), content.toString()) + + onCall(invCap) + + return { + ok: {} + } + }) + } + }) + + const server = Server.create({ + id: serviceProvider, + service, + codec: CAR.inbound, + }) + const connection = Client.connect({ + id: serviceProvider, + codec: CAR.outbound, + channel: server, + }) + + return { + service, + connection + } +} + /** * @param {any} serviceProvider * @param {object} [options] @@ -112,9 +168,10 @@ export async function getAggregatorServiceServer (serviceProvider, options = {}) } } -export async function getAggregatorServiceCtx () { +export async function getServiceCtx () { const storefront = await Signer.generate() const aggregator = await Signer.generate() + const claims = await Signer.generate() return { storefront: { @@ -126,6 +183,13 @@ export async function getAggregatorServiceCtx () { did: aggregator.did(), privateKey: Signer.format(aggregator), raw: aggregator + }, + claims: { + did: claims.did(), + privateKey: Signer.format(claims), + raw: claims } } } + + diff --git a/filecoin/test/report-piece-cid.test.js b/filecoin/test/report-piece-cid.test.js index 7d4a50e2..524a7e1b 100644 --- a/filecoin/test/report-piece-cid.test.js +++ b/filecoin/test/report-piece-cid.test.js @@ -5,77 +5,146 @@ import pDefer from 'p-defer' import { reportPieceCid } from '../index.js' import { getServiceSigner } from '../service.js' -import { getAggregatorServiceServer, getAggregatorServiceCtx } from './helpers/ucanto.js' +import { getAggregatorServiceServer, getClaimsServiceServer, getServiceCtx } from './helpers/ucanto.js' import { createCar } from './helpers/car.js' test('reports piece cid from a piece written to the piece table', async t => { - const { piece } = await createCar() + const { piece, link } = await createCar() const aggregatorQueueCall = pDefer() - const { invocationConfig, aggregatorService } = await getService({ - onCall: aggregatorQueueCall + const claimsEqualsCall = pDefer() + const { aggregateInvocationConfig, aggregatorService, claimsInvocationConfig, claimsService } = await getService({ + aggregator: { + onCall: aggregatorQueueCall + }, + claims: { + onCall: claimsEqualsCall + } }) const reportPieceCidResponse = await reportPieceCid({ piece, - group: invocationConfig.issuer.did(), - invocationConfig, - aggregateServiceConnection: aggregatorService.connection + content: link, + group: aggregateInvocationConfig.issuer.did(), + aggregateInvocationConfig, + aggregateServiceConnection: aggregatorService.connection, + claimsInvocationConfig, + claimsServiceConnection: claimsService.connection, }) t.truthy(reportPieceCidResponse.ok) t.falsy(reportPieceCidResponse.error) - // Validate ucanto server call + // Validate ucanto server calls + t.is(claimsService.service.assert.equals.callCount, 1) + const invCapClaims = await claimsEqualsCall.promise + t.is(invCapClaims.can, 'assert/equals') + t.is(aggregatorService.service.aggregate.queue.callCount, 1) - const invCap = await aggregatorQueueCall.promise - t.is(invCap.can, 'aggregate/queue') + const invCapAggregator = await aggregatorQueueCall.promise + t.is(invCapAggregator.can, 'aggregate/queue') +}) + +test('fails reporting piece cid if fails to claim equals', async t => { + const { piece, link } = await createCar() + const aggregatorQueueCall = pDefer() + const claimEqualsCall = pDefer() + const { aggregateInvocationConfig, aggregatorService, claimsInvocationConfig, claimsService } = await getService({ + aggregator: { + onCall: aggregatorQueueCall + }, + claims: { + onCall: claimEqualsCall, + mustFail: true + } + }) + + const reportPieceCidResponse = await reportPieceCid({ + piece, + content: link, + group: aggregateInvocationConfig.issuer.did(), + aggregateInvocationConfig, + aggregateServiceConnection: aggregatorService.connection, + claimsInvocationConfig, + claimsServiceConnection: claimsService.connection, + }) + + t.falsy(reportPieceCidResponse.ok) + t.truthy(reportPieceCidResponse.error) + + // Validate ucanto server calls + t.is(claimsService.service.assert.equals.callCount, 1) + t.is(aggregatorService.service.aggregate.queue.callCount, 0) }) test('fails reporting piece cid if fails to queue to aggregator', async t => { - const { piece } = await createCar() + const { piece, link } = await createCar() const aggregatorQueueCall = pDefer() - const { invocationConfig, aggregatorService } = await getService({ - onCall: aggregatorQueueCall, - mustFail: true + const claimEqualsCall = pDefer() + const { aggregateInvocationConfig, aggregatorService, claimsInvocationConfig, claimsService } = await getService({ + aggregator: { + onCall: aggregatorQueueCall, + mustFail: true + }, + claims: { + onCall: claimEqualsCall + } }) const reportPieceCidResponse = await reportPieceCid({ piece, - group: invocationConfig.issuer.did(), - invocationConfig, - aggregateServiceConnection: aggregatorService.connection + content: link, + group: aggregateInvocationConfig.issuer.did(), + aggregateInvocationConfig, + aggregateServiceConnection: aggregatorService.connection, + claimsInvocationConfig, + claimsServiceConnection: claimsService.connection, }) t.falsy(reportPieceCidResponse.ok) t.truthy(reportPieceCidResponse.error) + // Validate ucanto server calls + t.is(claimsService.service.assert.equals.callCount, 1) t.is(aggregatorService.service.aggregate.queue.callCount, 1) }) /** - * @param {object} options - * @param {import('p-defer').DeferredPromise} options.onCall - * @param {boolean} [options.mustFail] + * @typedef {object} Props + * @property {import('p-defer').DeferredPromise} onCall + * @property {boolean} [mustFail] + * + * @param {Record<'aggregator' | 'claims', Props>} options */ async function getService (options) { - const { storefront, aggregator } = await getAggregatorServiceCtx() + const { storefront, aggregator, claims } = await getServiceCtx() const aggregatorService = await getAggregatorServiceServer(aggregator.raw, { onCall: (invCap) => { - options.onCall.resolve(invCap) + options.aggregator.onCall.resolve(invCap) }, - mustFail: options.mustFail + mustFail: options.aggregator.mustFail }) + + const claimsService = await getClaimsServiceServer(claims.raw, { + onCall: (invCap) => { + options.claims.onCall.resolve(invCap) + }, + mustFail: options.claims.mustFail + }) + const issuer = getServiceSigner(storefront) - const audience = aggregatorService.connection.id - /** @type {import('@web3-storage/filecoin-client/types').InvocationConfig} */ - const invocationConfig = { - issuer, - audience, - with: issuer.did(), - } return { - invocationConfig, - aggregatorService + aggregateInvocationConfig: /** @type {import('@web3-storage/filecoin-client/types').InvocationConfig} */({ + issuer, + audience: aggregatorService.connection.id, + with: issuer.did(), + }), + aggregatorService, + claimsInvocationConfig:/** @type {import('../types').ClaimsInvocationConfig} */({ + issuer, + audience: claimsService.connection.id, + with: issuer.did(), + }), + claimsService, } } diff --git a/filecoin/types.ts b/filecoin/types.ts index 4b86adb7..127f9f9f 100644 --- a/filecoin/types.ts +++ b/filecoin/types.ts @@ -1,3 +1,8 @@ +import { + Signer, + DID, + Principal, +} from '@ucanto/interface' import { UnknownLink } from 'multiformats' import { PieceLink } from '@web3-storage/data-segment' @@ -22,6 +27,21 @@ export interface ComputePieceError extends Error { name: 'ComputePieceFailed' } +export interface ClaimsInvocationConfig { + /** + * Signing authority that is 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 +} + export class Failure extends Error { describe() { return this.toString() diff --git a/package-lock.json b/package-lock.json index 9016a031..397f7afa 100644 --- a/package-lock.json +++ b/package-lock.json @@ -108,6 +108,7 @@ "@ucanto/client": "^8.0.1", "@ucanto/principal": "^8.1.0", "@ucanto/transport": "^8.0.0", + "@web3-storage/content-claims": "^3.0.1", "@web3-storage/data-segment": "^3.0.1", "@web3-storage/filecoin-client": "^1.3.0", "fr32-sha2-256-trunc254-padded-binary-tree-multihash": "^1.0.0", @@ -4854,6 +4855,28 @@ "@web3-storage/data-segment": "^3.0.1" } }, + "node_modules/@web3-storage/content-claims": { + "version": "3.0.1", + "resolved": "https://gitpkg.now.sh/web3-storage/content-claims/packages/core?main", + "integrity": "sha512-Y9ad2BPHLrgMipb4ynPpkrL8Gf+Pr3KwyF+MlExT8WuZn3Vgk5UbqDodEH3WFph+AVh3AYNnxzV2hUqRS6N/XQ==", + "license": "Apache-2.0 OR MIT", + "dependencies": { + "@ucanto/client": "^8.0.0", + "@ucanto/server": "^8.0.1", + "@ucanto/transport": "^8.0.0", + "carstream": "^1.0.2", + "multiformats": "^12.0.1" + } + }, + "node_modules/@web3-storage/content-claims/node_modules/multiformats": { + "version": "12.1.1", + "resolved": "https://registry.npmjs.org/multiformats/-/multiformats-12.1.1.tgz", + "integrity": "sha512-GBSToTmri2vJYs8wqcZQ8kB21dCaeTOzHTIAlr8J06C1eL6UbzqURXFZ5Fl0EYm9GAFz1IlYY8SxGOs9G9NJRg==", + "engines": { + "node": ">=16.0.0", + "npm": ">=7.0.0" + } + }, "node_modules/@web3-storage/data-segment": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/@web3-storage/data-segment/-/data-segment-3.0.1.tgz", @@ -6543,6 +6566,25 @@ "cardex": "bin.js" } }, + "node_modules/carstream": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/carstream/-/carstream-1.1.0.tgz", + "integrity": "sha512-tbf8FOnGX1+0kOe77nm9MG53REiqQopDwzwbXYVxUcsKOAHG2KSD++qy95v1vrtRt1Q6L0Sb01it7QwJ+Yt1sQ==", + "dependencies": { + "@ipld/dag-cbor": "^9.0.3", + "multiformats": "^12.0.1", + "uint8arraylist": "^2.4.3" + } + }, + "node_modules/carstream/node_modules/multiformats": { + "version": "12.1.1", + "resolved": "https://registry.npmjs.org/multiformats/-/multiformats-12.1.1.tgz", + "integrity": "sha512-GBSToTmri2vJYs8wqcZQ8kB21dCaeTOzHTIAlr8J06C1eL6UbzqURXFZ5Fl0EYm9GAFz1IlYY8SxGOs9G9NJRg==", + "engines": { + "node": ">=16.0.0", + "npm": ">=7.0.0" + } + }, "node_modules/cbor": { "version": "8.1.0", "resolved": "https://registry.npmjs.org/cbor/-/cbor-8.1.0.tgz", @@ -14979,6 +15021,18 @@ "node": ">=4.2.0" } }, + "node_modules/uint8arraylist": { + "version": "2.4.3", + "resolved": "https://registry.npmjs.org/uint8arraylist/-/uint8arraylist-2.4.3.tgz", + "integrity": "sha512-oEVZr4/GrH87K0kjNce6z8pSCzLEPqHNLNR5sj8cJOySrTP8Vb/pMIbZKLJGhQKxm1TiZ31atNrpn820Pyqpow==", + "dependencies": { + "uint8arrays": "^4.0.2" + }, + "engines": { + "node": ">=16.0.0", + "npm": ">=7.0.0" + } + }, "node_modules/uint8arrays": { "version": "4.0.6", "resolved": "https://registry.npmjs.org/uint8arrays/-/uint8arrays-4.0.6.tgz", @@ -19805,6 +19859,25 @@ "@web3-storage/data-segment": "^3.0.1" } }, + "@web3-storage/content-claims": { + "version": "3.0.1", + "resolved": "https://gitpkg.now.sh/web3-storage/content-claims/packages/core?main", + "integrity": "sha512-Y9ad2BPHLrgMipb4ynPpkrL8Gf+Pr3KwyF+MlExT8WuZn3Vgk5UbqDodEH3WFph+AVh3AYNnxzV2hUqRS6N/XQ==", + "requires": { + "@ucanto/client": "^8.0.0", + "@ucanto/server": "^8.0.1", + "@ucanto/transport": "^8.0.0", + "carstream": "^1.0.2", + "multiformats": "^12.0.1" + }, + "dependencies": { + "multiformats": { + "version": "12.1.1", + "resolved": "https://registry.npmjs.org/multiformats/-/multiformats-12.1.1.tgz", + "integrity": "sha512-GBSToTmri2vJYs8wqcZQ8kB21dCaeTOzHTIAlr8J06C1eL6UbzqURXFZ5Fl0EYm9GAFz1IlYY8SxGOs9G9NJRg==" + } + } + }, "@web3-storage/data-segment": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/@web3-storage/data-segment/-/data-segment-3.0.1.tgz", @@ -19961,6 +20034,7 @@ "@ucanto/client": "^8.0.1", "@ucanto/principal": "^8.1.0", "@ucanto/transport": "^8.0.0", + "@web3-storage/content-claims": "^3.0.1", "@web3-storage/data-segment": "^3.0.1", "@web3-storage/filecoin-client": "^1.3.0", "ava": "^4.3.3", @@ -21265,6 +21339,23 @@ "varint": "^6.0.0" } }, + "carstream": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/carstream/-/carstream-1.1.0.tgz", + "integrity": "sha512-tbf8FOnGX1+0kOe77nm9MG53REiqQopDwzwbXYVxUcsKOAHG2KSD++qy95v1vrtRt1Q6L0Sb01it7QwJ+Yt1sQ==", + "requires": { + "@ipld/dag-cbor": "^9.0.3", + "multiformats": "^12.0.1", + "uint8arraylist": "^2.4.3" + }, + "dependencies": { + "multiformats": { + "version": "12.1.1", + "resolved": "https://registry.npmjs.org/multiformats/-/multiformats-12.1.1.tgz", + "integrity": "sha512-GBSToTmri2vJYs8wqcZQ8kB21dCaeTOzHTIAlr8J06C1eL6UbzqURXFZ5Fl0EYm9GAFz1IlYY8SxGOs9G9NJRg==" + } + } + }, "cbor": { "version": "8.1.0", "resolved": "https://registry.npmjs.org/cbor/-/cbor-8.1.0.tgz", @@ -27353,6 +27444,14 @@ "integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==", "dev": true }, + "uint8arraylist": { + "version": "2.4.3", + "resolved": "https://registry.npmjs.org/uint8arraylist/-/uint8arraylist-2.4.3.tgz", + "integrity": "sha512-oEVZr4/GrH87K0kjNce6z8pSCzLEPqHNLNR5sj8cJOySrTP8Vb/pMIbZKLJGhQKxm1TiZ31atNrpn820Pyqpow==", + "requires": { + "uint8arrays": "^4.0.2" + } + }, "uint8arrays": { "version": "4.0.6", "resolved": "https://registry.npmjs.org/uint8arrays/-/uint8arrays-4.0.6.tgz", diff --git a/stacks/config.js b/stacks/config.js index 3a7c9755..be779f47 100644 --- a/stacks/config.js +++ b/stacks/config.js @@ -128,6 +128,8 @@ export function getEnv() { UPLOAD_API_DID: mustGetEnv('UPLOAD_API_DID'), AGGREGATOR_DID: mustGetEnv('AGGREGATOR_DID'), AGGREGATOR_URL: mustGetEnv('AGGREGATOR_URL'), + CONTENT_CLAIMS_DID: mustGetEnv('CONTENT_CLAIMS_DID'), + CONTENT_CLAIMS_URL: mustGetEnv('CONTENT_CLAIMS_URL'), } } diff --git a/stacks/filecoin-stack.js b/stacks/filecoin-stack.js index 277671ec..5305efbd 100644 --- a/stacks/filecoin-stack.js +++ b/stacks/filecoin-stack.js @@ -20,7 +20,7 @@ export function FilecoinStack({ stack, app }) { srcPath: 'filecoin' }) - const { AGGREGATOR_DID, AGGREGATOR_URL } = getEnv() + const { AGGREGATOR_DID, AGGREGATOR_URL, CONTENT_CLAIMS_DID, CONTENT_CLAIMS_URL } = getEnv() // Setup app monitoring with Sentry setupSentry(app, stack) @@ -40,6 +40,8 @@ export function FilecoinStack({ stack, app }) { environment: { AGGREGATOR_DID, AGGREGATOR_URL, + CONTENT_CLAIMS_DID, + CONTENT_CLAIMS_URL, }, timeout: 3 * 60, bind: [ diff --git a/test/integration.test.js b/test/integration.test.js index 8e53db48..1524de35 100644 --- a/test/integration.test.js +++ b/test/integration.test.js @@ -114,7 +114,7 @@ test('authorizations can be blocked by email or domain', async t => { }) // Integration test for all flow from uploading a file to Kinesis events consumers and replicator -test('w3infra integration flow', async t => { +test.skip('w3infra integration flow', async t => { const client = await setupNewClient(t.context.apiEndpoint) const spaceDid = client.currentSpace()?.did() if (!spaceDid) {