@learncard/network-brain-service 3.19.0 → 4.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.env.example +47 -82
- package/AGENTS.md +52 -0
- package/CHANGELOG.md +147 -0
- package/README.md +51 -2
- package/bootstrapLambda.test.ts +72 -0
- package/config/config.dev.json +29 -0
- package/config/config.example.json +29 -0
- package/config/config.json +1 -0
- package/config/config.production.json +29 -0
- package/config/config.scouts.dev.json +24 -0
- package/config/config.scouts.production.json +24 -0
- package/contractEventsLambda.ts +18 -0
- package/contractEventsLambdaApp.ts +19 -0
- package/didWebLambda.ts +13 -18
- package/didWebLambdaApp.ts +22 -0
- package/dist/accesslayer/app-manifest-version/create.d.ts +10 -0
- package/dist/accesslayer/app-manifest-version/create.d.ts.map +1 -0
- package/dist/accesslayer/app-manifest-version/read.d.ts +11 -0
- package/dist/accesslayer/app-manifest-version/read.d.ts.map +1 -0
- package/dist/accesslayer/app-manifest-version/update.d.ts +7 -0
- package/dist/accesslayer/app-manifest-version/update.d.ts.map +1 -0
- package/dist/accesslayer/app-store-listing/read.d.ts +2 -1
- package/dist/accesslayer/app-store-listing/read.d.ts.map +1 -1
- package/dist/accesslayer/app-store-listing/relationships/create.d.ts +1 -0
- package/dist/accesslayer/app-store-listing/relationships/create.d.ts.map +1 -1
- package/dist/accesslayer/app-store-listing/relationships/read.d.ts +2 -0
- package/dist/accesslayer/app-store-listing/relationships/read.d.ts.map +1 -1
- package/dist/accesslayer/app-store-listing/update.d.ts +1 -0
- package/dist/accesslayer/app-store-listing/update.d.ts.map +1 -1
- package/dist/accesslayer/boost/relationships/create.d.ts +2 -1
- package/dist/accesslayer/boost/relationships/create.d.ts.map +1 -1
- package/dist/accesslayer/consentflowcontract/create.d.ts +9 -1
- package/dist/accesslayer/consentflowcontract/create.d.ts.map +1 -1
- package/dist/accesslayer/consentflowcontract/read.d.ts +34 -3
- package/dist/accesslayer/consentflowcontract/read.d.ts.map +1 -1
- package/dist/accesslayer/consentflowcontract/relationships/create.d.ts +4 -1
- package/dist/accesslayer/consentflowcontract/relationships/create.d.ts.map +1 -1
- package/dist/accesslayer/consentflowcontract/relationships/delete.d.ts.map +1 -1
- package/dist/accesslayer/consentflowcontract/relationships/read.d.ts +3 -2
- package/dist/accesslayer/consentflowcontract/relationships/read.d.ts.map +1 -1
- package/dist/accesslayer/consentflowcontract/relationships/recipients.d.ts +12 -0
- package/dist/accesslayer/consentflowcontract/relationships/recipients.d.ts.map +1 -0
- package/dist/accesslayer/consentflowcontract/relationships/smartResume.d.ts +12 -0
- package/dist/accesslayer/consentflowcontract/relationships/smartResume.d.ts.map +1 -0
- package/dist/accesslayer/consentflowcontract/relationships/update.d.ts +8 -5
- package/dist/accesslayer/consentflowcontract/relationships/update.d.ts.map +1 -1
- package/dist/accesslayer/credential/create.d.ts +2 -1
- package/dist/accesslayer/credential/create.d.ts.map +1 -1
- package/dist/accesslayer/credential/relationships/create.d.ts +4 -3
- package/dist/accesslayer/credential/relationships/create.d.ts.map +1 -1
- package/dist/accesslayer/inbox-credential/relationships/create.d.ts +2 -1
- package/dist/accesslayer/inbox-credential/relationships/create.d.ts.map +1 -1
- package/dist/accesslayer/inbox-credential/update.d.ts +2 -1
- package/dist/accesslayer/inbox-credential/update.d.ts.map +1 -1
- package/dist/accesslayer/presentation/read.d.ts.map +1 -1
- package/dist/accesslayer/presentation/relationships/create.d.ts +2 -2
- package/dist/accesslayer/presentation/relationships/create.d.ts.map +1 -1
- package/dist/accesslayer/presentation/relationships/read.d.ts.map +1 -1
- package/dist/accesslayer/share-link/abandon.d.ts +3 -0
- package/dist/accesslayer/share-link/abandon.d.ts.map +1 -0
- package/dist/accesslayer/share-link/attachment.d.ts +37 -0
- package/dist/accesslayer/share-link/attachment.d.ts.map +1 -0
- package/dist/accesslayer/share-link/cleanup.d.ts +6 -0
- package/dist/accesslayer/share-link/cleanup.d.ts.map +1 -0
- package/dist/accesslayer/share-link/errors.d.ts +8 -0
- package/dist/accesslayer/share-link/errors.d.ts.map +1 -0
- package/dist/accesslayer/share-link/finalize.d.ts +3 -0
- package/dist/accesslayer/share-link/finalize.d.ts.map +1 -0
- package/dist/accesslayer/share-link/helpers.d.ts +59 -0
- package/dist/accesslayer/share-link/helpers.d.ts.map +1 -0
- package/dist/accesslayer/share-link/index.d.ts +12 -0
- package/dist/accesslayer/share-link/index.d.ts.map +1 -0
- package/dist/accesslayer/share-link/list.d.ts +5 -0
- package/dist/accesslayer/share-link/list.d.ts.map +1 -0
- package/dist/accesslayer/share-link/read.d.ts +5 -0
- package/dist/accesslayer/share-link/read.d.ts.map +1 -0
- package/dist/accesslayer/share-link/receipt.d.ts +11 -0
- package/dist/accesslayer/share-link/receipt.d.ts.map +1 -0
- package/dist/accesslayer/share-link/recovery.d.ts +6 -0
- package/dist/accesslayer/share-link/recovery.d.ts.map +1 -0
- package/dist/accesslayer/share-link/reserve.d.ts +4 -0
- package/dist/accesslayer/share-link/reserve.d.ts.map +1 -0
- package/dist/accesslayer/share-link/revoke.d.ts +3 -0
- package/dist/accesslayer/share-link/revoke.d.ts.map +1 -0
- package/dist/accesslayer/share-link/transaction-execution.d.ts +18 -0
- package/dist/accesslayer/share-link/transaction-execution.d.ts.map +1 -0
- package/dist/accesslayer/share-link/transaction.d.ts +15 -0
- package/dist/accesslayer/share-link/transaction.d.ts.map +1 -0
- package/dist/accesslayer/share-link/types.d.ts +375 -0
- package/dist/accesslayer/share-link/types.d.ts.map +1 -0
- package/dist/app.d.ts +4 -0
- package/dist/app.d.ts.map +1 -1
- package/dist/cache/app-store.caches.d.ts +49 -0
- package/dist/cache/app-store.caches.d.ts.map +1 -1
- package/dist/config/applyDockerStageConfig.d.ts +2 -0
- package/dist/config/applyDockerStageConfig.d.ts.map +1 -0
- package/dist/config/environment.d.ts +14 -0
- package/dist/config/environment.d.ts.map +1 -1
- package/dist/config/serverlessFunctionEnv.test.d.ts +2 -0
- package/dist/config/serverlessFunctionEnv.test.d.ts.map +1 -0
- package/dist/config/stageConfig.d.ts +112 -0
- package/dist/config/stageConfig.d.ts.map +1 -0
- package/dist/config/stageConfig.test.d.ts +2 -0
- package/dist/config/stageConfig.test.d.ts.map +1 -0
- package/dist/constants/auth-grant.d.ts +2 -0
- package/dist/constants/auth-grant.d.ts.map +1 -1
- package/dist/didkit_wasm_bg.wasm +0 -0
- package/dist/docker-entry.d.ts +1 -1
- package/dist/docker-entry.d.ts.map +1 -1
- package/dist/helpers/app-manifest.helpers.d.ts +5 -0
- package/dist/helpers/app-manifest.helpers.d.ts.map +1 -0
- package/dist/helpers/boost.helpers.d.ts.map +1 -1
- package/dist/helpers/claim-hooks.helpers.d.ts +2 -1
- package/dist/helpers/claim-hooks.helpers.d.ts.map +1 -1
- package/dist/helpers/consent-referral.helpers.d.ts +2 -0
- package/dist/helpers/consent-referral.helpers.d.ts.map +1 -0
- package/dist/helpers/consent-scopes.helpers.d.ts +7 -0
- package/dist/helpers/consent-scopes.helpers.d.ts.map +1 -0
- package/dist/helpers/contract-event-maintenance.helpers.d.ts +3 -0
- package/dist/helpers/contract-event-maintenance.helpers.d.ts.map +1 -0
- package/dist/helpers/contract-events.helpers.d.ts +25 -0
- package/dist/helpers/contract-events.helpers.d.ts.map +1 -0
- package/dist/helpers/contract-requests.helpers.d.ts +19 -0
- package/dist/helpers/contract-requests.helpers.d.ts.map +1 -0
- package/dist/helpers/credentialSubject.helpers.d.ts +3 -0
- package/dist/helpers/credentialSubject.helpers.d.ts.map +1 -0
- package/dist/helpers/credentialSubject.helpers.test.d.ts +2 -0
- package/dist/helpers/credentialSubject.helpers.test.d.ts.map +1 -0
- package/dist/helpers/finalize-inbox.helpers.d.ts +3 -3
- package/dist/helpers/finalize-inbox.helpers.d.ts.map +1 -1
- package/dist/helpers/inbox.helpers.d.ts.map +1 -1
- package/dist/helpers/inline-template.helpers.d.ts +45 -0
- package/dist/helpers/inline-template.helpers.d.ts.map +1 -0
- package/dist/helpers/notificationMessages.d.ts +1 -1
- package/dist/helpers/notificationMessages.d.ts.map +1 -1
- package/dist/helpers/notificationQueue.helpers.d.ts.map +1 -1
- package/dist/helpers/notifications.helpers.d.ts +6 -0
- package/dist/helpers/notifications.helpers.d.ts.map +1 -1
- package/dist/helpers/presentation-metadata.d.ts +4 -0
- package/dist/helpers/presentation-metadata.d.ts.map +1 -0
- package/dist/helpers/presentation.helpers.d.ts +1 -1
- package/dist/helpers/presentation.helpers.d.ts.map +1 -1
- package/dist/helpers/profile.helpers.d.ts +2 -0
- package/dist/helpers/profile.helpers.d.ts.map +1 -1
- package/dist/helpers/share-content-client/adapters.d.ts +10 -0
- package/dist/helpers/share-content-client/adapters.d.ts.map +1 -0
- package/dist/helpers/share-content-client/canonical.d.ts +7 -0
- package/dist/helpers/share-content-client/canonical.d.ts.map +1 -0
- package/dist/helpers/share-content-client/client.d.ts +5 -0
- package/dist/helpers/share-content-client/client.d.ts.map +1 -0
- package/dist/helpers/share-content-client/config.d.ts +11 -0
- package/dist/helpers/share-content-client/config.d.ts.map +1 -0
- package/dist/helpers/share-content-client/index.d.ts +6 -0
- package/dist/helpers/share-content-client/index.d.ts.map +1 -0
- package/dist/helpers/share-content-client/responses.d.ts +16 -0
- package/dist/helpers/share-content-client/responses.d.ts.map +1 -0
- package/dist/helpers/share-content-client/types.d.ts +145 -0
- package/dist/helpers/share-content-client/types.d.ts.map +1 -0
- package/dist/helpers/share-link-attachment-access.d.ts +4 -0
- package/dist/helpers/share-link-attachment-access.d.ts.map +1 -0
- package/dist/helpers/share-link-attachment.d.ts +18 -0
- package/dist/helpers/share-link-attachment.d.ts.map +1 -0
- package/dist/helpers/share-link-coordinator/attachment-stat.d.ts +5 -0
- package/dist/helpers/share-link-coordinator/attachment-stat.d.ts.map +1 -0
- package/dist/helpers/share-link-coordinator/budget-helpers.d.ts +5 -0
- package/dist/helpers/share-link-coordinator/budget-helpers.d.ts.map +1 -0
- package/dist/helpers/share-link-coordinator/cleanup-runner.d.ts +3 -0
- package/dist/helpers/share-link-coordinator/cleanup-runner.d.ts.map +1 -0
- package/dist/helpers/share-link-coordinator/coordinator.d.ts +4 -0
- package/dist/helpers/share-link-coordinator/coordinator.d.ts.map +1 -0
- package/dist/helpers/share-link-coordinator/index.d.ts +5 -0
- package/dist/helpers/share-link-coordinator/index.d.ts.map +1 -0
- package/dist/helpers/share-link-coordinator/recovery-runner.d.ts +9 -0
- package/dist/helpers/share-link-coordinator/recovery-runner.d.ts.map +1 -0
- package/dist/helpers/share-link-coordinator/runtime.d.ts +11 -0
- package/dist/helpers/share-link-coordinator/runtime.d.ts.map +1 -0
- package/dist/helpers/share-link-coordinator/stat.d.ts +4 -0
- package/dist/helpers/share-link-coordinator/stat.d.ts.map +1 -0
- package/dist/helpers/share-link-coordinator/types.d.ts +160 -0
- package/dist/helpers/share-link-coordinator/types.d.ts.map +1 -0
- package/dist/helpers/share-link-lifecycle/index.d.ts +5 -0
- package/dist/helpers/share-link-lifecycle/index.d.ts.map +1 -0
- package/dist/helpers/share-link-lifecycle/lease.helpers.d.ts +4 -0
- package/dist/helpers/share-link-lifecycle/lease.helpers.d.ts.map +1 -0
- package/dist/helpers/share-link-lifecycle/recovery.helpers.d.ts +21 -0
- package/dist/helpers/share-link-lifecycle/recovery.helpers.d.ts.map +1 -0
- package/dist/helpers/share-link-lifecycle/request-hash.helpers.d.ts +9 -0
- package/dist/helpers/share-link-lifecycle/request-hash.helpers.d.ts.map +1 -0
- package/dist/helpers/share-link-lifecycle/types.d.ts +14 -0
- package/dist/helpers/share-link-lifecycle/types.d.ts.map +1 -0
- package/dist/helpers/share-link-maintenance/budget.d.ts +12 -0
- package/dist/helpers/share-link-maintenance/budget.d.ts.map +1 -0
- package/dist/helpers/share-link-maintenance/config.d.ts +48 -0
- package/dist/helpers/share-link-maintenance/config.d.ts.map +1 -0
- package/dist/helpers/share-link-maintenance/index.d.ts +8 -0
- package/dist/helpers/share-link-maintenance/index.d.ts.map +1 -0
- package/dist/helpers/share-link-maintenance/runner.d.ts +20 -0
- package/dist/helpers/share-link-maintenance/runner.d.ts.map +1 -0
- package/dist/helpers/share-link-maintenance/runtime.d.ts +38 -0
- package/dist/helpers/share-link-maintenance/runtime.d.ts.map +1 -0
- package/dist/helpers/share-link-maintenance/scheduler.d.ts +18 -0
- package/dist/helpers/share-link-maintenance/scheduler.d.ts.map +1 -0
- package/dist/helpers/share-link-maintenance/telemetry.d.ts +5 -0
- package/dist/helpers/share-link-maintenance/telemetry.d.ts.map +1 -0
- package/dist/helpers/share-link-maintenance/types.d.ts +56 -0
- package/dist/helpers/share-link-maintenance/types.d.ts.map +1 -0
- package/dist/helpers/share-link-owner/config.d.ts +16 -0
- package/dist/helpers/share-link-owner/config.d.ts.map +1 -0
- package/dist/helpers/share-link-owner/diagnostics.d.ts +26 -0
- package/dist/helpers/share-link-owner/diagnostics.d.ts.map +1 -0
- package/dist/helpers/share-link-owner/lazy-initializer.d.ts +2 -0
- package/dist/helpers/share-link-owner/lazy-initializer.d.ts.map +1 -0
- package/dist/helpers/share-link-owner-projection.d.ts +4 -0
- package/dist/helpers/share-link-owner-projection.d.ts.map +1 -0
- package/dist/helpers/share-link-passcode-abuse.d.ts +2 -0
- package/dist/helpers/share-link-passcode-abuse.d.ts.map +1 -0
- package/dist/helpers/share-link-passcode.d.ts +6 -0
- package/dist/helpers/share-link-passcode.d.ts.map +1 -0
- package/dist/helpers/share-link-policy/index.d.ts +4 -0
- package/dist/helpers/share-link-policy/index.d.ts.map +1 -0
- package/dist/helpers/share-link-policy/production.d.ts +11 -0
- package/dist/helpers/share-link-policy/production.d.ts.map +1 -0
- package/dist/helpers/share-link-policy/resolver.d.ts +6 -0
- package/dist/helpers/share-link-policy/resolver.d.ts.map +1 -0
- package/dist/helpers/share-link-policy/types.d.ts +22 -0
- package/dist/helpers/share-link-policy/types.d.ts.map +1 -0
- package/dist/helpers/share-link-public/config.d.ts +13 -0
- package/dist/helpers/share-link-public/config.d.ts.map +1 -0
- package/dist/helpers/share-link-view-notification.d.ts +2 -0
- package/dist/helpers/share-link-view-notification.d.ts.map +1 -0
- package/dist/helpers/signingAuthority.helpers.d.ts.map +1 -1
- package/dist/helpers/smartResume.helpers.d.ts +3 -0
- package/dist/helpers/smartResume.helpers.d.ts.map +1 -0
- package/dist/index.js +2 -2
- package/dist/models/AppManifestVersion.d.ts +20 -0
- package/dist/models/AppManifestVersion.d.ts.map +1 -0
- package/dist/models/AppStoreListing.d.ts +13 -0
- package/dist/models/AppStoreListing.d.ts.map +1 -1
- package/dist/models/ConsentFlowContract.d.ts +13 -2
- package/dist/models/ConsentFlowContract.d.ts.map +1 -1
- package/dist/models/ConsentFlowTerms.d.ts.map +1 -1
- package/dist/models/ConsentFlowTransaction.d.ts.map +1 -1
- package/dist/models/Integration.d.ts +2 -0
- package/dist/models/Integration.d.ts.map +1 -1
- package/dist/models/Presentation.d.ts +2 -0
- package/dist/models/Presentation.d.ts.map +1 -1
- package/dist/models/Profile.d.ts +2 -0
- package/dist/models/Profile.d.ts.map +1 -1
- package/dist/models/ShareLink.d.ts +46 -0
- package/dist/models/ShareLink.d.ts.map +1 -0
- package/dist/models/ShareViewReceipt.d.ts +22 -0
- package/dist/models/ShareViewReceipt.d.ts.map +1 -0
- package/dist/models/index.d.ts +2 -0
- package/dist/models/index.d.ts.map +1 -1
- package/dist/models/share-link-constraints.d.ts +3 -0
- package/dist/models/share-link-constraints.d.ts.map +1 -0
- package/dist/openapi.d.ts +1 -1
- package/dist/openapi.d.ts.map +1 -1
- package/dist/public-share-link-http.d.ts +4 -0
- package/dist/public-share-link-http.d.ts.map +1 -0
- package/dist/routes/app-store.d.ts +619 -0
- package/dist/routes/app-store.d.ts.map +1 -1
- package/dist/routes/boosts.d.ts +2 -2
- package/dist/routes/boosts.d.ts.map +1 -1
- package/dist/routes/contracts.d.ts +148 -5
- package/dist/routes/contracts.d.ts.map +1 -1
- package/dist/routes/credentials.d.ts +35 -1
- package/dist/routes/credentials.d.ts.map +1 -1
- package/dist/routes/inbox.d.ts +52 -2
- package/dist/routes/inbox.d.ts.map +1 -1
- package/dist/routes/index.d.ts +153 -0
- package/dist/routes/index.d.ts.map +1 -1
- package/dist/routes/presentations.d.ts +1 -0
- package/dist/routes/presentations.d.ts.map +1 -1
- package/dist/routes/profile-manager.d.ts.map +1 -1
- package/dist/routes/profiles.d.ts.map +1 -1
- package/dist/routes/public-share-links.d.ts +285 -0
- package/dist/routes/public-share-links.d.ts.map +1 -0
- package/dist/routes/share-links.d.ts +1005 -0
- package/dist/routes/share-links.d.ts.map +1 -0
- package/dist/routes/test.d.ts +5 -0
- package/dist/routes/test.d.ts.map +1 -1
- package/dist/routes/workflows.d.ts.map +1 -1
- package/dist/services/delivery/adapters/postmark.adapter.d.ts.map +1 -1
- package/dist/types/app-manifest.d.ts +339 -0
- package/dist/types/app-manifest.d.ts.map +1 -0
- package/dist/types/app-store-listing.d.ts +34 -0
- package/dist/types/app-store-listing.d.ts.map +1 -1
- package/dist/types/consentflowcontract.d.ts +31 -4
- package/dist/types/consentflowcontract.d.ts.map +1 -1
- package/dist/types/credential-refresh.d.ts +1 -1
- package/esbuildPlugins.cjs +1 -0
- package/lambda.ts +49 -192
- package/lambdaApp.ts +203 -0
- package/package.json +25 -17
- package/scripts/export-openapi.ts +20 -0
- package/scripts/offline-schema-environment.ts +18 -0
- package/scripts/seed-demo-persona.ts +425 -0
- package/scripts/verify-python-contracts.ts +72 -0
- package/serverless-local.yml +3 -6
- package/serverless.function-env.cjs +50 -0
- package/serverless.local.yml +0 -1
- package/serverless.yml +44 -55
- package/shareLinkMaintenanceLambda.ts +18 -0
- package/shareLinkMaintenanceLambdaApp.ts +50 -0
- package/src/accesslayer/app-manifest-version/create.ts +57 -0
- package/src/accesslayer/app-manifest-version/read.ts +129 -0
- package/src/accesslayer/app-manifest-version/update.ts +61 -0
- package/src/accesslayer/app-store-listing/read.ts +6 -0
- package/src/accesslayer/app-store-listing/relationships/create.ts +20 -0
- package/src/accesslayer/app-store-listing/relationships/read.ts +24 -1
- package/src/accesslayer/app-store-listing/update.ts +15 -0
- package/src/accesslayer/boost/relationships/create.ts +8 -2
- package/src/accesslayer/consentflowcontract/create.ts +81 -8
- package/src/accesslayer/consentflowcontract/read.ts +84 -10
- package/src/accesslayer/consentflowcontract/relationships/create.ts +125 -109
- package/src/accesslayer/consentflowcontract/relationships/delete.ts +1 -0
- package/src/accesslayer/consentflowcontract/relationships/read.ts +184 -108
- package/src/accesslayer/consentflowcontract/relationships/recipients.ts +125 -0
- package/src/accesslayer/consentflowcontract/relationships/smartResume.ts +125 -0
- package/src/accesslayer/consentflowcontract/relationships/update.ts +255 -217
- package/src/accesslayer/credential/create.test.ts +2 -115
- package/src/accesslayer/credential/create.ts +11 -6
- package/src/accesslayer/credential/relationships/create.ts +17 -10
- package/src/accesslayer/inbox-credential/relationships/create.ts +70 -48
- package/src/accesslayer/inbox-credential/update.ts +4 -2
- package/src/accesslayer/presentation/read.ts +57 -25
- package/src/accesslayer/presentation/relationships/create.ts +54 -11
- package/src/accesslayer/presentation/relationships/read.ts +21 -6
- package/src/accesslayer/share-link/abandon.ts +120 -0
- package/src/accesslayer/share-link/attachment.ts +360 -0
- package/src/accesslayer/share-link/cleanup.ts +271 -0
- package/src/accesslayer/share-link/errors.ts +32 -0
- package/src/accesslayer/share-link/finalize.ts +313 -0
- package/src/accesslayer/share-link/helpers.ts +450 -0
- package/src/accesslayer/share-link/index.ts +11 -0
- package/src/accesslayer/share-link/list.ts +162 -0
- package/src/accesslayer/share-link/read.ts +107 -0
- package/src/accesslayer/share-link/receipt.ts +548 -0
- package/src/accesslayer/share-link/recovery.ts +565 -0
- package/src/accesslayer/share-link/reserve.ts +763 -0
- package/src/accesslayer/share-link/revoke.ts +232 -0
- package/src/accesslayer/share-link/transaction-execution.ts +72 -0
- package/src/accesslayer/share-link/transaction.ts +126 -0
- package/src/accesslayer/share-link/types.ts +606 -0
- package/src/app.ts +6 -0
- package/src/cache/app-store.caches.ts +35 -0
- package/src/config/applyDockerStageConfig.ts +6 -0
- package/src/config/environment.test.ts +17 -12
- package/src/config/environment.ts +15 -0
- package/src/config/serverlessFunctionEnv.test.ts +105 -0
- package/src/config/stageConfig.test.ts +25 -0
- package/src/config/stageConfig.ts +24 -0
- package/src/constants/auth-grant.ts +5 -0
- package/src/docker-entry.ts +81 -0
- package/src/helpers/app-manifest.helpers.ts +185 -0
- package/src/helpers/boost.helpers.ts +4 -31
- package/src/helpers/claim-hooks.helpers.ts +20 -7
- package/src/helpers/consent-referral.helpers.ts +6 -0
- package/src/helpers/consent-scopes.helpers.ts +58 -0
- package/src/helpers/contract-event-maintenance.helpers.ts +43 -0
- package/src/helpers/contract-events.helpers.ts +540 -0
- package/src/helpers/contract-requests.helpers.ts +211 -0
- package/src/helpers/credentialSubject.helpers.test.ts +114 -0
- package/src/helpers/credentialSubject.helpers.ts +40 -0
- package/src/helpers/finalize-inbox.helpers.ts +163 -44
- package/src/helpers/inbox.helpers.ts +58 -70
- package/src/helpers/inline-template.helpers.ts +560 -0
- package/src/helpers/notificationMessages.ts +77 -1
- package/src/helpers/notificationQueue.helpers.ts +23 -5
- package/src/helpers/notifications.helpers.ts +25 -1
- package/src/helpers/presentation-metadata.ts +41 -0
- package/src/helpers/presentation.helpers.ts +27 -20
- package/src/helpers/profile.helpers.ts +13 -0
- package/src/helpers/share-content-client/adapters.ts +55 -0
- package/src/helpers/share-content-client/canonical.ts +25 -0
- package/src/helpers/share-content-client/client.ts +440 -0
- package/src/helpers/share-content-client/config.ts +189 -0
- package/src/helpers/share-content-client/index.ts +27 -0
- package/src/helpers/share-content-client/responses.ts +273 -0
- package/src/helpers/share-content-client/types.ts +253 -0
- package/src/helpers/share-link-attachment-access.ts +87 -0
- package/src/helpers/share-link-attachment.ts +115 -0
- package/src/helpers/share-link-coordinator/attachment-stat.ts +41 -0
- package/src/helpers/share-link-coordinator/budget-helpers.ts +53 -0
- package/src/helpers/share-link-coordinator/cleanup-runner.ts +286 -0
- package/src/helpers/share-link-coordinator/coordinator.ts +727 -0
- package/src/helpers/share-link-coordinator/index.ts +8 -0
- package/src/helpers/share-link-coordinator/recovery-runner.ts +443 -0
- package/src/helpers/share-link-coordinator/runtime.ts +88 -0
- package/src/helpers/share-link-coordinator/stat.ts +30 -0
- package/src/helpers/share-link-coordinator/types.ts +353 -0
- package/src/helpers/share-link-lifecycle/index.ts +4 -0
- package/src/helpers/share-link-lifecycle/lease.helpers.ts +35 -0
- package/src/helpers/share-link-lifecycle/recovery.helpers.ts +173 -0
- package/src/helpers/share-link-lifecycle/request-hash.helpers.ts +130 -0
- package/src/helpers/share-link-lifecycle/types.ts +58 -0
- package/src/helpers/share-link-maintenance/budget.ts +63 -0
- package/src/helpers/share-link-maintenance/config.ts +312 -0
- package/src/helpers/share-link-maintenance/index.ts +24 -0
- package/src/helpers/share-link-maintenance/runner.ts +251 -0
- package/src/helpers/share-link-maintenance/runtime.ts +340 -0
- package/src/helpers/share-link-maintenance/scheduler.ts +126 -0
- package/src/helpers/share-link-maintenance/telemetry.ts +73 -0
- package/src/helpers/share-link-maintenance/types.ts +91 -0
- package/src/helpers/share-link-owner/config.ts +48 -0
- package/src/helpers/share-link-owner/diagnostics.ts +89 -0
- package/src/helpers/share-link-owner/lazy-initializer.ts +29 -0
- package/src/helpers/share-link-owner-projection.ts +54 -0
- package/src/helpers/share-link-passcode-abuse.ts +36 -0
- package/src/helpers/share-link-passcode.ts +47 -0
- package/src/helpers/share-link-policy/index.ts +7 -0
- package/src/helpers/share-link-policy/production.ts +108 -0
- package/src/helpers/share-link-policy/resolver.ts +113 -0
- package/src/helpers/share-link-policy/types.ts +56 -0
- package/src/helpers/share-link-public/config.ts +41 -0
- package/src/helpers/share-link-view-notification.ts +18 -0
- package/src/helpers/signingAuthority.helpers.ts +23 -14
- package/src/helpers/smartResume.helpers.ts +123 -0
- package/src/models/AppManifestVersion.ts +43 -0
- package/src/models/AppStoreListing.ts +32 -0
- package/src/models/ConsentFlowContract.ts +34 -3
- package/src/models/ConsentFlowTerms.ts +10 -0
- package/src/models/ConsentFlowTransaction.ts +3 -0
- package/src/models/Integration.ts +2 -0
- package/src/models/Presentation.ts +2 -2
- package/src/models/Profile.ts +3 -2
- package/src/models/ShareLink.ts +134 -0
- package/src/models/ShareViewReceipt.ts +69 -0
- package/src/models/index.ts +25 -0
- package/src/models/share-link-constraints.ts +98 -0
- package/src/openapi.ts +44 -8
- package/src/public-share-link-http.ts +49 -0
- package/src/routes/app-store.ts +1056 -38
- package/src/routes/boosts.ts +14 -25
- package/src/routes/contracts.ts +384 -273
- package/src/routes/inbox.ts +4 -2
- package/src/routes/index.ts +142 -44
- package/src/routes/presentations.ts +19 -3
- package/src/routes/profile-manager.ts +29 -2
- package/src/routes/profiles.ts +29 -6
- package/src/routes/public-share-links.ts +983 -0
- package/src/routes/share-links.ts +709 -0
- package/src/routes/workflows.ts +3 -2
- package/src/services/delivery/adapters/postmark.adapter.ts +4 -1
- package/src/types/app-manifest.ts +28 -0
- package/src/types/app-store-listing.ts +13 -0
- package/src/types/consentflowcontract.ts +25 -4
- package/test/app-manifest.helpers.spec.ts +170 -0
- package/test/app-manifest.routes.spec.ts +299 -0
- package/test/app-store-listing-updates.spec.ts +236 -0
- package/test/app-store-listing.spec.ts +65 -3
- package/test/consent-data-boundaries.spec.ts +352 -0
- package/test/consent-recipients.spec.ts +513 -0
- package/test/consent-scopes.helpers.spec.ts +67 -0
- package/test/consentflow.spec.ts +63 -2
- package/test/contract-requests.spec.ts +1588 -0
- package/test/credential-storage.neo4j.spec.ts +122 -0
- package/test/dids.spec.ts +10 -1
- package/test/guardian-gated-routes.spec.ts +49 -0
- package/test/helpers/share-link-fixtures.ts +94 -0
- package/test/inbox-security.e2e.ts +26 -10
- package/test/inbox.spec.ts +184 -8
- package/test/inline-template.helpers.spec.ts +49 -0
- package/test/managed-recipient-inbox.spec.ts +152 -0
- package/test/notificationMessages.spec.ts +13 -0
- package/test/openapi.spec.ts +14 -53
- package/test/presentations.spec.ts +54 -6
- package/test/profiles.spec.ts +97 -1
- package/test/share-content-client.contract.spec.ts +365 -0
- package/test/share-content-client.unit.spec.ts +462 -0
- package/test/share-link-abuse.unit.spec.ts +65 -0
- package/test/share-link-attachment.neo4j.spec.ts +262 -0
- package/test/share-link-attachment.unit.spec.ts +244 -0
- package/test/share-link-coordinator.neo4j.spec.ts +684 -0
- package/test/share-link-coordinator.unit.spec.ts +990 -0
- package/test/share-link-dependency-diagnostics.unit.spec.ts +127 -0
- package/test/share-link-lifecycle.neo4j.spec.ts +1411 -0
- package/test/share-link-lifecycle.unit.spec.ts +205 -0
- package/test/share-link-list-cursor.unit.spec.ts +86 -0
- package/test/share-link-list.neo4j.spec.ts +324 -0
- package/test/share-link-maintenance.neo4j.spec.ts +355 -0
- package/test/share-link-maintenance.unit.spec.ts +1140 -0
- package/test/share-link-owner-policy.unit.spec.ts +210 -0
- package/test/share-link-owner-route.unit.spec.ts +820 -0
- package/test/share-link-owner-runtime.unit.spec.ts +102 -0
- package/test/share-link-passcode.unit.spec.ts +29 -0
- package/test/share-link-policy.unit.spec.ts +149 -0
- package/test/share-link-presentation-metadata.unit.spec.ts +49 -0
- package/test/share-link-production-policy.unit.spec.ts +126 -0
- package/test/share-link-public-config.unit.spec.ts +64 -0
- package/test/share-link-public-http-adapter.unit.spec.ts +315 -0
- package/test/share-link-public-route.unit.spec.ts +1079 -0
- package/test/share-link-receipt.neo4j.spec.ts +943 -0
- package/test/share-link-recovery.neo4j.spec.ts +935 -0
- package/test/share-link-recovery.unit.spec.ts +726 -0
- package/test/share-link-service-policy.neo4j.spec.ts +403 -0
- package/test/signing-authority-encryption.spec.ts +75 -0
- package/test/smart-resume-consent.spec.ts +233 -0
- package/test/smart-resume-upload.spec.ts +65 -0
- package/tsconfig.build.json +1 -1
- package/tsconfig.json +1 -0
- package/vitest.config.ts +14 -0
- package/vitest.inbox-e2e.config.ts +2 -0
- package/vitest.share-links-neo4j.config.ts +19 -0
- package/vitest.share-links-unit.config.ts +28 -0
- package/workerLambda.test.ts +100 -0
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import {
|
|
2
|
+
PutShareLinkAttachmentChunkInputValidator,
|
|
3
|
+
DeleteShareLinkAttachmentChunksInputValidator,
|
|
4
|
+
} from '@learncard/types';
|
|
5
|
+
import type {
|
|
6
|
+
PutShareLinkAttachmentChunkInput,
|
|
7
|
+
DeleteShareLinkAttachmentChunksInput,
|
|
8
|
+
} from '@learncard/types';
|
|
9
|
+
import type { ShareContentClient } from './share-content-client';
|
|
10
|
+
import {
|
|
11
|
+
beginShareAttachmentChunk,
|
|
12
|
+
completeShareAttachmentChunk,
|
|
13
|
+
deleteUnreferencedShareAttachment,
|
|
14
|
+
shareAttachmentTuple,
|
|
15
|
+
type ShareAttachmentBinding,
|
|
16
|
+
} from '../accesslayer/share-link/attachment';
|
|
17
|
+
import { ShareLinkCoordinatorError } from './share-link-coordinator/types';
|
|
18
|
+
import type { ShareOwnerContext } from './share-link-coordinator/types';
|
|
19
|
+
import { isShareLinkRepositoryError } from '../accesslayer/share-link/errors';
|
|
20
|
+
|
|
21
|
+
export type ShareLinkAttachmentOwnerApi = {
|
|
22
|
+
put: (
|
|
23
|
+
input: PutShareLinkAttachmentChunkInput,
|
|
24
|
+
owner: ShareOwnerContext
|
|
25
|
+
) => Promise<{ ok: true }>;
|
|
26
|
+
delete: (
|
|
27
|
+
input: DeleteShareLinkAttachmentChunksInput,
|
|
28
|
+
owner: ShareOwnerContext
|
|
29
|
+
) => Promise<{ ok: boolean }>;
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
export const createShareLinkAttachmentOwnerApi = (
|
|
33
|
+
client: Pick<ShareContentClient, 'put' | 'delete'>,
|
|
34
|
+
repository = {
|
|
35
|
+
begin: beginShareAttachmentChunk,
|
|
36
|
+
complete: completeShareAttachmentChunk,
|
|
37
|
+
deleteUnreferenced: deleteUnreferencedShareAttachment,
|
|
38
|
+
}
|
|
39
|
+
): ShareLinkAttachmentOwnerApi => {
|
|
40
|
+
const bindingFor = (
|
|
41
|
+
input: DeleteShareLinkAttachmentChunksInput,
|
|
42
|
+
owner: ShareOwnerContext
|
|
43
|
+
): ShareAttachmentBinding => ({
|
|
44
|
+
namespace: owner.namespace,
|
|
45
|
+
ownerProfileId: owner.ownerProfileId,
|
|
46
|
+
shareId: input.id,
|
|
47
|
+
contentVersion: input.contentVersion,
|
|
48
|
+
attachmentId: input.attachmentId,
|
|
49
|
+
chunkCount: input.chunkCount,
|
|
50
|
+
});
|
|
51
|
+
return {
|
|
52
|
+
put: async (input, owner) => {
|
|
53
|
+
const parsed = PutShareLinkAttachmentChunkInputValidator.safeParse(input);
|
|
54
|
+
if (!parsed.success)
|
|
55
|
+
throw new ShareLinkCoordinatorError('INVALID_INPUT', 'invalid attachment');
|
|
56
|
+
const value = parsed.data;
|
|
57
|
+
const binding = bindingFor(value, owner);
|
|
58
|
+
try {
|
|
59
|
+
await repository.begin(binding, value.chunkIndex);
|
|
60
|
+
const result = await client.put({
|
|
61
|
+
...shareAttachmentTuple(binding, value.chunkIndex),
|
|
62
|
+
envelope: value.envelope,
|
|
63
|
+
ownerEncryptedRecovery: value.ownerEncryptedRecovery,
|
|
64
|
+
});
|
|
65
|
+
if (!result.ok)
|
|
66
|
+
throw new ShareLinkCoordinatorError(
|
|
67
|
+
result.error === 'CONFLICT' ? 'CONFLICT' : 'UNAVAILABLE',
|
|
68
|
+
'attachment upload unavailable'
|
|
69
|
+
);
|
|
70
|
+
await repository.complete(binding, value.chunkIndex);
|
|
71
|
+
return { ok: true };
|
|
72
|
+
} catch (error) {
|
|
73
|
+
if (error instanceof ShareLinkCoordinatorError) throw error;
|
|
74
|
+
const code = isShareLinkRepositoryError(error) ? error.code : undefined;
|
|
75
|
+
throw new ShareLinkCoordinatorError(
|
|
76
|
+
code === 'CONFLICT'
|
|
77
|
+
? 'CONFLICT'
|
|
78
|
+
: code === 'NOT_FOUND'
|
|
79
|
+
? 'NOT_FOUND'
|
|
80
|
+
: code === 'PRECONDITION_FAILED' || code === 'INVALID_INPUT'
|
|
81
|
+
? code
|
|
82
|
+
: 'UNAVAILABLE',
|
|
83
|
+
'attachment upload unavailable'
|
|
84
|
+
);
|
|
85
|
+
}
|
|
86
|
+
},
|
|
87
|
+
delete: async (input, owner) => {
|
|
88
|
+
const parsed = DeleteShareLinkAttachmentChunksInputValidator.safeParse(input);
|
|
89
|
+
if (!parsed.success)
|
|
90
|
+
throw new ShareLinkCoordinatorError('INVALID_INPUT', 'invalid attachment');
|
|
91
|
+
const binding = bindingFor(parsed.data, owner);
|
|
92
|
+
let allowed: boolean;
|
|
93
|
+
try {
|
|
94
|
+
allowed = await repository.deleteUnreferenced(binding);
|
|
95
|
+
} catch {
|
|
96
|
+
throw new ShareLinkCoordinatorError(
|
|
97
|
+
'UNAVAILABLE',
|
|
98
|
+
'attachment cleanup unavailable'
|
|
99
|
+
);
|
|
100
|
+
}
|
|
101
|
+
if (!allowed) return { ok: false };
|
|
102
|
+
// Durable jobs were queued before I/O; interrupted/failed deletes are retried by maintenance.
|
|
103
|
+
let ok = true;
|
|
104
|
+
for (let index = 0; index < binding.chunkCount; index++) {
|
|
105
|
+
try {
|
|
106
|
+
const result = await client.delete(shareAttachmentTuple(binding, index));
|
|
107
|
+
if (!result.ok) ok = false;
|
|
108
|
+
} catch {
|
|
109
|
+
ok = false;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
return { ok };
|
|
113
|
+
},
|
|
114
|
+
};
|
|
115
|
+
};
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { shareAttachmentTuple } from '../../accesslayer/share-link/attachment';
|
|
2
|
+
import type { ShareLinkReservationRecord } from '../../accesslayer/share-link/types';
|
|
3
|
+
import type { ShareContentClient } from '../share-content-client';
|
|
4
|
+
import type { MaintenanceBudget } from './types';
|
|
5
|
+
import { budgetAllows } from './budget-helpers';
|
|
6
|
+
|
|
7
|
+
/** Verify the complete immutable set before releasing the atomic commit fence. */
|
|
8
|
+
export const verifyShareAttachmentStats = async (
|
|
9
|
+
client: Pick<ShareContentClient, 'stat'>,
|
|
10
|
+
reservation: ShareLinkReservationRecord,
|
|
11
|
+
budget?: MaintenanceBudget,
|
|
12
|
+
remoteAndFinalizeCostMs = 0
|
|
13
|
+
): Promise<boolean> => {
|
|
14
|
+
if (reservation.contentVersion === null || !reservation.attachmentId) return true;
|
|
15
|
+
const count = reservation.attachmentChunkCount;
|
|
16
|
+
if (!count || count < 1 || count > 16) return false;
|
|
17
|
+
const binding = {
|
|
18
|
+
namespace: reservation.namespace,
|
|
19
|
+
ownerProfileId: reservation.ownerProfileId,
|
|
20
|
+
shareId: reservation.shareId,
|
|
21
|
+
contentVersion: reservation.contentVersion,
|
|
22
|
+
attachmentId: reservation.attachmentId,
|
|
23
|
+
chunkCount: count,
|
|
24
|
+
};
|
|
25
|
+
for (let index = 0; index < count; index++) {
|
|
26
|
+
// Each bounded HTTP call must leave enough time for finalization and the
|
|
27
|
+
// pass reserve. A many-chunk asset cannot overrun the maintenance budget.
|
|
28
|
+
if (!budgetAllows(budget, remoteAndFinalizeCostMs)) return false;
|
|
29
|
+
const tuple = shareAttachmentTuple(binding, index);
|
|
30
|
+
const result = await client.stat(tuple);
|
|
31
|
+
if (!result.ok || result.value.kind !== 'active') return false;
|
|
32
|
+
const active = result.value;
|
|
33
|
+
if (
|
|
34
|
+
Object.entries(tuple).some(
|
|
35
|
+
([key, value]) => active[key as keyof typeof active] !== value
|
|
36
|
+
)
|
|
37
|
+
)
|
|
38
|
+
return false;
|
|
39
|
+
}
|
|
40
|
+
return true;
|
|
41
|
+
};
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import type { MaintenanceBudget } from './types';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* True only for the maintenance budget that opted into whole-unit admission.
|
|
5
|
+
* Legacy/direct callers that pass a bare budget keep the reviewed `exhausted`
|
|
6
|
+
* gate and are never subjected to the extra mid-unit rechecks.
|
|
7
|
+
*/
|
|
8
|
+
export const budgetHasUnitReserve = (budget: MaintenanceBudget | undefined): boolean =>
|
|
9
|
+
budget !== undefined && typeof budget.reserveMs === 'function';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Cooperative budget helpers shared by the recovery and cleanup runners.
|
|
13
|
+
*
|
|
14
|
+
* `budgetAllows` is deliberately conservative only when the caller opted into the
|
|
15
|
+
* maintenance reserve (`reserveMs`). Direct/legacy callers that pass a bare budget
|
|
16
|
+
* keep the reviewed `exhausted()` behaviour, so this cannot silently disable a
|
|
17
|
+
* one-shot runner that predates the maintenance scheduler.
|
|
18
|
+
*/
|
|
19
|
+
export const budgetAllows = (budget: MaintenanceBudget | undefined, costMs: number): boolean => {
|
|
20
|
+
if (!budget) return true;
|
|
21
|
+
|
|
22
|
+
if (typeof budget.reserveMs !== 'function' || !Number.isFinite(costMs)) {
|
|
23
|
+
return !budget.exhausted();
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const remaining = budget.remainingMs();
|
|
27
|
+
if (!Number.isFinite(remaining)) return false;
|
|
28
|
+
|
|
29
|
+
return remaining - budget.reserveMs() >= Math.max(0, costMs);
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Bound a per-call managed-transaction timeout by the time actually left before
|
|
34
|
+
* the pass reserve. The result is always >= 1 so an explicit maintenance
|
|
35
|
+
* transaction still has a positive server-side bound; it can never extend the
|
|
36
|
+
* configured timeout.
|
|
37
|
+
*/
|
|
38
|
+
export const boundTransactionTimeout = (
|
|
39
|
+
configuredTimeoutMs: number | undefined,
|
|
40
|
+
budget: MaintenanceBudget | undefined
|
|
41
|
+
): number | undefined => {
|
|
42
|
+
if (configuredTimeoutMs === undefined) return undefined;
|
|
43
|
+
if (!budget) return configuredTimeoutMs;
|
|
44
|
+
|
|
45
|
+
const remaining = budget.remainingMs();
|
|
46
|
+
if (!Number.isFinite(remaining)) return configuredTimeoutMs;
|
|
47
|
+
|
|
48
|
+
const reserved = typeof budget.reserveMs === 'function' ? budget.reserveMs() : 0;
|
|
49
|
+
const available = remaining - reserved;
|
|
50
|
+
if (!Number.isFinite(available)) return configuredTimeoutMs;
|
|
51
|
+
|
|
52
|
+
return Math.max(1, Math.trunc(Math.min(configuredTimeoutMs, available)));
|
|
53
|
+
};
|
|
@@ -0,0 +1,286 @@
|
|
|
1
|
+
import {
|
|
2
|
+
SHARE_LINK_CLEANUP_DEFAULT_BATCH,
|
|
3
|
+
SHARE_LINK_CLEANUP_MAX_BATCH,
|
|
4
|
+
} from '../share-link-lifecycle';
|
|
5
|
+
import {
|
|
6
|
+
isTransientShareContentError,
|
|
7
|
+
type ShareContentClientErrorCode,
|
|
8
|
+
} from '../share-content-client/types';
|
|
9
|
+
import {
|
|
10
|
+
ShareLinkCoordinatorError,
|
|
11
|
+
type CleanupRunnerDependencies,
|
|
12
|
+
type CleanupRunSummary,
|
|
13
|
+
type ShareCleanupCategory,
|
|
14
|
+
} from './types';
|
|
15
|
+
import { budgetAllows, budgetHasUnitReserve, boundTransactionTimeout } from './budget-helpers';
|
|
16
|
+
|
|
17
|
+
const OPAQUE_IDENTIFIER_RE = /^[A-Za-z0-9._~-]+$/;
|
|
18
|
+
|
|
19
|
+
const isOpaqueIdentifier = (value: unknown, maxLength = 128): value is string =>
|
|
20
|
+
typeof value === 'string' &&
|
|
21
|
+
value.length >= 1 &&
|
|
22
|
+
value.length <= maxLength &&
|
|
23
|
+
OPAQUE_IDENTIFIER_RE.test(value);
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Validate the operator-configured scope before any graph or remote I/O. A
|
|
27
|
+
* missing or malformed namespace means the pass does nothing at all.
|
|
28
|
+
*/
|
|
29
|
+
const assertRunnerScope = (dependencies: CleanupRunnerDependencies): void => {
|
|
30
|
+
if (!isOpaqueIdentifier(dependencies.claimant)) {
|
|
31
|
+
throw new ShareLinkCoordinatorError(
|
|
32
|
+
'INVALID_INPUT',
|
|
33
|
+
'claimant must be an opaque configured identifier'
|
|
34
|
+
);
|
|
35
|
+
}
|
|
36
|
+
if (!isOpaqueIdentifier(dependencies.namespace)) {
|
|
37
|
+
throw new ShareLinkCoordinatorError(
|
|
38
|
+
'INVALID_INPUT',
|
|
39
|
+
'namespace must be an opaque configured identifier'
|
|
40
|
+
);
|
|
41
|
+
}
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
const clampTotal = (limit: number | undefined): number => {
|
|
45
|
+
if (!Number.isSafeInteger(limit) || (limit as number) < 1)
|
|
46
|
+
return SHARE_LINK_CLEANUP_DEFAULT_BATCH;
|
|
47
|
+
|
|
48
|
+
return Math.min(limit as number, SHARE_LINK_CLEANUP_MAX_BATCH);
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
const clampBatchSize = (batchSize: number | undefined, total: number): number => {
|
|
52
|
+
if (!Number.isSafeInteger(batchSize) || (batchSize as number) < 1) return total;
|
|
53
|
+
|
|
54
|
+
return Math.min(batchSize as number, total);
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
const recordCategory = (
|
|
58
|
+
categories: Partial<Record<ShareCleanupCategory, number>>,
|
|
59
|
+
category: ShareCleanupCategory
|
|
60
|
+
): void => {
|
|
61
|
+
categories[category] = (categories[category] ?? 0) + 1;
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Cap the managed-transaction timeout by the time left in the pass so a graph
|
|
66
|
+
* unit cannot outlive a cooperative deadline. No budget means the configured
|
|
67
|
+
* repository default is used unchanged.
|
|
68
|
+
*/
|
|
69
|
+
const boundTransactionTimeoutMs = (
|
|
70
|
+
configuredTimeoutMs: number | undefined,
|
|
71
|
+
budget: CleanupRunnerDependencies['budget']
|
|
72
|
+
): number | undefined => boundTransactionTimeout(configuredTimeoutMs, budget);
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Worst-case bounded cost of one whole cleanup unit:
|
|
76
|
+
* claim (graph) + delete (HTTP) + complete (graph).
|
|
77
|
+
* The HTTP allowance is fixed when the transport client is constructed, so it
|
|
78
|
+
* cannot be reduced per call; the graph calls are bounded per call below.
|
|
79
|
+
*/
|
|
80
|
+
const cleanupUnitCostMs = (dependencies: CleanupRunnerDependencies): number =>
|
|
81
|
+
(dependencies.requestTimeoutMs ?? 0) + 2 * (dependencies.transactionTimeoutMs ?? 0);
|
|
82
|
+
|
|
83
|
+
/** Cost of the remote/complete tail once a job has already been claimed. */
|
|
84
|
+
const cleanupRemoteCostMs = (dependencies: CleanupRunnerDependencies): number =>
|
|
85
|
+
(dependencies.requestTimeoutMs ?? 0) + (dependencies.transactionTimeoutMs ?? 0);
|
|
86
|
+
|
|
87
|
+
type DeleteOutcome =
|
|
88
|
+
| { ok: true }
|
|
89
|
+
| { ok: false; transient: true; error: ShareContentClientErrorCode }
|
|
90
|
+
| { ok: false; category: ShareCleanupCategory };
|
|
91
|
+
|
|
92
|
+
type ClaimedCleanupJob = Awaited<
|
|
93
|
+
ReturnType<CleanupRunnerDependencies['repository']['claimCleanupJobs']>
|
|
94
|
+
>['jobs'][number];
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Bounded, one-shot cleanup runner for durable C3 cleanup jobs.
|
|
98
|
+
*
|
|
99
|
+
* It is deliberately NOT a scheduler: nothing registers it, no interval is
|
|
100
|
+
* created, and no ShareLink routes or workers are enabled by importing it. The
|
|
101
|
+
* maintenance scheduler owns cadence.
|
|
102
|
+
*
|
|
103
|
+
* For every claimed job it sends the exact immutable binding —
|
|
104
|
+
* `{ namespace, ownerProfileId, shareId, contentVersion, objectId: objectRef,
|
|
105
|
+
* operationId }` — to LearnCloud `delete`. `operationId` is the ORIGINAL object
|
|
106
|
+
* operation id recorded by C3, never the latest metadata operation.
|
|
107
|
+
*
|
|
108
|
+
* A job is completed only on a proven LearnCloud deletion/tombstone response.
|
|
109
|
+
* Transient/unavailable responses are returned to the queue with C3's capped
|
|
110
|
+
* exponential backoff. Any other response leaves the job claimed (never
|
|
111
|
+
* completed) so the fenced claim expires and another worker can retry; this
|
|
112
|
+
* never pretends a deletion happened. The returned summary contains aggregate
|
|
113
|
+
* counts and fixed categories only — never an object ref or exception text.
|
|
114
|
+
*/
|
|
115
|
+
export const runShareContentCleanupOnce = async (
|
|
116
|
+
dependencies: CleanupRunnerDependencies
|
|
117
|
+
): Promise<CleanupRunSummary> => {
|
|
118
|
+
assertRunnerScope(dependencies);
|
|
119
|
+
|
|
120
|
+
const { repository, client } = dependencies;
|
|
121
|
+
const total = clampTotal(dependencies.limit);
|
|
122
|
+
const batchSize = clampBatchSize(dependencies.claimBatchSize, total);
|
|
123
|
+
|
|
124
|
+
const summary: CleanupRunSummary = {
|
|
125
|
+
claimed: 0,
|
|
126
|
+
completed: 0,
|
|
127
|
+
retried: 0,
|
|
128
|
+
claimLost: 0,
|
|
129
|
+
skipped: 0,
|
|
130
|
+
categories: {},
|
|
131
|
+
};
|
|
132
|
+
|
|
133
|
+
// Claim just-in-time so a deadline can never strand a whole claimed batch.
|
|
134
|
+
// The default (batch size >= total) keeps the reviewed single-claim
|
|
135
|
+
// behaviour; maintenance sets a small batch size and re-checks the budget
|
|
136
|
+
// before every claim.
|
|
137
|
+
const singleShot = batchSize >= total;
|
|
138
|
+
|
|
139
|
+
while (summary.claimed < total) {
|
|
140
|
+
// Whole-unit admission: a claim is only started when the remaining budget
|
|
141
|
+
// can also cover the delete and the completion (or the retry write).
|
|
142
|
+
if (!budgetAllows(dependencies.budget, cleanupUnitCostMs(dependencies))) break;
|
|
143
|
+
|
|
144
|
+
const remaining = total - summary.claimed;
|
|
145
|
+
const claimLimit = singleShot ? total : Math.min(batchSize, remaining);
|
|
146
|
+
|
|
147
|
+
let claim: Awaited<ReturnType<CleanupRunnerDependencies['repository']['claimCleanupJobs']>>;
|
|
148
|
+
try {
|
|
149
|
+
claim = await repository.claimCleanupJobs({
|
|
150
|
+
namespace: dependencies.namespace,
|
|
151
|
+
claimant: dependencies.claimant,
|
|
152
|
+
limit: claimLimit,
|
|
153
|
+
claimMs: dependencies.claimMs,
|
|
154
|
+
transactionTimeoutMs: boundTransactionTimeoutMs(
|
|
155
|
+
dependencies.transactionTimeoutMs,
|
|
156
|
+
dependencies.budget
|
|
157
|
+
),
|
|
158
|
+
noInlineRetry: dependencies.noInlineRetry,
|
|
159
|
+
// Omit production time so the repository samples it after the lock.
|
|
160
|
+
now: dependencies.now?.(),
|
|
161
|
+
});
|
|
162
|
+
} catch {
|
|
163
|
+
// Could not confirm a claim; a later pass retries after the lease.
|
|
164
|
+
summary.claimLost += 1;
|
|
165
|
+
recordCategory(summary.categories, 'claim_error');
|
|
166
|
+
break;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
if (claim.jobs.length === 0) break;
|
|
170
|
+
|
|
171
|
+
let stoppedBeforeRemote = false;
|
|
172
|
+
|
|
173
|
+
for (const job of claim.jobs) {
|
|
174
|
+
summary.claimed += 1;
|
|
175
|
+
|
|
176
|
+
// Recheck after the awaited claim, before the network call: the claim
|
|
177
|
+
// may have waited on a lock. If the tail no longer fits, leave the job
|
|
178
|
+
// fenced (its lease will lapse) and start no further call. Only the
|
|
179
|
+
// maintenance budget opts into this mid-unit reserve.
|
|
180
|
+
if (
|
|
181
|
+
budgetHasUnitReserve(dependencies.budget) &&
|
|
182
|
+
!budgetAllows(dependencies.budget, cleanupRemoteCostMs(dependencies))
|
|
183
|
+
) {
|
|
184
|
+
recordCategory(summary.categories, 'budget_exhausted');
|
|
185
|
+
stoppedBeforeRemote = true;
|
|
186
|
+
break;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
const deletion = await safeDelete(client, job);
|
|
190
|
+
|
|
191
|
+
if (deletion.ok) {
|
|
192
|
+
const completion = await safeComplete(repository, {
|
|
193
|
+
objectRef: job.objectRef,
|
|
194
|
+
claimToken: claim.claimToken,
|
|
195
|
+
outcome: 'completed',
|
|
196
|
+
transactionTimeoutMs: boundTransactionTimeoutMs(
|
|
197
|
+
dependencies.transactionTimeoutMs,
|
|
198
|
+
dependencies.budget
|
|
199
|
+
),
|
|
200
|
+
noInlineRetry: dependencies.noInlineRetry,
|
|
201
|
+
now: dependencies.now?.(),
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
if (completion?.outcome === 'completed') summary.completed += 1;
|
|
205
|
+
else {
|
|
206
|
+
summary.claimLost += 1;
|
|
207
|
+
recordCategory(summary.categories, 'claim_lost');
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
continue;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
if ('transient' in deletion) {
|
|
214
|
+
const completion = await safeComplete(repository, {
|
|
215
|
+
objectRef: job.objectRef,
|
|
216
|
+
claimToken: claim.claimToken,
|
|
217
|
+
outcome: 'retry',
|
|
218
|
+
errorMessage: deletion.error,
|
|
219
|
+
transactionTimeoutMs: boundTransactionTimeoutMs(
|
|
220
|
+
dependencies.transactionTimeoutMs,
|
|
221
|
+
dependencies.budget
|
|
222
|
+
),
|
|
223
|
+
noInlineRetry: dependencies.noInlineRetry,
|
|
224
|
+
now: dependencies.now?.(),
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
if (completion?.outcome === 'retry') summary.retried += 1;
|
|
228
|
+
else {
|
|
229
|
+
summary.claimLost += 1;
|
|
230
|
+
recordCategory(summary.categories, 'claim_lost');
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
continue;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
// Deterministic/authorization failure: never complete. Leave the claim to
|
|
237
|
+
// expire so the bounded fence lets another worker retry later.
|
|
238
|
+
summary.skipped += 1;
|
|
239
|
+
recordCategory(summary.categories, deletion.category);
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
if (singleShot || stoppedBeforeRemote) break;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
return summary;
|
|
246
|
+
};
|
|
247
|
+
|
|
248
|
+
const safeDelete = async (
|
|
249
|
+
client: CleanupRunnerDependencies['client'],
|
|
250
|
+
job: ClaimedCleanupJob
|
|
251
|
+
): Promise<DeleteOutcome> => {
|
|
252
|
+
let result: Awaited<ReturnType<CleanupRunnerDependencies['client']['delete']>>;
|
|
253
|
+
|
|
254
|
+
try {
|
|
255
|
+
result = await client.delete({
|
|
256
|
+
namespace: job.namespace,
|
|
257
|
+
ownerProfileId: job.ownerProfileId,
|
|
258
|
+
shareId: job.shareId,
|
|
259
|
+
contentVersion: job.contentVersion,
|
|
260
|
+
objectId: job.objectRef,
|
|
261
|
+
operationId: job.operationId,
|
|
262
|
+
});
|
|
263
|
+
} catch {
|
|
264
|
+
return { ok: false, category: 'client_error' };
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
if (result.ok) return { ok: true };
|
|
268
|
+
if (isTransientShareContentError(result.error)) {
|
|
269
|
+
return { ok: false, transient: true, error: result.error };
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
return { ok: false, category: result.error };
|
|
273
|
+
};
|
|
274
|
+
|
|
275
|
+
const safeComplete = async (
|
|
276
|
+
repository: CleanupRunnerDependencies['repository'],
|
|
277
|
+
input: Parameters<CleanupRunnerDependencies['repository']['completeCleanupJob']>[0]
|
|
278
|
+
): Promise<Awaited<
|
|
279
|
+
ReturnType<CleanupRunnerDependencies['repository']['completeCleanupJob']>
|
|
280
|
+
> | null> => {
|
|
281
|
+
try {
|
|
282
|
+
return await repository.completeCleanupJob(input);
|
|
283
|
+
} catch {
|
|
284
|
+
return null;
|
|
285
|
+
}
|
|
286
|
+
};
|