@learncard/network-brain-service 3.18.0 → 3.19.1
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 +38 -2
- package/CHANGELOG.md +118 -0
- package/README.md +42 -0
- package/compose.inbox.yml +7 -0
- package/dist/accesslayer/auth-grant/read.d.ts +1 -0
- package/dist/accesslayer/auth-grant/read.d.ts.map +1 -1
- package/dist/accesslayer/consentflowcontract/relationships/create.d.ts.map +1 -1
- package/dist/accesslayer/consentflowcontract/relationships/update.d.ts.map +1 -1
- package/dist/accesslayer/credential-activity/create.d.ts.map +1 -1
- package/dist/accesslayer/credential-refresh/email-delivery.d.ts +18 -0
- package/dist/accesslayer/credential-refresh/email-delivery.d.ts.map +1 -0
- package/dist/accesslayer/credential-refresh/index.d.ts +1 -0
- package/dist/accesslayer/credential-refresh/index.d.ts.map +1 -1
- package/dist/accesslayer/credential-refresh/read.d.ts.map +1 -1
- package/dist/accesslayer/credential-refresh/update.d.ts.map +1 -1
- package/dist/accesslayer/inbox-batch/store.d.ts +33 -0
- package/dist/accesslayer/inbox-batch/store.d.ts.map +1 -0
- package/dist/accesslayer/inbox-credential/create.d.ts +5 -0
- package/dist/accesslayer/inbox-credential/create.d.ts.map +1 -1
- package/dist/accesslayer/inbox-credential/refresh.d.ts +71 -0
- package/dist/accesslayer/inbox-credential/refresh.d.ts.map +1 -0
- package/dist/accesslayer/inbox-credential/update.d.ts +1 -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/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 +56 -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 +370 -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/atomic-counter.test-helpers.d.ts +3 -0
- package/dist/cache/atomic-counter.test-helpers.d.ts.map +1 -0
- package/dist/cache/atomic-counter.test.d.ts +2 -0
- package/dist/cache/atomic-counter.test.d.ts.map +1 -0
- package/dist/cache/index.d.ts +1 -1
- package/dist/cache/index.d.ts.map +1 -1
- package/dist/config/environment.d.ts +55 -0
- package/dist/config/environment.d.ts.map +1 -1
- package/dist/constants/auth-grant.d.ts +2 -0
- package/dist/constants/auth-grant.d.ts.map +1 -1
- package/dist/credential-refresh.d.ts.map +1 -1
- package/dist/helpers/activity.helpers.d.ts +4 -0
- package/dist/helpers/activity.helpers.d.ts.map +1 -1
- package/dist/helpers/boost.helpers.d.ts.map +1 -1
- package/dist/helpers/contact-method.helpers.d.ts.map +1 -1
- package/dist/helpers/contact-method.helpers.test.d.ts +2 -0
- package/dist/helpers/contact-method.helpers.test.d.ts.map +1 -0
- package/dist/helpers/credential-refresh-email-content.helpers.d.ts +44 -0
- package/dist/helpers/credential-refresh-email-content.helpers.d.ts.map +1 -0
- package/dist/helpers/credential-refresh-email-content.helpers.test.d.ts +2 -0
- package/dist/helpers/credential-refresh-email-content.helpers.test.d.ts.map +1 -0
- package/dist/helpers/credential-refresh-email.helpers.d.ts +17 -0
- package/dist/helpers/credential-refresh-email.helpers.d.ts.map +1 -0
- package/dist/helpers/credential-refresh-proof.helpers.d.ts +4 -0
- package/dist/helpers/credential-refresh-proof.helpers.d.ts.map +1 -0
- package/dist/helpers/credential-refresh-proof.helpers.test.d.ts +2 -0
- package/dist/helpers/credential-refresh-proof.helpers.test.d.ts.map +1 -0
- package/dist/helpers/credential-refresh.helpers.d.ts +45 -2
- package/dist/helpers/credential-refresh.helpers.d.ts.map +1 -1
- package/dist/helpers/credential.helpers.d.ts +2 -1
- package/dist/helpers/credential.helpers.d.ts.map +1 -1
- 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.map +1 -1
- package/dist/helpers/inbox-batch-http.helpers.d.ts +15 -0
- package/dist/helpers/inbox-batch-http.helpers.d.ts.map +1 -0
- package/dist/helpers/inbox-batch-http.helpers.test.d.ts +2 -0
- package/dist/helpers/inbox-batch-http.helpers.test.d.ts.map +1 -0
- package/dist/helpers/inbox-batch-status.helpers.d.ts +3 -0
- package/dist/helpers/inbox-batch-status.helpers.d.ts.map +1 -0
- package/dist/helpers/inbox-batch.helpers.d.ts +10 -0
- package/dist/helpers/inbox-batch.helpers.d.ts.map +1 -0
- package/dist/helpers/inbox-batch.helpers.test.d.ts +2 -0
- package/dist/helpers/inbox-batch.helpers.test.d.ts.map +1 -0
- package/dist/helpers/inbox-issuance-error.helpers.d.ts +13 -0
- package/dist/helpers/inbox-issuance-error.helpers.d.ts.map +1 -0
- package/dist/helpers/inbox-queue.helpers.d.ts +11 -0
- package/dist/helpers/inbox-queue.helpers.d.ts.map +1 -0
- package/dist/helpers/inbox-queue.helpers.test.d.ts +2 -0
- package/dist/helpers/inbox-queue.helpers.test.d.ts.map +1 -0
- package/dist/helpers/inbox-refresh.helpers.d.ts +186 -0
- package/dist/helpers/inbox-refresh.helpers.d.ts.map +1 -0
- package/dist/helpers/inbox.helpers.d.ts +9 -1
- package/dist/helpers/inbox.helpers.d.ts.map +1 -1
- package/dist/helpers/notificationMessages.d.ts +1 -1
- package/dist/helpers/notificationMessages.d.ts.map +1 -1
- 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 +1 -0
- package/dist/helpers/profile.helpers.d.ts.map +1 -1
- package/dist/helpers/rateLimit.helpers.d.ts.map +1 -1
- package/dist/helpers/refresh-send-intent.helpers.d.ts +51 -0
- package/dist/helpers/refresh-send-intent.helpers.d.ts.map +1 -0
- 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-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 +19 -0
- package/dist/helpers/share-link-maintenance/runner.d.ts.map +1 -0
- package/dist/helpers/share-link-maintenance/runtime.d.ts +36 -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 +53 -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 +13 -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 +16 -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/shim.d.ts +1 -1
- package/dist/helpers/shim.d.ts.map +1 -1
- package/dist/inbox-queue-local.d.ts +2 -0
- package/dist/inbox-queue-local.d.ts.map +1 -0
- package/dist/index.js +2 -2
- package/dist/models/AuthGrant.d.ts.map +1 -1
- package/dist/models/CredentialActivity.d.ts +1 -0
- package/dist/models/CredentialActivity.d.ts.map +1 -1
- package/dist/models/CredentialRefresh.d.ts +5 -2
- package/dist/models/CredentialRefresh.d.ts.map +1 -1
- package/dist/models/InboxCredential.d.ts +1 -0
- package/dist/models/InboxCredential.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 +44 -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/credential-refresh-constraints.d.ts.map +1 -1
- package/dist/models/index.d.ts +1 -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.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/act-as.test.d.ts +2 -0
- package/dist/routes/act-as.test.d.ts.map +1 -0
- package/dist/routes/activity.d.ts +3 -0
- package/dist/routes/activity.d.ts.map +1 -1
- package/dist/routes/app-store.d.ts.map +1 -1
- package/dist/routes/auth-grants.d.ts +10 -0
- package/dist/routes/auth-grants.d.ts.map +1 -1
- package/dist/routes/boosts.d.ts +494 -0
- package/dist/routes/boosts.d.ts.map +1 -1
- package/dist/routes/contracts.d.ts +2 -2
- package/dist/routes/contracts.d.ts.map +1 -1
- package/dist/routes/credential-refreshes.d.ts.map +1 -1
- package/dist/routes/credentials.d.ts +1 -1
- package/dist/routes/credentials.d.ts.map +1 -1
- package/dist/routes/inbox.d.ts +953 -11
- package/dist/routes/inbox.d.ts.map +1 -1
- package/dist/routes/index.d.ts +154 -85
- 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 +230 -0
- package/dist/routes/public-share-links.d.ts.map +1 -0
- package/dist/routes/share-links.d.ts +844 -0
- package/dist/routes/share-links.d.ts.map +1 -0
- package/dist/routes/workflows.d.ts.map +1 -1
- package/dist/services/delivery/adapters/postmark.adapter.d.ts.map +1 -1
- package/dist/services/delivery/adapters/postmark.adapter.test.d.ts +2 -0
- package/dist/services/delivery/adapters/postmark.adapter.test.d.ts.map +1 -0
- package/dist/services/registry/registry.factory.d.ts.map +1 -1
- package/dist/types/activity.d.ts +4 -0
- package/dist/types/activity.d.ts.map +1 -1
- package/dist/types/consentflowcontract.d.ts +1 -1
- package/dist/types/credential-refresh.d.ts +10 -1
- package/dist/types/credential-refresh.d.ts.map +1 -1
- package/dist/types/inbox-batch.d.ts +38 -0
- package/dist/types/inbox-batch.d.ts.map +1 -0
- package/inbox-e2e-setup.ts +53 -1
- package/inbox-elasticmq.conf +10 -0
- package/lambda.ts +64 -16
- package/package.json +24 -16
- package/scripts/seed-demo-persona.ts +425 -0
- package/scripts/setup-inbox-queues.ts +60 -0
- package/serverless-local.yml +2 -0
- package/serverless.yml +111 -0
- package/shareLinkMaintenanceLambda.ts +49 -0
- package/src/accesslayer/auth-grant/read.ts +2 -1
- package/src/accesslayer/consentflowcontract/relationships/create.ts +9 -16
- package/src/accesslayer/consentflowcontract/relationships/update.ts +15 -28
- package/src/accesslayer/credential-activity/create.ts +1 -0
- package/src/accesslayer/credential-refresh/email-delivery.ts +116 -0
- package/src/accesslayer/credential-refresh/index.ts +1 -0
- package/src/accesslayer/credential-refresh/read.ts +8 -2
- package/src/accesslayer/credential-refresh/update.ts +4 -0
- package/src/accesslayer/inbox-batch/store.ts +469 -0
- package/src/accesslayer/inbox-credential/create.ts +51 -19
- package/src/accesslayer/inbox-credential/refresh.ts +221 -0
- package/src/accesslayer/inbox-credential/update.ts +1 -1
- 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 +100 -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 +265 -0
- package/src/accesslayer/share-link/helpers.ts +419 -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 +545 -0
- package/src/accesslayer/share-link/reserve.ts +684 -0
- package/src/accesslayer/share-link/revoke.ts +197 -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 +601 -0
- package/src/app.ts +6 -0
- package/src/cache/atomic-counter.test-helpers.ts +27 -0
- package/src/cache/atomic-counter.test.ts +6 -0
- package/src/cache/index.ts +25 -13
- package/src/config/environment.ts +40 -0
- package/src/constants/auth-grant.ts +5 -0
- package/src/credential-refresh.ts +5 -1
- package/src/docker-entry.ts +65 -0
- package/src/helpers/activity.helpers.ts +4 -0
- package/src/helpers/app-counter.helpers.ts +1 -1
- package/src/helpers/boost.helpers.ts +4 -31
- package/src/helpers/contact-method.helpers.test.ts +43 -0
- package/src/helpers/contact-method.helpers.ts +3 -4
- package/src/helpers/credential-refresh-email-content.helpers.test.ts +222 -0
- package/src/helpers/credential-refresh-email-content.helpers.ts +155 -0
- package/src/helpers/credential-refresh-email.helpers.ts +201 -0
- package/src/helpers/credential-refresh-proof.helpers.test.ts +103 -0
- package/src/helpers/credential-refresh-proof.helpers.ts +43 -0
- package/src/helpers/credential-refresh.helpers.ts +375 -99
- package/src/helpers/credential.helpers.ts +4 -1
- package/src/helpers/credentialSubject.helpers.test.ts +114 -0
- package/src/helpers/credentialSubject.helpers.ts +40 -0
- package/src/helpers/finalize-inbox.helpers.ts +29 -14
- package/src/helpers/inbox-batch-http.helpers.test.ts +188 -0
- package/src/helpers/inbox-batch-http.helpers.ts +150 -0
- package/src/helpers/inbox-batch-status.helpers.ts +11 -0
- package/src/helpers/inbox-batch.helpers.test.ts +668 -0
- package/src/helpers/inbox-batch.helpers.ts +374 -0
- package/src/helpers/inbox-issuance-error.helpers.ts +25 -0
- package/src/helpers/inbox-queue.helpers.test.ts +253 -0
- package/src/helpers/inbox-queue.helpers.ts +372 -0
- package/src/helpers/inbox-refresh.helpers.ts +432 -0
- package/src/helpers/inbox.helpers.ts +275 -36
- package/src/helpers/notificationMessages.ts +18 -1
- package/src/helpers/notifications.helpers.ts +4 -2
- package/src/helpers/presentation-metadata.ts +41 -0
- package/src/helpers/presentation.helpers.ts +27 -20
- package/src/helpers/profile.helpers.ts +7 -0
- package/src/helpers/rateLimit.helpers.ts +2 -5
- package/src/helpers/refresh-send-intent.helpers.ts +273 -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-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 +714 -0
- package/src/helpers/share-link-coordinator/index.ts +8 -0
- package/src/helpers/share-link-coordinator/recovery-runner.ts +428 -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 +223 -0
- package/src/helpers/share-link-maintenance/runtime.ts +334 -0
- package/src/helpers/share-link-maintenance/scheduler.ts +126 -0
- package/src/helpers/share-link-maintenance/telemetry.ts +70 -0
- package/src/helpers/share-link-maintenance/types.ts +89 -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 +52 -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 +88 -0
- package/src/helpers/share-link-policy/resolver.ts +108 -0
- package/src/helpers/share-link-policy/types.ts +48 -0
- package/src/helpers/share-link-public/config.ts +41 -0
- package/src/helpers/share-link-view-notification.ts +18 -0
- package/src/helpers/shim.ts +32 -31
- package/src/inbox-queue-local.ts +28 -0
- package/src/models/AuthGrant.ts +1 -0
- package/src/models/CredentialActivity.ts +3 -5
- package/src/models/CredentialRefresh.ts +4 -2
- package/src/models/InboxCredential.ts +2 -0
- package/src/models/Presentation.ts +2 -2
- package/src/models/Profile.ts +3 -2
- package/src/models/ShareLink.ts +130 -0
- package/src/models/ShareViewReceipt.ts +69 -0
- package/src/models/credential-refresh-constraints.test.ts +8 -3
- package/src/models/credential-refresh-constraints.ts +5 -0
- package/src/models/index.ts +2 -0
- package/src/models/share-link-constraints.ts +93 -0
- package/src/openapi.ts +8 -0
- package/src/public-share-link-http.ts +49 -0
- package/src/routes/act-as.test.ts +239 -0
- package/src/routes/app-store.ts +4 -13
- package/src/routes/auth-grants.ts +13 -11
- package/src/routes/boosts.ts +916 -103
- package/src/routes/contracts.ts +2 -11
- package/src/routes/credential-refreshes.test.ts +8 -2
- package/src/routes/credential-refreshes.ts +4 -1
- package/src/routes/credentials.ts +2 -1
- package/src/routes/inbox.ts +95 -49
- package/src/routes/index.ts +216 -46
- package/src/routes/presentations.ts +19 -3
- package/src/routes/profile-manager.ts +10 -2
- package/src/routes/profiles.ts +17 -6
- package/src/routes/public-share-links.ts +814 -0
- package/src/routes/share-links.ts +640 -0
- package/src/routes/skills.ts +1 -1
- package/src/routes/workflows.ts +43 -24
- package/src/services/delivery/adapters/postmark.adapter.test.ts +54 -0
- package/src/services/delivery/adapters/postmark.adapter.ts +16 -1
- package/src/services/registry/registry.factory.ts +2 -1
- package/src/types/activity.ts +2 -0
- package/src/types/credential-refresh.ts +21 -2
- package/src/types/inbox-batch.ts +46 -0
- package/test/atomic-counter.e2e.ts +12 -0
- package/test/auth-grant.spec.ts +21 -21
- package/test/credential-refresh-email.spec.ts +663 -0
- package/test/credential-refresh-publication.spec.ts +2 -0
- package/test/dids.spec.ts +10 -1
- package/test/guardian-gated-routes.spec.ts +13 -0
- package/test/helpers/inbox-e2e-environment.ts +6 -0
- package/test/helpers/share-link-fixtures.ts +94 -0
- package/test/inbox-batch.spec.ts +1338 -0
- package/test/inbox-refresh.spec.ts +534 -0
- package/test/managed-recipient-inbox.spec.ts +130 -0
- package/test/notificationMessages.spec.ts +13 -0
- package/test/openapi.spec.ts +61 -1
- package/test/presentations.spec.ts +54 -6
- package/test/profiles.spec.ts +67 -1
- package/test/refresh-send.spec.ts +1799 -0
- package/test/registry.spec.ts +56 -11
- 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-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 +1268 -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 +1113 -0
- package/test/share-link-owner-policy.unit.spec.ts +210 -0
- package/test/share-link-owner-route.unit.spec.ts +752 -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 +135 -0
- package/test/share-link-presentation-metadata.unit.spec.ts +49 -0
- package/test/share-link-production-policy.unit.spec.ts +77 -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 +800 -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/vitest.config.ts +10 -0
- package/vitest.inbox-e2e.config.ts +5 -1
- package/vitest.integration.config.ts +2 -0
- package/vitest.share-links-neo4j.config.ts +19 -0
- package/vitest.share-links-unit.config.ts +28 -0
|
@@ -0,0 +1,334 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
CleanupRunnerDependencies,
|
|
3
|
+
RecoveryRunnerDependencies,
|
|
4
|
+
} from '../share-link-coordinator';
|
|
5
|
+
import type { PruneShareViewReceiptsInput } from '../../accesslayer/share-link/types';
|
|
6
|
+
import type { ShareContentClient } from '../share-content-client/types';
|
|
7
|
+
import { createMaintenancePassBudget, resolveMaintenanceBudgetMs } from './budget';
|
|
8
|
+
import { resolveShareLinkMaintenanceConfig } from './config';
|
|
9
|
+
import { runShareLinkMaintenancePass } from './runner';
|
|
10
|
+
import { safeLogMaintenance } from './telemetry';
|
|
11
|
+
import type {
|
|
12
|
+
ShareLinkMaintenanceConfigResolution,
|
|
13
|
+
ShareLinkMaintenanceLogger,
|
|
14
|
+
ShareLinkMaintenanceRunSummary,
|
|
15
|
+
} from './types';
|
|
16
|
+
|
|
17
|
+
type MaintenanceClient = Pick<ShareContentClient, 'stat' | 'delete'>;
|
|
18
|
+
|
|
19
|
+
type ResolvedRunnerDependencies = {
|
|
20
|
+
recoveryRepository: RecoveryRunnerDependencies['repository'];
|
|
21
|
+
cleanupRepository: CleanupRunnerDependencies['repository'];
|
|
22
|
+
pruneReceipts: (input: PruneShareViewReceiptsInput) => Promise<number>;
|
|
23
|
+
client: MaintenanceClient;
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
type DependencySetup =
|
|
27
|
+
| { status: 'ready'; dependencies: ResolvedRunnerDependencies }
|
|
28
|
+
| { status: 'failed' }
|
|
29
|
+
| { status: 'expired' };
|
|
30
|
+
|
|
31
|
+
export type ShareLinkMaintenanceRuntime = {
|
|
32
|
+
/** Config outcome fixed at construction; never contains raw values. */
|
|
33
|
+
readonly resolution: ShareLinkMaintenanceConfigResolution;
|
|
34
|
+
runOnce: (options?: {
|
|
35
|
+
/**
|
|
36
|
+
* Live Lambda remaining-time source. A numeric value is accepted as a
|
|
37
|
+
* backward-compatible test seam; production passes the live
|
|
38
|
+
* `getRemainingTimeInMillis` callback.
|
|
39
|
+
*/
|
|
40
|
+
remainingTimeMs?: number | (() => number);
|
|
41
|
+
}) => Promise<ShareLinkMaintenanceRunSummary>;
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
export type CreateShareLinkMaintenanceRuntimeOptions = {
|
|
45
|
+
rawEnvironment?: Record<string, unknown>;
|
|
46
|
+
logger?: ShareLinkMaintenanceLogger;
|
|
47
|
+
now?: () => Date;
|
|
48
|
+
monotonicNow?: () => number;
|
|
49
|
+
/** Test seam: resolve the transport client without dynamic imports. */
|
|
50
|
+
createClient?: (
|
|
51
|
+
config: Extract<ShareLinkMaintenanceConfigResolution, { status: 'enabled' }>['config']
|
|
52
|
+
) => Promise<MaintenanceClient>;
|
|
53
|
+
/** Test seam: resolve the production repositories without loading Neo4j. */
|
|
54
|
+
createRepositories?: (
|
|
55
|
+
client: MaintenanceClient,
|
|
56
|
+
config: Extract<ShareLinkMaintenanceConfigResolution, { status: 'enabled' }>['config']
|
|
57
|
+
) => Promise<{
|
|
58
|
+
recoveryRepository: RecoveryRunnerDependencies['repository'];
|
|
59
|
+
cleanupRepository: CleanupRunnerDependencies['repository'];
|
|
60
|
+
pruneReceipts: ResolvedRunnerDependencies['pruneReceipts'];
|
|
61
|
+
}>;
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
const inertSummary = (
|
|
65
|
+
status: 'disabled' | 'invalid_configuration',
|
|
66
|
+
categories: ShareLinkMaintenanceRunSummary['categories']
|
|
67
|
+
): ShareLinkMaintenanceRunSummary => ({
|
|
68
|
+
status,
|
|
69
|
+
durationMs: 0,
|
|
70
|
+
budgetMs: 0,
|
|
71
|
+
recovery: null,
|
|
72
|
+
cleanup: null,
|
|
73
|
+
receiptPrune: null,
|
|
74
|
+
categories,
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
const deadlineSummary = (
|
|
78
|
+
budget: ReturnType<typeof createMaintenancePassBudget>
|
|
79
|
+
): ShareLinkMaintenanceRunSummary => ({
|
|
80
|
+
status: 'deadline_exhausted',
|
|
81
|
+
durationMs: budget.elapsedMs(),
|
|
82
|
+
budgetMs: budget.configuredMs,
|
|
83
|
+
recovery: null,
|
|
84
|
+
cleanup: null,
|
|
85
|
+
receiptPrune: null,
|
|
86
|
+
categories: {},
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
const initializationFailedSummary = (): ShareLinkMaintenanceRunSummary => ({
|
|
90
|
+
status: 'initialization_failed',
|
|
91
|
+
durationMs: 0,
|
|
92
|
+
budgetMs: 0,
|
|
93
|
+
recovery: null,
|
|
94
|
+
cleanup: null,
|
|
95
|
+
receiptPrune: null,
|
|
96
|
+
categories: { share_link_maintenance_initialization_failed: 1 },
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
/** Sample a live remaining-time source once; fail closed on a bad value. */
|
|
100
|
+
const sampleRemainingMs = (source: number | (() => number) | undefined): number | undefined => {
|
|
101
|
+
if (source === undefined) return undefined;
|
|
102
|
+
|
|
103
|
+
let value: unknown;
|
|
104
|
+
|
|
105
|
+
try {
|
|
106
|
+
value = typeof source === 'function' ? source() : source;
|
|
107
|
+
} catch {
|
|
108
|
+
// A throwing Lambda context must not start work; fail closed.
|
|
109
|
+
return 0;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
if (typeof value !== 'number' || !Number.isFinite(value)) return 0;
|
|
113
|
+
|
|
114
|
+
return value;
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Production composition for the share-link maintenance pass.
|
|
119
|
+
*
|
|
120
|
+
* Disabled or malformed configuration resolves to an inert runtime: `runOnce`
|
|
121
|
+
* returns a fixed summary and never constructs the client, signer, repositories
|
|
122
|
+
* or any graph/remote dependency. When enabled, composition is lazy on the first
|
|
123
|
+
* invocation and reuses the existing did:web signing adapter with an explicitly
|
|
124
|
+
* configured trusted origin/audience/opaque namespace. There is no unsigned or
|
|
125
|
+
* fresh-key path.
|
|
126
|
+
*
|
|
127
|
+
* The invocation budget starts BEFORE dependency initialization: a cold start
|
|
128
|
+
* consumes the same deadline as the work, and an already-exhausted invocation
|
|
129
|
+
* performs no dependency/signer/graph setup at all. A successful setup is cached;
|
|
130
|
+
* a failed or budget-aborted setup is cleared so the next cadence retries.
|
|
131
|
+
*/
|
|
132
|
+
export const createShareLinkMaintenanceRuntime = (
|
|
133
|
+
options: CreateShareLinkMaintenanceRuntimeOptions = {}
|
|
134
|
+
): ShareLinkMaintenanceRuntime => {
|
|
135
|
+
const resolution = resolveShareLinkMaintenanceConfig(
|
|
136
|
+
options.rawEnvironment ?? (process.env as Record<string, unknown>)
|
|
137
|
+
);
|
|
138
|
+
const logger = options.logger ?? consoleLogger;
|
|
139
|
+
|
|
140
|
+
if (resolution.status === 'disabled') {
|
|
141
|
+
const summary = inertSummary('disabled', {});
|
|
142
|
+
return {
|
|
143
|
+
resolution,
|
|
144
|
+
runOnce: async () => {
|
|
145
|
+
safeLogMaintenance(logger, summary);
|
|
146
|
+
|
|
147
|
+
return summary;
|
|
148
|
+
},
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
if (resolution.status === 'invalid') {
|
|
153
|
+
const summary = inertSummary('invalid_configuration', {
|
|
154
|
+
share_link_maintenance_configuration_invalid: 1,
|
|
155
|
+
});
|
|
156
|
+
return {
|
|
157
|
+
resolution,
|
|
158
|
+
runOnce: async () => {
|
|
159
|
+
safeLogMaintenance(logger, summary);
|
|
160
|
+
|
|
161
|
+
return summary;
|
|
162
|
+
},
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
const { config } = resolution;
|
|
167
|
+
let cached: Promise<DependencySetup> | null = null;
|
|
168
|
+
|
|
169
|
+
const buildDependencies = async (
|
|
170
|
+
budget: ReturnType<typeof createMaintenancePassBudget>
|
|
171
|
+
): Promise<DependencySetup> => {
|
|
172
|
+
try {
|
|
173
|
+
const client = options.createClient
|
|
174
|
+
? await options.createClient(config)
|
|
175
|
+
: await buildRuntimeClient(config);
|
|
176
|
+
|
|
177
|
+
if (budget.exhausted()) return { status: 'expired' };
|
|
178
|
+
|
|
179
|
+
const repositories = options.createRepositories
|
|
180
|
+
? await options.createRepositories(client, config)
|
|
181
|
+
: await buildRuntimeRepositories(client, config);
|
|
182
|
+
|
|
183
|
+
if (budget.exhausted()) return { status: 'expired' };
|
|
184
|
+
|
|
185
|
+
return { status: 'ready', dependencies: { client, ...repositories } };
|
|
186
|
+
} catch {
|
|
187
|
+
return { status: 'failed' };
|
|
188
|
+
}
|
|
189
|
+
};
|
|
190
|
+
|
|
191
|
+
const resolveDependencies = async (
|
|
192
|
+
budget: ReturnType<typeof createMaintenancePassBudget>
|
|
193
|
+
): Promise<DependencySetup> => {
|
|
194
|
+
if (!cached) {
|
|
195
|
+
const pending = buildDependencies(budget);
|
|
196
|
+
cached = pending;
|
|
197
|
+
|
|
198
|
+
const result = await pending;
|
|
199
|
+
|
|
200
|
+
// Keep a successful/in-flight setup cached; clear a failed or
|
|
201
|
+
// budget-aborted one so the next cadence can retry.
|
|
202
|
+
if (result.status !== 'ready' && cached === pending) cached = null;
|
|
203
|
+
|
|
204
|
+
return result;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
return cached;
|
|
208
|
+
};
|
|
209
|
+
|
|
210
|
+
return {
|
|
211
|
+
resolution,
|
|
212
|
+
runOnce: async runOptions => {
|
|
213
|
+
const requestedRemaining = sampleRemainingMs(runOptions?.remainingTimeMs);
|
|
214
|
+
const budget = createMaintenancePassBudget({
|
|
215
|
+
budgetMs: resolveMaintenanceBudgetMs(config.budgetMs, requestedRemaining),
|
|
216
|
+
finalizeReserveMs: config.finalizeReserveMs,
|
|
217
|
+
monotonicNow: options.monotonicNow,
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
if (budget.exhausted()) {
|
|
221
|
+
const summary = deadlineSummary(budget);
|
|
222
|
+
safeLogMaintenance(logger, summary);
|
|
223
|
+
|
|
224
|
+
return summary;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
const setup = await resolveDependencies(budget);
|
|
228
|
+
|
|
229
|
+
if (setup.status === 'failed') {
|
|
230
|
+
const summary = initializationFailedSummary();
|
|
231
|
+
safeLogMaintenance(logger, summary);
|
|
232
|
+
|
|
233
|
+
return summary;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
if (setup.status === 'expired' || budget.exhausted()) {
|
|
237
|
+
const summary = deadlineSummary(budget);
|
|
238
|
+
safeLogMaintenance(logger, summary);
|
|
239
|
+
|
|
240
|
+
return summary;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
return runShareLinkMaintenancePass({
|
|
244
|
+
config,
|
|
245
|
+
recoveryRepository: setup.dependencies.recoveryRepository,
|
|
246
|
+
cleanupRepository: setup.dependencies.cleanupRepository,
|
|
247
|
+
pruneReceipts: setup.dependencies.pruneReceipts,
|
|
248
|
+
client: setup.dependencies.client,
|
|
249
|
+
logger,
|
|
250
|
+
monotonicNow: options.monotonicNow,
|
|
251
|
+
now: options.now,
|
|
252
|
+
budget,
|
|
253
|
+
});
|
|
254
|
+
},
|
|
255
|
+
};
|
|
256
|
+
};
|
|
257
|
+
|
|
258
|
+
/** Lazy production transport: loads the did:web signer only when enabled. */
|
|
259
|
+
const buildRuntimeClient = async (
|
|
260
|
+
config: Extract<ShareLinkMaintenanceConfigResolution, { status: 'enabled' }>['config']
|
|
261
|
+
): Promise<MaintenanceClient> => {
|
|
262
|
+
const [{ getServerDidWebDID }, { createDidWebLearnCardTokenSigner }, clientModule] =
|
|
263
|
+
await Promise.all([
|
|
264
|
+
import('../learnCard.helpers'),
|
|
265
|
+
import('../share-content-client/adapters'),
|
|
266
|
+
import('../share-content-client'),
|
|
267
|
+
]);
|
|
268
|
+
|
|
269
|
+
const signerDid = getServerDidWebDID();
|
|
270
|
+
const clientConfig = clientModule.resolveShareContentClientConfig({
|
|
271
|
+
enabled: true,
|
|
272
|
+
origin: config.origin,
|
|
273
|
+
namespace: config.namespace,
|
|
274
|
+
audience: config.audience,
|
|
275
|
+
signerDid,
|
|
276
|
+
allowInsecureLoopback: config.allowInsecureLoopback,
|
|
277
|
+
signer: createDidWebLearnCardTokenSigner(),
|
|
278
|
+
requestTimeoutMs: config.requestTimeoutMs,
|
|
279
|
+
// Maintenance is one bounded attempt per unit; the scheduler/cadence owns
|
|
280
|
+
// retry, so a transient failure is re-queued rather than retried inline.
|
|
281
|
+
maxAttempts: 1,
|
|
282
|
+
retryBackoffMs: 0,
|
|
283
|
+
});
|
|
284
|
+
|
|
285
|
+
if (!clientConfig.enabled) {
|
|
286
|
+
throw new Error('share-link maintenance client configuration was rejected');
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
return clientModule.createShareContentClient(clientConfig);
|
|
290
|
+
};
|
|
291
|
+
|
|
292
|
+
/** Lazy production repositories: imports the Neo4j-backed wiring only when enabled. */
|
|
293
|
+
const buildRuntimeRepositories = async (
|
|
294
|
+
client: MaintenanceClient,
|
|
295
|
+
config: Extract<ShareLinkMaintenanceConfigResolution, { status: 'enabled' }>['config']
|
|
296
|
+
): Promise<{
|
|
297
|
+
recoveryRepository: RecoveryRunnerDependencies['repository'];
|
|
298
|
+
cleanupRepository: CleanupRunnerDependencies['repository'];
|
|
299
|
+
pruneReceipts: ResolvedRunnerDependencies['pruneReceipts'];
|
|
300
|
+
}> => {
|
|
301
|
+
const runtimeModule = await import('../share-link-coordinator/runtime');
|
|
302
|
+
|
|
303
|
+
const recoveryDependencies = runtimeModule.createRuntimeRecoveryRunnerDependencies({
|
|
304
|
+
client,
|
|
305
|
+
claimant: config.claimant,
|
|
306
|
+
namespace: config.namespace,
|
|
307
|
+
});
|
|
308
|
+
const cleanupDependencies = runtimeModule.createRuntimeCleanupRunnerDependencies({
|
|
309
|
+
client,
|
|
310
|
+
claimant: config.claimant,
|
|
311
|
+
namespace: config.namespace,
|
|
312
|
+
});
|
|
313
|
+
|
|
314
|
+
// Preinitialize the idempotent share-link constraint/index gate once, outside
|
|
315
|
+
// the per-unit budget, so a maintenance transaction never pays the one-time
|
|
316
|
+
// schema setup. It is cached in-process and is not a per-pass cost.
|
|
317
|
+
const { ensureShareLinkConstraints } = await import('../../models/share-link-constraints');
|
|
318
|
+
await ensureShareLinkConstraints();
|
|
319
|
+
|
|
320
|
+
// Load the receipt-prune repository last: it pulls the Neo4j `@instance`, so
|
|
321
|
+
// it must only be imported when an enabled runtime actually composes work.
|
|
322
|
+
const { pruneShareViewReceipts } = await import('../../accesslayer/share-link/receipt');
|
|
323
|
+
|
|
324
|
+
return {
|
|
325
|
+
recoveryRepository: recoveryDependencies.repository,
|
|
326
|
+
cleanupRepository: cleanupDependencies.repository,
|
|
327
|
+
pruneReceipts: pruneShareViewReceipts,
|
|
328
|
+
};
|
|
329
|
+
};
|
|
330
|
+
|
|
331
|
+
const consoleLogger: ShareLinkMaintenanceLogger = {
|
|
332
|
+
info: event => console.log('[share-link-maintenance]', event),
|
|
333
|
+
error: event => console.error('[share-link-maintenance]', event),
|
|
334
|
+
};
|
|
@@ -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,70 @@
|
|
|
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
|
+
},
|
|
43
|
+
categories: {
|
|
44
|
+
...(summary.recovery?.categories ?? {}),
|
|
45
|
+
...(summary.cleanup?.categories ?? {}),
|
|
46
|
+
...summary.categories,
|
|
47
|
+
},
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Emit one allowlisted event. A throwing logger is swallowed: telemetry must
|
|
52
|
+
* never corrupt lifecycle state or turn a completed pass into a failure.
|
|
53
|
+
*/
|
|
54
|
+
export const safeLogMaintenance = (
|
|
55
|
+
logger: ShareLinkMaintenanceLogger,
|
|
56
|
+
summary: ShareLinkMaintenanceRunSummary
|
|
57
|
+
): void => {
|
|
58
|
+
try {
|
|
59
|
+
const event = toMaintenanceLogEvent(summary);
|
|
60
|
+
if (summary.status === 'failed') logger.error(event);
|
|
61
|
+
else logger.info(event);
|
|
62
|
+
} catch {
|
|
63
|
+
// Intentionally ignored: lifecycle state is authoritative.
|
|
64
|
+
}
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
export const createConsoleMaintenanceLogger = (): ShareLinkMaintenanceLogger => ({
|
|
68
|
+
info: event => console.log('[share-link-maintenance]', event),
|
|
69
|
+
error: event => console.error('[share-link-maintenance]', event),
|
|
70
|
+
});
|
|
@@ -0,0 +1,89 @@
|
|
|
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_receipt_prune_failed'
|
|
54
|
+
| 'share_link_maintenance_initialization_failed'
|
|
55
|
+
| 'share_link_maintenance_configuration_invalid';
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Aggregate-only result of one maintenance invocation. It contains counts,
|
|
59
|
+
* durations and fixed categories; it never contains object/share/owner ids,
|
|
60
|
+
* URLs, keys, ciphertext, recovery, titles, notes or exception objects.
|
|
61
|
+
*/
|
|
62
|
+
export type ShareLinkMaintenanceRunSummary = {
|
|
63
|
+
status: ShareLinkMaintenanceStatus;
|
|
64
|
+
durationMs: number;
|
|
65
|
+
budgetMs: number;
|
|
66
|
+
recovery: ShareRecoveryRunSummary | null;
|
|
67
|
+
cleanup: CleanupRunSummary | null;
|
|
68
|
+
/** Namespace-scoped expired/consumed receipt nodes deleted this pass. */
|
|
69
|
+
receiptPrune: { pruned: number } | null;
|
|
70
|
+
categories: Partial<Record<ShareLinkMaintenanceFailureCategory, number>>;
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Allowlisted log event shape. The logger is called with this object only; a
|
|
75
|
+
* throwing logger must never corrupt lifecycle state.
|
|
76
|
+
*/
|
|
77
|
+
export type ShareLinkMaintenanceLogEvent = {
|
|
78
|
+
event: 'share_link_maintenance';
|
|
79
|
+
status: ShareLinkMaintenanceStatus;
|
|
80
|
+
durationMs: number;
|
|
81
|
+
budgetMs: number;
|
|
82
|
+
counts: Record<string, number>;
|
|
83
|
+
categories: Record<string, number>;
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
export type ShareLinkMaintenanceLogger = {
|
|
87
|
+
info: (event: ShareLinkMaintenanceLogEvent) => void;
|
|
88
|
+
error: (event: ShareLinkMaintenanceLogEvent) => void;
|
|
89
|
+
};
|
|
@@ -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
|
+
};
|