@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,126 @@
|
|
|
1
|
+
import { safeLogMaintenance } from './telemetry';
|
|
2
|
+
import type { ShareLinkMaintenanceLogger, ShareLinkMaintenanceRunSummary } from './types';
|
|
3
|
+
|
|
4
|
+
export type ShareLinkMaintenanceSchedulerDependencies = {
|
|
5
|
+
runOnce: (options?: { remainingTimeMs?: number }) => Promise<unknown>;
|
|
6
|
+
intervalMs: number;
|
|
7
|
+
logger: ShareLinkMaintenanceLogger;
|
|
8
|
+
setTimeoutFn?: (handler: () => void, timeoutMs: number) => unknown;
|
|
9
|
+
clearTimeoutFn?: (handle: unknown) => void;
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
export type ShareLinkMaintenanceScheduler = {
|
|
13
|
+
/** Idempotent; schedules the first bounded pass one interval from now. */
|
|
14
|
+
start: () => void;
|
|
15
|
+
/** Stops scheduling and drains an in-flight pass. */
|
|
16
|
+
stop: () => Promise<void>;
|
|
17
|
+
/** Runs one pass now, coalescing with an in-flight pass (no overlap). */
|
|
18
|
+
trigger: () => Promise<void>;
|
|
19
|
+
isRunning: () => boolean;
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
const defaultSetTimeout = (handler: () => void, timeoutMs: number): unknown =>
|
|
23
|
+
setTimeout(handler, timeoutMs);
|
|
24
|
+
|
|
25
|
+
const defaultClearTimeout = (handle: unknown): void => {
|
|
26
|
+
clearTimeout(handle as ReturnType<typeof setTimeout>);
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
const fixedFailureSummary = (intervalMs: number): ShareLinkMaintenanceRunSummary => ({
|
|
30
|
+
status: 'failed',
|
|
31
|
+
durationMs: 0,
|
|
32
|
+
budgetMs: intervalMs,
|
|
33
|
+
recovery: null,
|
|
34
|
+
cleanup: null,
|
|
35
|
+
receiptPrune: null,
|
|
36
|
+
categories: { share_link_maintenance_pass_failed: 1 },
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Non-overlapping maintenance loop.
|
|
41
|
+
*
|
|
42
|
+
* The next tick is scheduled only *after* the awaited pass settles, so a slow
|
|
43
|
+
* pass can never stack timers. `trigger` coalesces with an in-flight pass, which
|
|
44
|
+
* is the per-process overlap guard; other processes are fenced by the durable
|
|
45
|
+
* repository claims. `stop` is terminal: it clears the pending tick, awaits the
|
|
46
|
+
* in-flight pass, and rejects any later explicit trigger or already-queued timer
|
|
47
|
+
* callback. Nothing here registers a timer at module import, and a logger failure
|
|
48
|
+
* cannot corrupt the loop state.
|
|
49
|
+
*/
|
|
50
|
+
export const createShareLinkMaintenanceScheduler = (
|
|
51
|
+
dependencies: ShareLinkMaintenanceSchedulerDependencies
|
|
52
|
+
): ShareLinkMaintenanceScheduler => {
|
|
53
|
+
const setTimeoutFn = dependencies.setTimeoutFn ?? defaultSetTimeout;
|
|
54
|
+
const clearTimeoutFn = dependencies.clearTimeoutFn ?? defaultClearTimeout;
|
|
55
|
+
|
|
56
|
+
let started = false;
|
|
57
|
+
let stopped = false;
|
|
58
|
+
let timer: unknown = null;
|
|
59
|
+
let inFlight: Promise<void> | null = null;
|
|
60
|
+
|
|
61
|
+
const scheduleNext = (): void => {
|
|
62
|
+
if (stopped || !started) return;
|
|
63
|
+
|
|
64
|
+
timer = setTimeoutFn(() => {
|
|
65
|
+
void tick();
|
|
66
|
+
}, dependencies.intervalMs);
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
const tick = async (): Promise<void> => {
|
|
70
|
+
await trigger();
|
|
71
|
+
scheduleNext();
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
const trigger = (): Promise<void> => {
|
|
75
|
+
// Stop is terminal: nothing, including an explicit trigger or an already
|
|
76
|
+
// queued timer callback, may start work after `stop()`.
|
|
77
|
+
if (stopped) return Promise.resolve();
|
|
78
|
+
|
|
79
|
+
if (inFlight) return inFlight;
|
|
80
|
+
|
|
81
|
+
// An `async` wrapper converts a synchronous `runOnce` throw into a
|
|
82
|
+
// rejected promise, and `.finally` always settles on a microtask, so the
|
|
83
|
+
// assignment below can never race the clearing callback and leave
|
|
84
|
+
// `inFlight` stuck on an already-settled promise.
|
|
85
|
+
const run = async (): Promise<void> => {
|
|
86
|
+
try {
|
|
87
|
+
await dependencies.runOnce();
|
|
88
|
+
} catch {
|
|
89
|
+
safeLogMaintenance(
|
|
90
|
+
dependencies.logger,
|
|
91
|
+
fixedFailureSummary(dependencies.intervalMs)
|
|
92
|
+
);
|
|
93
|
+
}
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
const pass = run().finally(() => {
|
|
97
|
+
if (inFlight === pass) inFlight = null;
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
inFlight = pass;
|
|
101
|
+
|
|
102
|
+
return pass;
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
return {
|
|
106
|
+
start: () => {
|
|
107
|
+
if (started || stopped) return;
|
|
108
|
+
|
|
109
|
+
started = true;
|
|
110
|
+
scheduleNext();
|
|
111
|
+
},
|
|
112
|
+
stop: async () => {
|
|
113
|
+
stopped = true;
|
|
114
|
+
started = false;
|
|
115
|
+
|
|
116
|
+
if (timer !== null) {
|
|
117
|
+
clearTimeoutFn(timer);
|
|
118
|
+
timer = null;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
if (inFlight) await inFlight;
|
|
122
|
+
},
|
|
123
|
+
trigger,
|
|
124
|
+
isRunning: () => inFlight !== null,
|
|
125
|
+
};
|
|
126
|
+
};
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import type { CleanupRunSummary, ShareRecoveryRunSummary } from '../share-link-coordinator';
|
|
2
|
+
import type {
|
|
3
|
+
ShareLinkMaintenanceLogEvent,
|
|
4
|
+
ShareLinkMaintenanceLogger,
|
|
5
|
+
ShareLinkMaintenanceRunSummary,
|
|
6
|
+
} from './types';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Fixed, allowlisted aggregate counters. This is the ONLY surface a maintenance
|
|
10
|
+
* log may contain: no object ref, share id, owner id, namespace value, title,
|
|
11
|
+
* note, URL, fragment, key, ciphertext, recovery blob or exception object.
|
|
12
|
+
*/
|
|
13
|
+
const recoveryCounts = (recovery: ShareRecoveryRunSummary | null): Record<string, number> => ({
|
|
14
|
+
recoveryDiscovered: recovery?.discovered ?? 0,
|
|
15
|
+
recoveryClaimed: recovery?.claimed ?? 0,
|
|
16
|
+
recoveryFinalized: recovery?.finalized ?? 0,
|
|
17
|
+
recoveryAbandoned: recovery?.abandoned ?? 0,
|
|
18
|
+
recoveryDeferred: recovery?.deferred ?? 0,
|
|
19
|
+
recoveryClaimLost: recovery?.claimLost ?? 0,
|
|
20
|
+
recoveryAnomalies: recovery?.anomalies ?? 0,
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
const cleanupCounts = (cleanup: CleanupRunSummary | null): Record<string, number> => ({
|
|
24
|
+
cleanupClaimed: cleanup?.claimed ?? 0,
|
|
25
|
+
cleanupCompleted: cleanup?.completed ?? 0,
|
|
26
|
+
cleanupRetried: cleanup?.retried ?? 0,
|
|
27
|
+
cleanupClaimLost: cleanup?.claimLost ?? 0,
|
|
28
|
+
cleanupSkipped: cleanup?.skipped ?? 0,
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
export const toMaintenanceLogEvent = (
|
|
32
|
+
summary: ShareLinkMaintenanceRunSummary
|
|
33
|
+
): ShareLinkMaintenanceLogEvent => ({
|
|
34
|
+
event: 'share_link_maintenance',
|
|
35
|
+
status: summary.status,
|
|
36
|
+
durationMs: Math.max(0, Math.trunc(summary.durationMs)),
|
|
37
|
+
budgetMs: Math.max(0, Math.trunc(summary.budgetMs)),
|
|
38
|
+
counts: {
|
|
39
|
+
...recoveryCounts(summary.recovery),
|
|
40
|
+
...cleanupCounts(summary.cleanup),
|
|
41
|
+
receiptsPruned: summary.receiptPrune?.pruned ?? 0,
|
|
42
|
+
...(summary.attachmentStages
|
|
43
|
+
? { attachmentStagesQueued: summary.attachmentStages.queued }
|
|
44
|
+
: {}),
|
|
45
|
+
},
|
|
46
|
+
categories: {
|
|
47
|
+
...(summary.recovery?.categories ?? {}),
|
|
48
|
+
...(summary.cleanup?.categories ?? {}),
|
|
49
|
+
...summary.categories,
|
|
50
|
+
},
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Emit one allowlisted event. A throwing logger is swallowed: telemetry must
|
|
55
|
+
* never corrupt lifecycle state or turn a completed pass into a failure.
|
|
56
|
+
*/
|
|
57
|
+
export const safeLogMaintenance = (
|
|
58
|
+
logger: ShareLinkMaintenanceLogger,
|
|
59
|
+
summary: ShareLinkMaintenanceRunSummary
|
|
60
|
+
): void => {
|
|
61
|
+
try {
|
|
62
|
+
const event = toMaintenanceLogEvent(summary);
|
|
63
|
+
if (summary.status === 'failed') logger.error(event);
|
|
64
|
+
else logger.info(event);
|
|
65
|
+
} catch {
|
|
66
|
+
// Intentionally ignored: lifecycle state is authoritative.
|
|
67
|
+
}
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
export const createConsoleMaintenanceLogger = (): ShareLinkMaintenanceLogger => ({
|
|
71
|
+
info: event => console.log('[share-link-maintenance]', event),
|
|
72
|
+
error: event => console.error('[share-link-maintenance]', event),
|
|
73
|
+
});
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import type { ShareRecoveryRunSummary, CleanupRunSummary } from '../share-link-coordinator';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* LC-2187 share-link maintenance (scheduling + cleanup integration).
|
|
5
|
+
*
|
|
6
|
+
* This module owns the automatically configured, namespace-scoped maintenance pass. It
|
|
7
|
+
* is deliberately separate from the coordinator barrel so unit tests never load
|
|
8
|
+
* the Neo4j `@instance` or the LearnCard signing graph.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
/** A fully validated, enabled maintenance configuration. */
|
|
12
|
+
export type ShareLinkMaintenanceConfig = {
|
|
13
|
+
namespace: string;
|
|
14
|
+
origin: string;
|
|
15
|
+
audience: string;
|
|
16
|
+
allowInsecureLoopback: boolean;
|
|
17
|
+
claimant: string;
|
|
18
|
+
recoveryLimit: number;
|
|
19
|
+
cleanupLimit: number;
|
|
20
|
+
receiptPruneLimit: number;
|
|
21
|
+
leaseMs: number;
|
|
22
|
+
claimMs: number;
|
|
23
|
+
budgetMs: number;
|
|
24
|
+
intervalMs: number;
|
|
25
|
+
requestTimeoutMs: number;
|
|
26
|
+
finalizeReserveMs: number;
|
|
27
|
+
graphTimeoutMs: number;
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Fixed, non-sensitive configuration outcome. `invalid` never carries the bad
|
|
32
|
+
* value or an exception, so it is safe to log verbatim.
|
|
33
|
+
*/
|
|
34
|
+
export type ShareLinkMaintenanceConfigResolution =
|
|
35
|
+
| { status: 'enabled'; config: ShareLinkMaintenanceConfig }
|
|
36
|
+
| { status: 'disabled' }
|
|
37
|
+
| { status: 'invalid'; category: 'share_link_maintenance_configuration_invalid' };
|
|
38
|
+
|
|
39
|
+
/** Fixed pass status. Never carries request/record data. */
|
|
40
|
+
export type ShareLinkMaintenanceStatus =
|
|
41
|
+
| 'disabled'
|
|
42
|
+
| 'invalid_configuration'
|
|
43
|
+
| 'initialization_failed'
|
|
44
|
+
| 'completed'
|
|
45
|
+
| 'deadline_exhausted'
|
|
46
|
+
| 'failed';
|
|
47
|
+
|
|
48
|
+
/** Fixed failure categories allowed in a maintenance summary/log. */
|
|
49
|
+
export type ShareLinkMaintenanceFailureCategory =
|
|
50
|
+
| 'share_link_maintenance_pass_failed'
|
|
51
|
+
| 'share_link_maintenance_recovery_failed'
|
|
52
|
+
| 'share_link_maintenance_cleanup_failed'
|
|
53
|
+
| 'share_link_maintenance_attachment_stage_failed'
|
|
54
|
+
| 'share_link_maintenance_receipt_prune_failed'
|
|
55
|
+
| 'share_link_maintenance_initialization_failed'
|
|
56
|
+
| 'share_link_maintenance_configuration_invalid';
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Aggregate-only result of one maintenance invocation. It contains counts,
|
|
60
|
+
* durations and fixed categories; it never contains object/share/owner ids,
|
|
61
|
+
* URLs, keys, ciphertext, recovery, titles, notes or exception objects.
|
|
62
|
+
*/
|
|
63
|
+
export type ShareLinkMaintenanceRunSummary = {
|
|
64
|
+
status: ShareLinkMaintenanceStatus;
|
|
65
|
+
durationMs: number;
|
|
66
|
+
budgetMs: number;
|
|
67
|
+
recovery: ShareRecoveryRunSummary | null;
|
|
68
|
+
cleanup: CleanupRunSummary | null;
|
|
69
|
+
/** Namespace-scoped expired/consumed receipt nodes deleted this pass. */
|
|
70
|
+
receiptPrune: { pruned: number } | null;
|
|
71
|
+
attachmentStages?: { queued: number } | null;
|
|
72
|
+
categories: Partial<Record<ShareLinkMaintenanceFailureCategory, number>>;
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Allowlisted log event shape. The logger is called with this object only; a
|
|
77
|
+
* throwing logger must never corrupt lifecycle state.
|
|
78
|
+
*/
|
|
79
|
+
export type ShareLinkMaintenanceLogEvent = {
|
|
80
|
+
event: 'share_link_maintenance';
|
|
81
|
+
status: ShareLinkMaintenanceStatus;
|
|
82
|
+
durationMs: number;
|
|
83
|
+
budgetMs: number;
|
|
84
|
+
counts: Record<string, number>;
|
|
85
|
+
categories: Record<string, number>;
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
export type ShareLinkMaintenanceLogger = {
|
|
89
|
+
info: (event: ShareLinkMaintenanceLogEvent) => void;
|
|
90
|
+
error: (event: ShareLinkMaintenanceLogEvent) => void;
|
|
91
|
+
};
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import {
|
|
2
|
+
hasShareLinkServiceTransportConfig,
|
|
3
|
+
resolveShareLinkServiceTransportConfig,
|
|
4
|
+
} from '../share-link-maintenance/config';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Available automatically once trusted service configuration is complete.
|
|
8
|
+
* Namespace, origin and audience come only from server configuration. Missing
|
|
9
|
+
* wiring is inactive; partial or invalid wiring fails closed.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
export const SHARE_LINK_OWNER_API_ENV = {
|
|
13
|
+
NAMESPACE: 'SHARE_LINK_OWNER_API_NAMESPACE',
|
|
14
|
+
} as const;
|
|
15
|
+
|
|
16
|
+
export type ShareLinkOwnerApiConfigResolution =
|
|
17
|
+
| { status: 'disabled' }
|
|
18
|
+
| { status: 'invalid' }
|
|
19
|
+
| {
|
|
20
|
+
status: 'enabled';
|
|
21
|
+
namespace: string;
|
|
22
|
+
origin: string;
|
|
23
|
+
audience: string;
|
|
24
|
+
allowInsecureLoopback: boolean;
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
export const resolveShareLinkOwnerApiConfig = (raw: unknown): ShareLinkOwnerApiConfigResolution => {
|
|
28
|
+
if (typeof raw !== 'object' || raw === null || Array.isArray(raw)) return { status: 'invalid' };
|
|
29
|
+
|
|
30
|
+
const source = raw as Record<string, unknown>;
|
|
31
|
+
if (!hasShareLinkServiceTransportConfig(source)) return { status: 'disabled' };
|
|
32
|
+
|
|
33
|
+
const transport = resolveShareLinkServiceTransportConfig(source);
|
|
34
|
+
if (transport.status !== 'enabled') return { status: 'invalid' };
|
|
35
|
+
|
|
36
|
+
const ownerNamespace = source[SHARE_LINK_OWNER_API_ENV.NAMESPACE];
|
|
37
|
+
if (ownerNamespace !== undefined && ownerNamespace !== transport.config.namespace) {
|
|
38
|
+
return { status: 'invalid' };
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
return {
|
|
42
|
+
status: 'enabled',
|
|
43
|
+
namespace: transport.config.namespace,
|
|
44
|
+
origin: transport.config.origin,
|
|
45
|
+
audience: transport.config.audience,
|
|
46
|
+
allowInsecureLoopback: transport.config.allowInsecureLoopback,
|
|
47
|
+
};
|
|
48
|
+
};
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Bounded, sanitized diagnostics for share-link service composition.
|
|
3
|
+
*
|
|
4
|
+
* The owner and public APIs fail closed for two states that are otherwise
|
|
5
|
+
* indistinguishable to an operator:
|
|
6
|
+
*
|
|
7
|
+
* - intentionally absent configuration (`status: 'disabled'`) is silent and
|
|
8
|
+
* inert; it is a supported deployment;
|
|
9
|
+
* - malformed/partial service wiring (`status: 'invalid'`) or a failed
|
|
10
|
+
* dependency build emits one fixed category so a misconfiguration is
|
|
11
|
+
* distinguishable from an unconfigured deployment.
|
|
12
|
+
*
|
|
13
|
+
* The reporter is bounded per process: each category is emitted at most once,
|
|
14
|
+
* so a flood of requests against a misconfigured deployment cannot flood logs.
|
|
15
|
+
* The event carries only a fixed allowlisted category string: never an
|
|
16
|
+
* environment value, URL, request body, DID key, title, note or ciphertext.
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
export const SHARE_LINK_COMPOSITION_DIAGNOSTIC = {
|
|
20
|
+
OWNER_CONFIGURATION_INVALID: 'share_link_owner_configuration_invalid',
|
|
21
|
+
OWNER_INITIALIZATION_FAILED: 'share_link_owner_initialization_failed',
|
|
22
|
+
PUBLIC_CONFIGURATION_INVALID: 'share_link_public_configuration_invalid',
|
|
23
|
+
PUBLIC_INITIALIZATION_FAILED: 'share_link_public_initialization_failed',
|
|
24
|
+
} as const;
|
|
25
|
+
|
|
26
|
+
export type ShareLinkCompositionDiagnosticCategory =
|
|
27
|
+
(typeof SHARE_LINK_COMPOSITION_DIAGNOSTIC)[keyof typeof SHARE_LINK_COMPOSITION_DIAGNOSTIC];
|
|
28
|
+
|
|
29
|
+
export type ShareLinkDiagnosticSink = (category: ShareLinkCompositionDiagnosticCategory) => void;
|
|
30
|
+
|
|
31
|
+
/** Aggregate-only sink matching the existing route reporting convention. */
|
|
32
|
+
export const defaultShareLinkDiagnosticSink: ShareLinkDiagnosticSink = category => {
|
|
33
|
+
console.warn('share_link_composition_diagnostic', { category });
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
/** Wrap a sink so each category is emitted at most once per process. */
|
|
37
|
+
export const createBoundedShareLinkDiagnosticReporter = (
|
|
38
|
+
sink: ShareLinkDiagnosticSink = defaultShareLinkDiagnosticSink
|
|
39
|
+
): ShareLinkDiagnosticSink => {
|
|
40
|
+
const reported = new Set<ShareLinkCompositionDiagnosticCategory>();
|
|
41
|
+
|
|
42
|
+
return category => {
|
|
43
|
+
if (reported.has(category)) return;
|
|
44
|
+
reported.add(category);
|
|
45
|
+
sink(category);
|
|
46
|
+
};
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
type ShareLinkConfigStatus = { status: 'disabled' } | { status: 'invalid' } | { status: 'enabled' };
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Compose the lazy getter a share-link router uses around a pure config
|
|
53
|
+
* resolution and a retryable initializer.
|
|
54
|
+
*
|
|
55
|
+
* Disabled configuration returns `null` silently. Invalid configuration reports
|
|
56
|
+
* a single sanitized diagnostic and returns `null`, preserving the anonymous
|
|
57
|
+
* not_found behavior. An enabled but failing build reports a single sanitized
|
|
58
|
+
* diagnostic and rethrows so the caller maps it to a generic error while the
|
|
59
|
+
* retryable initializer clears the failed attempt cache.
|
|
60
|
+
*/
|
|
61
|
+
export const createShareLinkDependencyResolver = <TDependencies>(options: {
|
|
62
|
+
readonly resolveConfig: () => ShareLinkConfigStatus;
|
|
63
|
+
readonly initializeDependencies: () => Promise<TDependencies>;
|
|
64
|
+
readonly reportDiagnostic: ShareLinkDiagnosticSink;
|
|
65
|
+
readonly configurationInvalidCategory: ShareLinkCompositionDiagnosticCategory;
|
|
66
|
+
readonly initializationFailedCategory: ShareLinkCompositionDiagnosticCategory;
|
|
67
|
+
}): (() => Promise<TDependencies | null>) => {
|
|
68
|
+
return async () => {
|
|
69
|
+
const config = options.resolveConfig();
|
|
70
|
+
|
|
71
|
+
// Intentionally absent configuration: inert and silent.
|
|
72
|
+
if (config.status === 'disabled') return null;
|
|
73
|
+
|
|
74
|
+
// Malformed/partial wiring: one bounded diagnostic, still fail closed.
|
|
75
|
+
if (config.status === 'invalid') {
|
|
76
|
+
options.reportDiagnostic(options.configurationInvalidCategory);
|
|
77
|
+
return null;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
try {
|
|
81
|
+
return await options.initializeDependencies();
|
|
82
|
+
} catch (error) {
|
|
83
|
+
// The initializer has already cleared its failed attempt; report the
|
|
84
|
+
// category once and let the caller surface a sanitized error.
|
|
85
|
+
options.reportDiagnostic(options.initializationFailedCategory);
|
|
86
|
+
throw error;
|
|
87
|
+
}
|
|
88
|
+
};
|
|
89
|
+
};
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Retryable lazy initialization for the owner share-link runtime.
|
|
3
|
+
*
|
|
4
|
+
* Successful and in-flight builds are shared across concurrent callers. A
|
|
5
|
+
* *failed* build must never be cached: a transient constraint/graph/signer
|
|
6
|
+
* failure would otherwise poison the owner API until the process restarts
|
|
7
|
+
* (the same defect previously corrected for the C7 setup cache). Only the
|
|
8
|
+
* failed attempt is cleared, so a later request retries while any other
|
|
9
|
+
* in-flight attempt keeps its own shared promise.
|
|
10
|
+
*/
|
|
11
|
+
export const createRetryableLazyInitializer = <T>(build: () => Promise<T>): (() => Promise<T>) => {
|
|
12
|
+
let cached: Promise<T> | null = null;
|
|
13
|
+
|
|
14
|
+
return () => {
|
|
15
|
+
if (cached) return cached;
|
|
16
|
+
|
|
17
|
+
const attempt = build();
|
|
18
|
+
cached = attempt;
|
|
19
|
+
|
|
20
|
+
// Preserve the rejection for the awaiting caller, but detach it from the
|
|
21
|
+
// cache so the next call rebuilds. `catch` also prevents an unhandled
|
|
22
|
+
// rejection when nobody is awaiting this particular attempt.
|
|
23
|
+
attempt.catch(() => {
|
|
24
|
+
if (cached === attempt) cached = null;
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
return attempt;
|
|
28
|
+
};
|
|
29
|
+
};
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import type { ShareLink } from '@learncard/types';
|
|
2
|
+
|
|
3
|
+
import type { ShareLinkRecord } from '../models/ShareLink';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Explicit owner projection of a `ShareLinkRecord`.
|
|
7
|
+
*
|
|
8
|
+
* This is a deliberate field-by-field mapping, never a spread: the raw record
|
|
9
|
+
* carries internal object refs, operation ids, leases, hashes, byte counts and
|
|
10
|
+
* ciphertext bindings that must never reach a caller. `contentUrl` is
|
|
11
|
+
* intentionally omitted here — the public resolve/content routes own the guarded
|
|
12
|
+
* relative URL, and a LearnCloud object URL (or a fabricated decryption key)
|
|
13
|
+
* must never be exposed.
|
|
14
|
+
*
|
|
15
|
+
* `viewCountingEnabled` is the FRESHLY re-resolved authoritative eligibility for
|
|
16
|
+
* the owner (never the stale `minorPolicyViewCountingEnabled` snapshot alone and
|
|
17
|
+
* never a caller field). When it is not exactly `true`, `viewCount` is omitted
|
|
18
|
+
* and `lastViewedAt` is nulled so a formerly eligible owner's historical counts
|
|
19
|
+
* stop leaking through get/mutation/status/retry.
|
|
20
|
+
*/
|
|
21
|
+
export const toOwnerShareLink = (
|
|
22
|
+
record: ShareLinkRecord,
|
|
23
|
+
viewCountingEnabled?: boolean
|
|
24
|
+
): ShareLink => {
|
|
25
|
+
const countingEnabled = viewCountingEnabled === true;
|
|
26
|
+
|
|
27
|
+
return {
|
|
28
|
+
id: record.id,
|
|
29
|
+
title: record.title,
|
|
30
|
+
...(record.note === null ? {} : { note: record.note }),
|
|
31
|
+
selectedCount: record.selectedCount,
|
|
32
|
+
version: record.version,
|
|
33
|
+
contentVersion: record.contentVersion,
|
|
34
|
+
attachmentId: record.attachmentId ?? null,
|
|
35
|
+
attachmentChunkCount: record.attachmentChunkCount ?? null,
|
|
36
|
+
status: record.status,
|
|
37
|
+
contentState: record.contentState,
|
|
38
|
+
createdAt: record.createdAt,
|
|
39
|
+
updatedAt: record.updatedAt,
|
|
40
|
+
expiresAt: record.expiresAt,
|
|
41
|
+
stoppedAt: record.stoppedAt,
|
|
42
|
+
...(countingEnabled
|
|
43
|
+
? { lastViewedAt: record.lastViewedAt, viewCount: record.viewCount }
|
|
44
|
+
: { lastViewedAt: null }),
|
|
45
|
+
passcodeProtected: record.passcodeHash != null,
|
|
46
|
+
notifyOnView: record.notifyOnView === true,
|
|
47
|
+
minorPolicy: {
|
|
48
|
+
isMinor: record.minorPolicyIsMinor,
|
|
49
|
+
policyResolved: record.minorPolicyResolved,
|
|
50
|
+
defaultExpiryDays: record.minorPolicyDefaultExpiryDays === 365 ? 365 : 30,
|
|
51
|
+
viewCountingEnabled: record.minorPolicyViewCountingEnabled,
|
|
52
|
+
},
|
|
53
|
+
};
|
|
54
|
+
};
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import cache from '@cache';
|
|
2
|
+
import { environment } from '@environment';
|
|
3
|
+
|
|
4
|
+
// The short shared window limits distributed guessing while bounding the time
|
|
5
|
+
// an attacker can deny access to a legitimate recipient. Per-source limits
|
|
6
|
+
// preserve capacity for other recipients until the shared budget is exhausted.
|
|
7
|
+
const WINDOW_SECONDS = 60;
|
|
8
|
+
// A successful recipient view checks the passcode at resolve and content, so
|
|
9
|
+
// it spends two attempts. Keep that in mind before lowering these budgets.
|
|
10
|
+
const SHARE_ATTEMPT_LIMIT = 24;
|
|
11
|
+
const SOURCE_ATTEMPT_LIMIT = 6;
|
|
12
|
+
|
|
13
|
+
const keys = (namespace: string, shareId: string, sourceIp?: string): [string, string] => [
|
|
14
|
+
`share-link-passcode-attempt:${namespace}:${shareId}`,
|
|
15
|
+
`share-link-passcode-attempt-source:${namespace}:${shareId}:${sourceIp ?? 'unknown'}`,
|
|
16
|
+
];
|
|
17
|
+
|
|
18
|
+
export const reserveSharePasscodeAttempt = async (
|
|
19
|
+
namespace: string,
|
|
20
|
+
shareId: string,
|
|
21
|
+
sourceIp?: string
|
|
22
|
+
): Promise<boolean> => {
|
|
23
|
+
// Process-local fallback counters cannot stop rotated-IP guesses across
|
|
24
|
+
// production workers. Protected access requires the shared Redis counter.
|
|
25
|
+
if (environment.NODE_ENV === 'production' && !cache.redis) return false;
|
|
26
|
+
const [shareKey, sourceKey] = keys(namespace, shareId, sourceIp);
|
|
27
|
+
// Reserve before Argon2. Redis INCR is atomic across Lambda instances, so
|
|
28
|
+
// simultaneous guesses cannot all read a stale budget. Count successful
|
|
29
|
+
// attempts too; no cross-key refund race and no stored submitted value.
|
|
30
|
+
const sourceCount = await cache.incr(sourceKey, WINDOW_SECONDS);
|
|
31
|
+
if (sourceCount === undefined || sourceCount > SOURCE_ATTEMPT_LIMIT) return false;
|
|
32
|
+
// Rejected requests from this source never consume another recipient's
|
|
33
|
+
// share-wide budget. Both reservations still precede Argon2.
|
|
34
|
+
const shareCount = await cache.incr(shareKey, WINDOW_SECONDS);
|
|
35
|
+
return shareCount !== undefined && shareCount <= SHARE_ATTEMPT_LIMIT;
|
|
36
|
+
};
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { randomBytes } from 'node:crypto';
|
|
2
|
+
|
|
3
|
+
import { argon2id, argon2Verify } from 'hash-wasm';
|
|
4
|
+
|
|
5
|
+
const ARGON2_MEMORY_KIB = 19_456;
|
|
6
|
+
const ARGON2_ITERATIONS = 2;
|
|
7
|
+
const ARGON2_PARALLELISM = 1;
|
|
8
|
+
const ARGON2_HASH_BYTES = 32;
|
|
9
|
+
const MAX_CONCURRENT_VERIFICATIONS = 4;
|
|
10
|
+
let activeVerifications = 0;
|
|
11
|
+
|
|
12
|
+
export class SharePasscodeCapacityError extends Error {
|
|
13
|
+
constructor() {
|
|
14
|
+
super('share passcode verification capacity exhausted');
|
|
15
|
+
this.name = 'SharePasscodeCapacityError';
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/** Hash a share passcode as an Argon2id PHC string with a fresh 128-bit salt. */
|
|
20
|
+
export const hashSharePasscode = async (passcode: string): Promise<string> =>
|
|
21
|
+
argon2id({
|
|
22
|
+
password: passcode,
|
|
23
|
+
salt: randomBytes(16),
|
|
24
|
+
iterations: ARGON2_ITERATIONS,
|
|
25
|
+
memorySize: ARGON2_MEMORY_KIB,
|
|
26
|
+
parallelism: ARGON2_PARALLELISM,
|
|
27
|
+
hashLength: ARGON2_HASH_BYTES,
|
|
28
|
+
outputType: 'encoded',
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
/** Verify without ever logging, returning, or persisting the plaintext passcode. */
|
|
32
|
+
export const verifySharePasscode = async (
|
|
33
|
+
passcodeHash: string,
|
|
34
|
+
passcode: string
|
|
35
|
+
): Promise<boolean> => {
|
|
36
|
+
// Local defense for long-lived workers; Lambda's cross-instance budget is
|
|
37
|
+
// reserved in Redis before this function runs. Capacity is not a wrong code.
|
|
38
|
+
if (activeVerifications >= MAX_CONCURRENT_VERIFICATIONS) throw new SharePasscodeCapacityError();
|
|
39
|
+
activeVerifications += 1;
|
|
40
|
+
try {
|
|
41
|
+
return await argon2Verify({ password: passcode, hash: passcodeHash });
|
|
42
|
+
} catch {
|
|
43
|
+
return false;
|
|
44
|
+
} finally {
|
|
45
|
+
activeVerifications -= 1;
|
|
46
|
+
}
|
|
47
|
+
};
|