@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,640 @@
|
|
|
1
|
+
import { TRPCError } from '@trpc/server';
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
CreateShareLinkInputValidator,
|
|
5
|
+
ListShareLinksInputValidator,
|
|
6
|
+
PaginatedShareLinksValidator,
|
|
7
|
+
ShareLinkOwnerCommitOutputValidator,
|
|
8
|
+
ShareLinkOwnerContentOutputValidator,
|
|
9
|
+
ShareLinkOwnerRecoveryOutputValidator,
|
|
10
|
+
ShareLinkOwnerStatusOutputValidator,
|
|
11
|
+
ShareLinkOperationKeyInputValidator,
|
|
12
|
+
ShareOwnerRecoveryValidator,
|
|
13
|
+
UpdateShareLinkInputValidator,
|
|
14
|
+
} from '@learncard/types';
|
|
15
|
+
|
|
16
|
+
import { t, profileRouteWithoutInputCapture } from '@routes';
|
|
17
|
+
import {
|
|
18
|
+
AUTH_GRANT_SHARE_LINKS_READ_SCOPE,
|
|
19
|
+
AUTH_GRANT_SHARE_LINKS_WRITE_SCOPE,
|
|
20
|
+
} from 'src/constants/auth-grant';
|
|
21
|
+
import { enforceRateLimits } from '@helpers/rateLimit.helpers';
|
|
22
|
+
import { toOwnerShareLink } from '@helpers/share-link-owner-projection';
|
|
23
|
+
import { createRetryableLazyInitializer } from '@helpers/share-link-owner/lazy-initializer';
|
|
24
|
+
import { resolveShareLinkOwnerApiConfig } from '@helpers/share-link-owner/config';
|
|
25
|
+
import type { ShareLinkOwnerApiConfigResolution } from '@helpers/share-link-owner/config';
|
|
26
|
+
import {
|
|
27
|
+
createBoundedShareLinkDiagnosticReporter,
|
|
28
|
+
createShareLinkDependencyResolver,
|
|
29
|
+
SHARE_LINK_COMPOSITION_DIAGNOSTIC,
|
|
30
|
+
} from '@helpers/share-link-owner/diagnostics';
|
|
31
|
+
import { createShareLinkPolicyResolver } from '@helpers/share-link-policy/resolver';
|
|
32
|
+
import { ShareLinkCoordinatorError } from '@helpers/share-link-coordinator';
|
|
33
|
+
import { getShareLinkRequestHashSecret } from '@helpers/share-link-lifecycle';
|
|
34
|
+
import type {
|
|
35
|
+
RecoveryRunnerDependencies,
|
|
36
|
+
ShareLinkCoordinator,
|
|
37
|
+
ShareLinkCoordinatorErrorCode,
|
|
38
|
+
} from '@helpers/share-link-coordinator';
|
|
39
|
+
import { decodeShareLinkListCursor } from '@accesslayer/share-link/list';
|
|
40
|
+
import type {
|
|
41
|
+
ListShareLinksInput,
|
|
42
|
+
ListShareLinksResult,
|
|
43
|
+
ShareLinkListCursor,
|
|
44
|
+
} from '@accesslayer/share-link/types';
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* LC-2187 owner share-link APIs (D1).
|
|
48
|
+
*
|
|
49
|
+
* Disabled by default and fail-closed: the namespace is taken exclusively from
|
|
50
|
+
* trusted server configuration (never from the caller, tenant header or Host),
|
|
51
|
+
* the owner exclusively from the authenticated profile, and the default policy
|
|
52
|
+
* exclusively from server-side sources. There are no public resolve/content/ack
|
|
53
|
+
* routes in D1.
|
|
54
|
+
*/
|
|
55
|
+
|
|
56
|
+
export type ShareLinkRouterDependencies = {
|
|
57
|
+
readonly namespace: string;
|
|
58
|
+
readonly coordinator: ShareLinkCoordinator;
|
|
59
|
+
readonly recovery: RecoveryRunnerDependencies;
|
|
60
|
+
/**
|
|
61
|
+
* Bounded, namespace+owner-scoped keyset listing over the immutable
|
|
62
|
+
* `(createdAt, id)` key. Receives only trusted scope; the route decodes and
|
|
63
|
+
* validates any caller cursor first.
|
|
64
|
+
*/
|
|
65
|
+
readonly listShareLinks: (input: ListShareLinksInput) => Promise<ListShareLinksResult>;
|
|
66
|
+
/** Per-owner create/update/revoke/retry abuse limit. */
|
|
67
|
+
readonly enforceOwnerWriteRateLimit: (ownerProfileId: string) => Promise<void>;
|
|
68
|
+
/**
|
|
69
|
+
* Freshly re-resolve authoritative view-counting eligibility for the owner.
|
|
70
|
+
* Used only to decide whether `viewCount`/`lastViewedAt` may appear in the
|
|
71
|
+
* owner projection; a resolver failure fails closed to `false`.
|
|
72
|
+
*/
|
|
73
|
+
readonly resolveViewCountingEligibility: (ownerProfileId: string) => Promise<boolean>;
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
/** Eligibility for projection: an unavailable resolver never leaks a count. */
|
|
77
|
+
const safeEligibility = async (
|
|
78
|
+
dependencies: ShareLinkRouterDependencies,
|
|
79
|
+
ownerProfileId: string
|
|
80
|
+
): Promise<boolean> => {
|
|
81
|
+
try {
|
|
82
|
+
return (await dependencies.resolveViewCountingEligibility(ownerProfileId)) === true;
|
|
83
|
+
} catch {
|
|
84
|
+
return false;
|
|
85
|
+
}
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
function notFound(): never {
|
|
89
|
+
throw new TRPCError({ code: 'NOT_FOUND' });
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Fixed, sanitized tRPC mapping for coordinator/repository failures.
|
|
94
|
+
*
|
|
95
|
+
* Raw messages, stack traces and `cause` chains from the graph/remote layer
|
|
96
|
+
* must never cross the route boundary or reach telemetry. Every documented
|
|
97
|
+
* coordinator code maps to one stable tRPC code and one safe message.
|
|
98
|
+
*/
|
|
99
|
+
const mapCoordinatorError = (code: ShareLinkCoordinatorErrorCode): TRPCError => {
|
|
100
|
+
switch (code) {
|
|
101
|
+
case 'INVALID_INPUT':
|
|
102
|
+
return new TRPCError({ code: 'BAD_REQUEST', message: 'invalid share-link request' });
|
|
103
|
+
case 'NOT_FOUND':
|
|
104
|
+
return new TRPCError({ code: 'NOT_FOUND', message: 'share-link not found' });
|
|
105
|
+
case 'CONFLICT':
|
|
106
|
+
case 'OPERATION_IN_FLIGHT':
|
|
107
|
+
case 'LEASE_EXPIRED':
|
|
108
|
+
case 'STALE_GENERATION':
|
|
109
|
+
return new TRPCError({ code: 'CONFLICT', message: 'share-link state conflict' });
|
|
110
|
+
case 'PRECONDITION_FAILED':
|
|
111
|
+
return new TRPCError({
|
|
112
|
+
code: 'PRECONDITION_FAILED',
|
|
113
|
+
message: 'share-link precondition failed',
|
|
114
|
+
});
|
|
115
|
+
case 'UNAUTHORIZED':
|
|
116
|
+
return new TRPCError({
|
|
117
|
+
code: 'UNAUTHORIZED',
|
|
118
|
+
message: 'share-link operation is not authorized',
|
|
119
|
+
});
|
|
120
|
+
case 'PAYLOAD_TOO_LARGE':
|
|
121
|
+
return new TRPCError({
|
|
122
|
+
code: 'PAYLOAD_TOO_LARGE',
|
|
123
|
+
message: 'share-link payload is too large',
|
|
124
|
+
});
|
|
125
|
+
case 'UNAVAILABLE':
|
|
126
|
+
return new TRPCError({
|
|
127
|
+
code: 'SERVICE_UNAVAILABLE',
|
|
128
|
+
message: 'share-link service is unavailable',
|
|
129
|
+
});
|
|
130
|
+
case 'SIGNING_FAILED':
|
|
131
|
+
case 'UNEXPECTED':
|
|
132
|
+
return new TRPCError({
|
|
133
|
+
code: 'INTERNAL_SERVER_ERROR',
|
|
134
|
+
message: 'share-link operation failed',
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
};
|
|
138
|
+
|
|
139
|
+
/** Run a coordinator/recovery call, converting only its typed failures. */
|
|
140
|
+
const runCoordinated = async <T>(operation: () => Promise<T>): Promise<T> => {
|
|
141
|
+
try {
|
|
142
|
+
return await operation();
|
|
143
|
+
} catch (error) {
|
|
144
|
+
if (error instanceof TRPCError) throw error;
|
|
145
|
+
if (error instanceof ShareLinkCoordinatorError) throw mapCoordinatorError(error.code);
|
|
146
|
+
|
|
147
|
+
throw new TRPCError({
|
|
148
|
+
code: 'INTERNAL_SERVER_ERROR',
|
|
149
|
+
message: 'share-link operation failed',
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
};
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* Complete-request byte bounding lives in the route base
|
|
156
|
+
* (`openRouteWithoutInputCapture`) so it runs before authentication and before
|
|
157
|
+
* Zod parsing; see `@routes`.
|
|
158
|
+
*/
|
|
159
|
+
|
|
160
|
+
const openapi = (method: 'GET' | 'POST', path: `/${string}`, summary: string) => ({
|
|
161
|
+
protect: true as const,
|
|
162
|
+
method,
|
|
163
|
+
path,
|
|
164
|
+
tags: ['ShareLinks'],
|
|
165
|
+
summary,
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
const projectCommit = (
|
|
169
|
+
result:
|
|
170
|
+
| { status: 'committed' | 'replayed'; share: Parameters<typeof toOwnerShareLink>[0] }
|
|
171
|
+
| { status: 'pending'; reservation: { operationId: string } },
|
|
172
|
+
id: string,
|
|
173
|
+
viewCountingEnabled: boolean
|
|
174
|
+
) =>
|
|
175
|
+
result.status === 'pending'
|
|
176
|
+
? { status: 'pending' as const, id, operationId: result.reservation.operationId }
|
|
177
|
+
: {
|
|
178
|
+
status: 'completed' as const,
|
|
179
|
+
share: toOwnerShareLink(result.share, viewCountingEnabled),
|
|
180
|
+
};
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* Build the owner router over injected dependencies. Route-boundary tests pass
|
|
184
|
+
* fakes; production passes the lazily-built runtime.
|
|
185
|
+
*/
|
|
186
|
+
export const createShareLinksRouter = (
|
|
187
|
+
getDependencies: () => Promise<ShareLinkRouterDependencies | null>
|
|
188
|
+
) => {
|
|
189
|
+
const resolve = async (): Promise<ShareLinkRouterDependencies> => {
|
|
190
|
+
let dependencies: ShareLinkRouterDependencies | null;
|
|
191
|
+
|
|
192
|
+
try {
|
|
193
|
+
dependencies = await getDependencies();
|
|
194
|
+
} catch {
|
|
195
|
+
// Initialization failed transiently. Never surface the raw failure;
|
|
196
|
+
// the next request retries initialization.
|
|
197
|
+
throw new TRPCError({
|
|
198
|
+
code: 'INTERNAL_SERVER_ERROR',
|
|
199
|
+
message: 'share-link service is unavailable',
|
|
200
|
+
});
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
if (!dependencies) notFound();
|
|
204
|
+
|
|
205
|
+
return dependencies;
|
|
206
|
+
};
|
|
207
|
+
|
|
208
|
+
return t.router({
|
|
209
|
+
create: profileRouteWithoutInputCapture
|
|
210
|
+
.meta({
|
|
211
|
+
openapi: openapi('POST', '/share-links/create', 'Create an owner share link'),
|
|
212
|
+
requiredScope: AUTH_GRANT_SHARE_LINKS_WRITE_SCOPE,
|
|
213
|
+
})
|
|
214
|
+
.input(CreateShareLinkInputValidator)
|
|
215
|
+
.output(ShareLinkOwnerCommitOutputValidator)
|
|
216
|
+
.mutation(async ({ ctx, input }) => {
|
|
217
|
+
const dependencies = await resolve();
|
|
218
|
+
await dependencies.enforceOwnerWriteRateLimit(ctx.user.profile.profileId);
|
|
219
|
+
|
|
220
|
+
const result = await runCoordinated(() =>
|
|
221
|
+
dependencies.coordinator.createShareLink(input, {
|
|
222
|
+
namespace: dependencies.namespace,
|
|
223
|
+
ownerProfileId: ctx.user.profile.profileId,
|
|
224
|
+
})
|
|
225
|
+
);
|
|
226
|
+
|
|
227
|
+
return projectCommit(
|
|
228
|
+
result,
|
|
229
|
+
input.id,
|
|
230
|
+
await safeEligibility(dependencies, ctx.user.profile.profileId)
|
|
231
|
+
);
|
|
232
|
+
}),
|
|
233
|
+
|
|
234
|
+
update: profileRouteWithoutInputCapture
|
|
235
|
+
.meta({
|
|
236
|
+
openapi: openapi('POST', '/share-links/update', 'Update an owner share link'),
|
|
237
|
+
requiredScope: AUTH_GRANT_SHARE_LINKS_WRITE_SCOPE,
|
|
238
|
+
})
|
|
239
|
+
.input(UpdateShareLinkInputValidator)
|
|
240
|
+
.output(ShareLinkOwnerCommitOutputValidator)
|
|
241
|
+
.mutation(async ({ ctx, input }) => {
|
|
242
|
+
const dependencies = await resolve();
|
|
243
|
+
await dependencies.enforceOwnerWriteRateLimit(ctx.user.profile.profileId);
|
|
244
|
+
|
|
245
|
+
const result = await runCoordinated(() =>
|
|
246
|
+
dependencies.coordinator.updateShareLink(input, {
|
|
247
|
+
namespace: dependencies.namespace,
|
|
248
|
+
ownerProfileId: ctx.user.profile.profileId,
|
|
249
|
+
})
|
|
250
|
+
);
|
|
251
|
+
|
|
252
|
+
return projectCommit(
|
|
253
|
+
result,
|
|
254
|
+
input.id,
|
|
255
|
+
await safeEligibility(dependencies, ctx.user.profile.profileId)
|
|
256
|
+
);
|
|
257
|
+
}),
|
|
258
|
+
|
|
259
|
+
revoke: profileRouteWithoutInputCapture
|
|
260
|
+
.meta({
|
|
261
|
+
openapi: openapi('POST', '/share-links/revoke', 'Revoke an owner share link'),
|
|
262
|
+
requiredScope: AUTH_GRANT_SHARE_LINKS_WRITE_SCOPE,
|
|
263
|
+
})
|
|
264
|
+
.input(
|
|
265
|
+
ShareLinkOperationKeyInputValidator.pick({ id: true }).extend({
|
|
266
|
+
expectedVersion: UpdateShareLinkInputValidator.shape.expectedVersion.optional(),
|
|
267
|
+
clientRequestId: CreateShareLinkInputValidator.shape.clientRequestId.optional(),
|
|
268
|
+
})
|
|
269
|
+
)
|
|
270
|
+
.output(ShareLinkOwnerCommitOutputValidator)
|
|
271
|
+
.mutation(async ({ ctx, input }) => {
|
|
272
|
+
const dependencies = await resolve();
|
|
273
|
+
await dependencies.enforceOwnerWriteRateLimit(ctx.user.profile.profileId);
|
|
274
|
+
|
|
275
|
+
const result = await runCoordinated(() =>
|
|
276
|
+
dependencies.coordinator.revokeShareLink(input, {
|
|
277
|
+
namespace: dependencies.namespace,
|
|
278
|
+
ownerProfileId: ctx.user.profile.profileId,
|
|
279
|
+
})
|
|
280
|
+
);
|
|
281
|
+
|
|
282
|
+
// `completed` is the outer terminal tag; the authoritative state
|
|
283
|
+
// (here `stopped`) lives on `share.status`.
|
|
284
|
+
return {
|
|
285
|
+
status: 'completed' as const,
|
|
286
|
+
share: toOwnerShareLink(
|
|
287
|
+
result.share,
|
|
288
|
+
await safeEligibility(dependencies, ctx.user.profile.profileId)
|
|
289
|
+
),
|
|
290
|
+
};
|
|
291
|
+
}),
|
|
292
|
+
|
|
293
|
+
list: profileRouteWithoutInputCapture
|
|
294
|
+
.meta({
|
|
295
|
+
// A collection path with no trailing segment, so it can never be
|
|
296
|
+
// shadowed by (or shadow) the `GET /share-links/{id}` owner route
|
|
297
|
+
// or any public `/public/share-links/...` route.
|
|
298
|
+
openapi: openapi('GET', '/share-links', 'List owner share links'),
|
|
299
|
+
requiredScope: AUTH_GRANT_SHARE_LINKS_READ_SCOPE,
|
|
300
|
+
})
|
|
301
|
+
.input(ListShareLinksInputValidator)
|
|
302
|
+
.output(PaginatedShareLinksValidator)
|
|
303
|
+
.query(async ({ ctx, input }) => {
|
|
304
|
+
const dependencies = await resolve();
|
|
305
|
+
|
|
306
|
+
let cursor: ShareLinkListCursor | null = null;
|
|
307
|
+
if (input.cursor !== undefined) {
|
|
308
|
+
cursor = decodeShareLinkListCursor(input.cursor);
|
|
309
|
+
// Malformed/oversized/impossible/foreign-version cursors fail
|
|
310
|
+
// closed with one fixed message and never reach the graph.
|
|
311
|
+
if (cursor === null) {
|
|
312
|
+
throw new TRPCError({
|
|
313
|
+
code: 'BAD_REQUEST',
|
|
314
|
+
message: 'invalid share-link cursor',
|
|
315
|
+
});
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
let page: ListShareLinksResult;
|
|
320
|
+
try {
|
|
321
|
+
page = await dependencies.listShareLinks({
|
|
322
|
+
namespace: dependencies.namespace,
|
|
323
|
+
ownerProfileId: ctx.user.profile.profileId,
|
|
324
|
+
limit: input.limit,
|
|
325
|
+
cursor,
|
|
326
|
+
});
|
|
327
|
+
} catch {
|
|
328
|
+
// Fixed safe mapping: never surface a raw repository message.
|
|
329
|
+
throw new TRPCError({
|
|
330
|
+
code: 'INTERNAL_SERVER_ERROR',
|
|
331
|
+
message: 'share-link list is unavailable',
|
|
332
|
+
});
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
// Eligibility is re-resolved exactly once per request; a failure
|
|
336
|
+
// or non-`true` answer suppresses viewCount/lastViewedAt for
|
|
337
|
+
// every record (fail-closed).
|
|
338
|
+
const viewCountingEnabled = await safeEligibility(
|
|
339
|
+
dependencies,
|
|
340
|
+
ctx.user.profile.profileId
|
|
341
|
+
);
|
|
342
|
+
|
|
343
|
+
return {
|
|
344
|
+
records: page.records.map(record =>
|
|
345
|
+
toOwnerShareLink(record, viewCountingEnabled)
|
|
346
|
+
),
|
|
347
|
+
hasMore: page.hasMore,
|
|
348
|
+
...(page.nextCursor === null ? {} : { cursor: page.nextCursor }),
|
|
349
|
+
};
|
|
350
|
+
}),
|
|
351
|
+
|
|
352
|
+
get: profileRouteWithoutInputCapture
|
|
353
|
+
.meta({
|
|
354
|
+
openapi: openapi('GET', '/share-links/{id}', 'Get owner share-link metadata'),
|
|
355
|
+
requiredScope: AUTH_GRANT_SHARE_LINKS_READ_SCOPE,
|
|
356
|
+
})
|
|
357
|
+
.input(ShareLinkOperationKeyInputValidator.pick({ id: true }))
|
|
358
|
+
.output(ShareLinkOwnerStatusOutputValidator)
|
|
359
|
+
.query(async ({ ctx, input }) => {
|
|
360
|
+
const dependencies = await resolve();
|
|
361
|
+
const share = await runCoordinated(() =>
|
|
362
|
+
dependencies.coordinator.getShareLink(input.id, {
|
|
363
|
+
namespace: dependencies.namespace,
|
|
364
|
+
ownerProfileId: ctx.user.profile.profileId,
|
|
365
|
+
})
|
|
366
|
+
);
|
|
367
|
+
|
|
368
|
+
return share === null
|
|
369
|
+
? { status: 'not_found' as const, id: input.id }
|
|
370
|
+
: {
|
|
371
|
+
status: 'found' as const,
|
|
372
|
+
share: toOwnerShareLink(
|
|
373
|
+
share,
|
|
374
|
+
await safeEligibility(dependencies, ctx.user.profile.profileId)
|
|
375
|
+
),
|
|
376
|
+
};
|
|
377
|
+
}),
|
|
378
|
+
|
|
379
|
+
getOperationStatus: profileRouteWithoutInputCapture
|
|
380
|
+
.meta({
|
|
381
|
+
openapi: openapi(
|
|
382
|
+
'GET',
|
|
383
|
+
'/share-links/operations/{operationId}',
|
|
384
|
+
'Get owner scoped operation status'
|
|
385
|
+
),
|
|
386
|
+
requiredScope: AUTH_GRANT_SHARE_LINKS_READ_SCOPE,
|
|
387
|
+
})
|
|
388
|
+
.input(ShareLinkOperationKeyInputValidator)
|
|
389
|
+
.output(ShareLinkOwnerStatusOutputValidator)
|
|
390
|
+
.query(async ({ ctx, input }) => {
|
|
391
|
+
const dependencies = await resolve();
|
|
392
|
+
const target = await runCoordinated(() =>
|
|
393
|
+
dependencies.recovery.repository.readShareLinkRecoveryTarget({
|
|
394
|
+
namespace: dependencies.namespace,
|
|
395
|
+
ownerProfileId: ctx.user.profile.profileId,
|
|
396
|
+
shareId: input.id,
|
|
397
|
+
operationId: input.operationId,
|
|
398
|
+
})
|
|
399
|
+
);
|
|
400
|
+
|
|
401
|
+
if (target.state === 'committed') {
|
|
402
|
+
return {
|
|
403
|
+
status: 'found' as const,
|
|
404
|
+
share: toOwnerShareLink(
|
|
405
|
+
target.share,
|
|
406
|
+
await safeEligibility(dependencies, ctx.user.profile.profileId)
|
|
407
|
+
),
|
|
408
|
+
};
|
|
409
|
+
}
|
|
410
|
+
if (target.state === 'reservation') {
|
|
411
|
+
return {
|
|
412
|
+
status: 'pending' as const,
|
|
413
|
+
id: input.id,
|
|
414
|
+
operationId: input.operationId,
|
|
415
|
+
};
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
return { status: 'not_found' as const, id: input.id };
|
|
419
|
+
}),
|
|
420
|
+
|
|
421
|
+
retry: profileRouteWithoutInputCapture
|
|
422
|
+
.meta({
|
|
423
|
+
openapi: openapi('POST', '/share-links/retry', 'Retry an owner operation'),
|
|
424
|
+
requiredScope: AUTH_GRANT_SHARE_LINKS_WRITE_SCOPE,
|
|
425
|
+
})
|
|
426
|
+
.input(ShareLinkOperationKeyInputValidator)
|
|
427
|
+
.output(ShareLinkOwnerStatusOutputValidator)
|
|
428
|
+
.mutation(async ({ ctx, input }) => {
|
|
429
|
+
const dependencies = await resolve();
|
|
430
|
+
await dependencies.enforceOwnerWriteRateLimit(ctx.user.profile.profileId);
|
|
431
|
+
|
|
432
|
+
// Persisted-key recovery only: the server re-derives the immutable
|
|
433
|
+
// object/lease/generation bindings from durable Brain state. A
|
|
434
|
+
// caller-supplied descriptor is never accepted.
|
|
435
|
+
const result = await runCoordinated(async () => {
|
|
436
|
+
const { recoverShareLinkOperation } =
|
|
437
|
+
await import('@helpers/share-link-coordinator/recovery-runner');
|
|
438
|
+
|
|
439
|
+
return recoverShareLinkOperation(dependencies.recovery, {
|
|
440
|
+
namespace: dependencies.namespace,
|
|
441
|
+
ownerProfileId: ctx.user.profile.profileId,
|
|
442
|
+
shareId: input.id,
|
|
443
|
+
operationId: input.operationId,
|
|
444
|
+
});
|
|
445
|
+
});
|
|
446
|
+
|
|
447
|
+
if (result.status === 'committed') {
|
|
448
|
+
return {
|
|
449
|
+
status: 'found' as const,
|
|
450
|
+
share: toOwnerShareLink(
|
|
451
|
+
result.share,
|
|
452
|
+
await safeEligibility(dependencies, ctx.user.profile.profileId)
|
|
453
|
+
),
|
|
454
|
+
};
|
|
455
|
+
}
|
|
456
|
+
if (result.status === 'abandoned' || result.status === 'absent') {
|
|
457
|
+
return { status: 'not_found' as const, id: input.id };
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
return {
|
|
461
|
+
status: 'pending' as const,
|
|
462
|
+
id: input.id,
|
|
463
|
+
operationId: input.operationId,
|
|
464
|
+
};
|
|
465
|
+
}),
|
|
466
|
+
|
|
467
|
+
getRecovery: profileRouteWithoutInputCapture
|
|
468
|
+
.meta({
|
|
469
|
+
openapi: openapi(
|
|
470
|
+
'GET',
|
|
471
|
+
'/share-links/{id}/recovery',
|
|
472
|
+
'Get owner-encrypted recovery (owner only)'
|
|
473
|
+
),
|
|
474
|
+
requiredScope: AUTH_GRANT_SHARE_LINKS_READ_SCOPE,
|
|
475
|
+
})
|
|
476
|
+
.input(ShareLinkOperationKeyInputValidator.pick({ id: true }))
|
|
477
|
+
.output(ShareLinkOwnerRecoveryOutputValidator)
|
|
478
|
+
.query(async ({ ctx, input }) => {
|
|
479
|
+
const dependencies = await resolve();
|
|
480
|
+
const result = await runCoordinated(() =>
|
|
481
|
+
dependencies.coordinator.readOwnerRecovery(input.id, {
|
|
482
|
+
namespace: dependencies.namespace,
|
|
483
|
+
ownerProfileId: ctx.user.profile.profileId,
|
|
484
|
+
})
|
|
485
|
+
);
|
|
486
|
+
|
|
487
|
+
if (!result.ok) notFound();
|
|
488
|
+
|
|
489
|
+
const parsed = ShareOwnerRecoveryValidator.safeParse(
|
|
490
|
+
result.value.ownerEncryptedRecovery
|
|
491
|
+
);
|
|
492
|
+
if (!parsed.success) notFound();
|
|
493
|
+
|
|
494
|
+
return { recovery: parsed.data };
|
|
495
|
+
}),
|
|
496
|
+
|
|
497
|
+
getContent: profileRouteWithoutInputCapture
|
|
498
|
+
.meta({
|
|
499
|
+
openapi: openapi(
|
|
500
|
+
'GET',
|
|
501
|
+
'/share-links/{id}/content',
|
|
502
|
+
'Get encrypted share content (owner only)'
|
|
503
|
+
),
|
|
504
|
+
requiredScope: AUTH_GRANT_SHARE_LINKS_READ_SCOPE,
|
|
505
|
+
})
|
|
506
|
+
.input(ShareLinkOperationKeyInputValidator.pick({ id: true }))
|
|
507
|
+
.output(ShareLinkOwnerContentOutputValidator)
|
|
508
|
+
.query(async ({ ctx, input }) => {
|
|
509
|
+
const dependencies = await resolve();
|
|
510
|
+
const result = await runCoordinated(() =>
|
|
511
|
+
dependencies.coordinator.fetchShareContent(
|
|
512
|
+
input.id,
|
|
513
|
+
{
|
|
514
|
+
namespace: dependencies.namespace,
|
|
515
|
+
ownerProfileId: ctx.user.profile.profileId,
|
|
516
|
+
},
|
|
517
|
+
{ allowExpired: true }
|
|
518
|
+
)
|
|
519
|
+
);
|
|
520
|
+
|
|
521
|
+
if (!result.ok) notFound();
|
|
522
|
+
|
|
523
|
+
return ShareLinkOwnerContentOutputValidator.parse({
|
|
524
|
+
id: input.id,
|
|
525
|
+
contentVersion: result.value.contentVersion,
|
|
526
|
+
envelope: result.value.envelope,
|
|
527
|
+
});
|
|
528
|
+
}),
|
|
529
|
+
});
|
|
530
|
+
};
|
|
531
|
+
|
|
532
|
+
export type ShareLinksRouter = ReturnType<typeof createShareLinksRouter>;
|
|
533
|
+
|
|
534
|
+
/** Per-hour owner write budget. Fails closed when the cache is unavailable. */
|
|
535
|
+
export const enforceOwnerShareWriteRateLimit = async (
|
|
536
|
+
namespace: string,
|
|
537
|
+
ownerProfileId: string
|
|
538
|
+
): Promise<void> => {
|
|
539
|
+
await enforceRateLimits([
|
|
540
|
+
{
|
|
541
|
+
key: `share-link-owner-write:${namespace}:${ownerProfileId}`,
|
|
542
|
+
limit: 60,
|
|
543
|
+
windowSeconds: 60 * 60,
|
|
544
|
+
description: 'owner share-link writes',
|
|
545
|
+
},
|
|
546
|
+
]);
|
|
547
|
+
};
|
|
548
|
+
|
|
549
|
+
/**
|
|
550
|
+
* Lazy production composition. Config is resolved before any signer/graph import.
|
|
551
|
+
* A disabled configuration is inert and silent; malformed wiring emits a bounded
|
|
552
|
+
* sanitized diagnostic and fails closed to NOT_FOUND. A failed build is never
|
|
553
|
+
* cached, so a later request retries with non-null dependencies.
|
|
554
|
+
*/
|
|
555
|
+
const initializeProductionDependencies =
|
|
556
|
+
createRetryableLazyInitializer<ShareLinkRouterDependencies>(() =>
|
|
557
|
+
buildProductionDependencies(
|
|
558
|
+
resolveShareLinkOwnerApiConfig(process.env as Record<string, unknown>) as Extract<
|
|
559
|
+
ShareLinkOwnerApiConfigResolution,
|
|
560
|
+
{ status: 'enabled' }
|
|
561
|
+
>
|
|
562
|
+
)
|
|
563
|
+
);
|
|
564
|
+
|
|
565
|
+
export const getProductionDependencies =
|
|
566
|
+
createShareLinkDependencyResolver<ShareLinkRouterDependencies>({
|
|
567
|
+
resolveConfig: () => resolveShareLinkOwnerApiConfig(process.env as Record<string, unknown>),
|
|
568
|
+
initializeDependencies: initializeProductionDependencies,
|
|
569
|
+
reportDiagnostic: createBoundedShareLinkDiagnosticReporter(),
|
|
570
|
+
configurationInvalidCategory: SHARE_LINK_COMPOSITION_DIAGNOSTIC.OWNER_CONFIGURATION_INVALID,
|
|
571
|
+
initializationFailedCategory: SHARE_LINK_COMPOSITION_DIAGNOSTIC.OWNER_INITIALIZATION_FAILED,
|
|
572
|
+
});
|
|
573
|
+
|
|
574
|
+
const buildProductionDependencies = async (
|
|
575
|
+
config: Extract<ShareLinkOwnerApiConfigResolution, { status: 'enabled' }>
|
|
576
|
+
): Promise<ShareLinkRouterDependencies> => {
|
|
577
|
+
// Validate before accepting owner operations, not on the first write.
|
|
578
|
+
getShareLinkRequestHashSecret();
|
|
579
|
+
const [
|
|
580
|
+
{ getServerDidWebDID },
|
|
581
|
+
{ createDidWebLearnCardTokenSigner },
|
|
582
|
+
clientModule,
|
|
583
|
+
runtimeModule,
|
|
584
|
+
{ ensureShareLinkConstraints },
|
|
585
|
+
{ createProductionShareLinkPolicySource },
|
|
586
|
+
listRepository,
|
|
587
|
+
] = await Promise.all([
|
|
588
|
+
import('@helpers/learnCard.helpers'),
|
|
589
|
+
import('@helpers/share-content-client/adapters'),
|
|
590
|
+
import('@helpers/share-content-client'),
|
|
591
|
+
import('@helpers/share-link-coordinator/runtime'),
|
|
592
|
+
import('../models/share-link-constraints'),
|
|
593
|
+
import('@helpers/share-link-policy/production'),
|
|
594
|
+
import('@accesslayer/share-link/list'),
|
|
595
|
+
]);
|
|
596
|
+
|
|
597
|
+
const clientConfig = clientModule.resolveShareContentClientConfig({
|
|
598
|
+
enabled: true,
|
|
599
|
+
origin: config.origin,
|
|
600
|
+
namespace: config.namespace,
|
|
601
|
+
audience: config.audience,
|
|
602
|
+
signerDid: getServerDidWebDID(),
|
|
603
|
+
allowInsecureLoopback: config.allowInsecureLoopback,
|
|
604
|
+
signer: createDidWebLearnCardTokenSigner(),
|
|
605
|
+
});
|
|
606
|
+
|
|
607
|
+
if (!clientConfig.enabled) {
|
|
608
|
+
// Fail the build so the retryable lazy initializer does not cache it and
|
|
609
|
+
// a later request retries once the wiring is corrected.
|
|
610
|
+
throw new Error('share-content client configuration is invalid');
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
const client = clientModule.createShareContentClient(clientConfig);
|
|
614
|
+
const policyResolver = createShareLinkPolicyResolver(createProductionShareLinkPolicySource());
|
|
615
|
+
const coordinator = runtimeModule.createRuntimeShareLinkCoordinator({
|
|
616
|
+
client,
|
|
617
|
+
leaseOwner: 'brain-share-link-owner-api',
|
|
618
|
+
policyResolver,
|
|
619
|
+
});
|
|
620
|
+
const recovery = runtimeModule.createRuntimeRecoveryRunnerDependencies({
|
|
621
|
+
client,
|
|
622
|
+
claimant: 'brain-share-link-owner-api',
|
|
623
|
+
namespace: config.namespace,
|
|
624
|
+
});
|
|
625
|
+
|
|
626
|
+
await ensureShareLinkConstraints();
|
|
627
|
+
|
|
628
|
+
return {
|
|
629
|
+
namespace: config.namespace,
|
|
630
|
+
coordinator,
|
|
631
|
+
recovery,
|
|
632
|
+
listShareLinks: listRepository.listShareLinks,
|
|
633
|
+
enforceOwnerWriteRateLimit: ownerProfileId =>
|
|
634
|
+
enforceOwnerShareWriteRateLimit(config.namespace, ownerProfileId),
|
|
635
|
+
resolveViewCountingEligibility: async ownerProfileId =>
|
|
636
|
+
(await policyResolver.resolve(ownerProfileId)).viewCountingEnabled,
|
|
637
|
+
};
|
|
638
|
+
};
|
|
639
|
+
|
|
640
|
+
export const shareLinksRouter = createShareLinksRouter(getProductionDependencies);
|
package/src/routes/skills.ts
CHANGED
|
@@ -241,7 +241,7 @@ export const skillsRouter = t.router({
|
|
|
241
241
|
|
|
242
242
|
if (rateCount > SEMANTIC_SEARCH_RATE_LIMIT_PER_MIN) {
|
|
243
243
|
throw new TRPCError({
|
|
244
|
-
code: 'TOO_MANY_REQUESTS'
|
|
244
|
+
code: 'TOO_MANY_REQUESTS',
|
|
245
245
|
message: `Rate limit exceeded: max ${SEMANTIC_SEARCH_RATE_LIMIT_PER_MIN} semantic searches per minute`,
|
|
246
246
|
});
|
|
247
247
|
}
|