@learncard/network-brain-service 4.1.3 → 5.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.env.example +14 -0
- package/CHANGELOG.md +143 -0
- package/README.md +27 -1
- package/bootstrapLambda.test.ts +70 -32
- package/config/config.dev.json +1 -0
- package/config/config.example.json +2 -1
- package/config/config.production.json +1 -0
- package/config/config.scouts.dev.json +1 -0
- package/config/config.scouts.production.json +1 -0
- package/consentFlowDeadlineLambda.test.ts +108 -0
- package/consentFlowDeadlineLambda.ts +18 -0
- package/consentFlowDeadlineLambdaApp.ts +14 -0
- package/dist/accesslayer/boost/create.d.ts +8 -2
- package/dist/accesslayer/boost/create.d.ts.map +1 -1
- package/dist/accesslayer/campaign/audience.d.ts +4 -4
- package/dist/accesslayer/campaign/audience.d.ts.map +1 -1
- package/dist/accesslayer/campaign/create.d.ts +1 -1
- package/dist/accesslayer/campaign/create.d.ts.map +1 -1
- package/dist/accesslayer/campaign/delivery.d.ts +5 -0
- package/dist/accesslayer/campaign/delivery.d.ts.map +1 -0
- package/dist/accesslayer/campaign/dispatch.d.ts +5 -0
- package/dist/accesslayer/campaign/dispatch.d.ts.map +1 -0
- package/dist/accesslayer/campaign/dispatch.test.d.ts +2 -0
- package/dist/accesslayer/campaign/dispatch.test.d.ts.map +1 -0
- package/dist/accesslayer/campaign/email.d.ts +10 -0
- package/dist/accesslayer/campaign/email.d.ts.map +1 -0
- package/dist/accesslayer/campaign/email.test.d.ts +2 -0
- package/dist/accesslayer/campaign/email.test.d.ts.map +1 -0
- package/dist/accesslayer/campaign/preferences.d.ts +5 -0
- package/dist/accesslayer/campaign/preferences.d.ts.map +1 -0
- package/dist/accesslayer/campaign/read.d.ts.map +1 -1
- package/dist/accesslayer/campaign/test.d.ts +5 -0
- package/dist/accesslayer/campaign/test.d.ts.map +1 -0
- package/dist/accesslayer/campaign/test.test.d.ts +2 -0
- package/dist/accesslayer/campaign/test.test.d.ts.map +1 -0
- package/dist/accesslayer/campaign/update.d.ts.map +1 -1
- package/dist/accesslayer/consentflowcontract/create.d.ts.map +1 -1
- package/dist/accesslayer/consentflowcontract/delete.d.ts +1 -1
- package/dist/accesslayer/consentflowcontract/delete.d.ts.map +1 -1
- package/dist/accesslayer/consentflowcontract/read.d.ts.map +1 -1
- package/dist/accesslayer/consentflowcontract/relationships/create.d.ts +2 -1
- package/dist/accesslayer/consentflowcontract/relationships/create.d.ts.map +1 -1
- package/dist/accesslayer/consentflowcontract/relationships/read.d.ts +19 -4
- package/dist/accesslayer/consentflowcontract/relationships/read.d.ts.map +1 -1
- package/dist/accesslayer/consentflowcontract/relationships/update.d.ts +6 -1
- package/dist/accesslayer/consentflowcontract/relationships/update.d.ts.map +1 -1
- package/dist/accesslayer/consentflowcontract/versions.d.ts +68 -0
- package/dist/accesslayer/consentflowcontract/versions.d.ts.map +1 -0
- package/dist/accesslayer/credential/create.d.ts +3 -1
- package/dist/accesslayer/credential/create.d.ts.map +1 -1
- package/dist/accesslayer/credential/delete.d.ts +1 -0
- package/dist/accesslayer/credential/delete.d.ts.map +1 -1
- package/dist/accesslayer/credential/relationships/create.d.ts +15 -3
- package/dist/accesslayer/credential/relationships/create.d.ts.map +1 -1
- package/dist/accesslayer/credential/relationships/read.d.ts +2 -0
- package/dist/accesslayer/credential/relationships/read.d.ts.map +1 -1
- package/dist/accesslayer/credential/relationships/update.d.ts.map +1 -1
- package/dist/accesslayer/credential-refresh/read.d.ts +1 -0
- 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-credential/create.d.ts +1 -0
- package/dist/accesslayer/inbox-credential/create.d.ts.map +1 -1
- package/dist/accesslayer/inbox-credential/refresh.d.ts +7 -0
- package/dist/accesslayer/inbox-credential/refresh.d.ts.map +1 -1
- package/dist/app.d.ts +11 -1
- package/dist/app.d.ts.map +1 -1
- package/dist/cache/app-store.caches.d.ts +61 -1
- package/dist/cache/app-store.caches.d.ts.map +1 -1
- package/dist/campaign-unsubscribe.d.ts +7 -0
- package/dist/campaign-unsubscribe.d.ts.map +1 -0
- package/dist/campaign-unsubscribe.test.d.ts +2 -0
- package/dist/campaign-unsubscribe.test.d.ts.map +1 -0
- package/dist/config/environment.d.ts +42 -0
- package/dist/config/environment.d.ts.map +1 -1
- package/dist/config/stageConfig.d.ts +4 -0
- package/dist/config/stageConfig.d.ts.map +1 -1
- package/dist/credential-refresh.d.ts.map +1 -1
- package/dist/helpers/boost.helpers.d.ts +4 -1
- package/dist/helpers/boost.helpers.d.ts.map +1 -1
- package/dist/helpers/campaign-app-origin.helpers.d.ts +2 -0
- package/dist/helpers/campaign-app-origin.helpers.d.ts.map +1 -0
- package/dist/helpers/campaign-email.helpers.d.ts +13 -0
- package/dist/helpers/campaign-email.helpers.d.ts.map +1 -0
- package/dist/helpers/campaign-notifications.helpers.d.ts +7 -0
- package/dist/helpers/campaign-notifications.helpers.d.ts.map +1 -0
- package/dist/helpers/campaign-queue.helpers.d.ts +7 -0
- package/dist/helpers/campaign-queue.helpers.d.ts.map +1 -0
- package/dist/helpers/campaign-queue.helpers.test.d.ts +2 -0
- package/dist/helpers/campaign-queue.helpers.test.d.ts.map +1 -0
- package/dist/helpers/campaign-unsubscribe-page.d.ts +2 -0
- package/dist/helpers/campaign-unsubscribe-page.d.ts.map +1 -0
- package/dist/helpers/campaign-unsubscribe.helpers.d.ts +7 -0
- package/dist/helpers/campaign-unsubscribe.helpers.d.ts.map +1 -0
- package/dist/helpers/campaign-webhook.helpers.test.d.ts +2 -0
- package/dist/helpers/campaign-webhook.helpers.test.d.ts.map +1 -0
- package/dist/helpers/consentflow-deadline.helpers.d.ts +5 -0
- package/dist/helpers/consentflow-deadline.helpers.d.ts.map +1 -0
- package/dist/helpers/consentflow-legacy-migration.helpers.d.ts +2 -0
- package/dist/helpers/consentflow-legacy-migration.helpers.d.ts.map +1 -0
- package/dist/helpers/contract-events.helpers.d.ts.map +1 -1
- package/dist/helpers/contract.helpers.d.ts +1 -1
- package/dist/helpers/contract.helpers.d.ts.map +1 -1
- package/dist/helpers/credential-display.helpers.d.ts +5 -0
- package/dist/helpers/credential-display.helpers.d.ts.map +1 -0
- package/dist/helpers/credential-display.helpers.test.d.ts +2 -0
- package/dist/helpers/credential-display.helpers.test.d.ts.map +1 -0
- package/dist/helpers/credential-refresh-email.helpers.d.ts.map +1 -1
- package/dist/helpers/credential-refresh-materiality.helpers.d.ts +4 -0
- package/dist/helpers/credential-refresh-materiality.helpers.d.ts.map +1 -1
- package/dist/helpers/credential-refresh.helpers.d.ts +30 -1
- package/dist/helpers/credential-refresh.helpers.d.ts.map +1 -1
- package/dist/helpers/credential-status-mutation.helpers.d.ts +5 -0
- package/dist/helpers/credential-status-mutation.helpers.d.ts.map +1 -0
- package/dist/helpers/credential-status-mutation.helpers.test.d.ts +2 -0
- package/dist/helpers/credential-status-mutation.helpers.test.d.ts.map +1 -0
- package/dist/helpers/credential.helpers.d.ts +1 -1
- package/dist/helpers/credential.helpers.d.ts.map +1 -1
- package/dist/helpers/inbox-refresh.helpers.d.ts +4 -2
- package/dist/helpers/inbox-refresh.helpers.d.ts.map +1 -1
- package/dist/helpers/inbox.helpers.d.ts +1 -0
- package/dist/helpers/inbox.helpers.d.ts.map +1 -1
- package/dist/helpers/learnCard.helpers.d.ts +1 -0
- package/dist/helpers/learnCard.helpers.d.ts.map +1 -1
- package/dist/helpers/managed-status.helpers.d.ts +7 -0
- package/dist/helpers/managed-status.helpers.d.ts.map +1 -0
- package/dist/helpers/managed-status.helpers.test.d.ts +2 -0
- package/dist/helpers/managed-status.helpers.test.d.ts.map +1 -0
- package/dist/helpers/notification-delivery-boundary.helpers.test.d.ts +2 -0
- package/dist/helpers/notification-delivery-boundary.helpers.test.d.ts.map +1 -0
- package/dist/helpers/notification-webhook-fetch.helpers.d.ts +13 -0
- package/dist/helpers/notification-webhook-fetch.helpers.d.ts.map +1 -0
- package/dist/helpers/notification-webhook-fetch.helpers.test.d.ts +2 -0
- package/dist/helpers/notification-webhook-fetch.helpers.test.d.ts.map +1 -0
- package/dist/helpers/notificationQueue.helpers.d.ts.map +1 -1
- package/dist/helpers/notifications.helpers.d.ts.map +1 -1
- package/dist/helpers/refresh-send-intent.helpers.d.ts +13 -5
- package/dist/helpers/refresh-send-intent.helpers.d.ts.map +1 -1
- package/dist/helpers/refresh-send-intent.helpers.test.d.ts +2 -0
- package/dist/helpers/refresh-send-intent.helpers.test.d.ts.map +1 -0
- package/dist/helpers/send-contract.helpers.d.ts +11 -0
- package/dist/helpers/send-contract.helpers.d.ts.map +1 -0
- package/dist/helpers/status-list.helpers.d.ts +12 -1
- package/dist/helpers/status-list.helpers.d.ts.map +1 -1
- package/dist/index.js +2 -2
- package/dist/models/ConsentFlowContract.d.ts.map +1 -1
- package/dist/models/ConsentFlowContractVersion.d.ts +12 -0
- package/dist/models/ConsentFlowContractVersion.d.ts.map +1 -0
- package/dist/models/ConsentFlowTerms.d.ts.map +1 -1
- package/dist/models/Credential.d.ts +2 -0
- package/dist/models/Credential.d.ts.map +1 -1
- package/dist/models/CredentialRefresh.d.ts +5 -0
- package/dist/models/CredentialRefresh.d.ts.map +1 -1
- package/dist/models/Integration.d.ts +1 -1
- package/dist/models/Profile.d.ts.map +1 -1
- package/dist/models/campaign-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/openapi.d.ts.map +1 -1
- package/dist/publish-metafile.json +1 -1
- package/dist/routes/activity.d.ts +15 -5
- package/dist/routes/activity.d.ts.map +1 -1
- package/dist/routes/app-store.d.ts +15 -5
- package/dist/routes/app-store.d.ts.map +1 -1
- package/dist/routes/auth-grants.d.ts +12 -2
- package/dist/routes/auth-grants.d.ts.map +1 -1
- package/dist/routes/bench.d.ts +11 -1
- package/dist/routes/bench.d.ts.map +1 -1
- package/dist/routes/boosts.d.ts +70 -15
- package/dist/routes/boosts.d.ts.map +1 -1
- package/dist/routes/campaigns.d.ts +183 -51
- package/dist/routes/campaigns.d.ts.map +1 -1
- package/dist/routes/claim-hooks.d.ts +11 -1
- package/dist/routes/claim-hooks.d.ts.map +1 -1
- package/dist/routes/contact-methods.d.ts +11 -1
- package/dist/routes/contact-methods.d.ts.map +1 -1
- package/dist/routes/contracts.d.ts +374 -8
- package/dist/routes/contracts.d.ts.map +1 -1
- package/dist/routes/credential-input-privacy.test.d.ts +2 -0
- package/dist/routes/credential-input-privacy.test.d.ts.map +1 -0
- package/dist/routes/credential-refreshes.d.ts +210 -1
- package/dist/routes/credential-refreshes.d.ts.map +1 -1
- package/dist/routes/credential-share-previews.d.ts +11 -1
- package/dist/routes/credential-share-previews.d.ts.map +1 -1
- package/dist/routes/credentials.d.ts +132 -3
- package/dist/routes/credentials.d.ts.map +1 -1
- package/dist/routes/did-metadata.d.ts +11 -1
- package/dist/routes/did-metadata.d.ts.map +1 -1
- package/dist/routes/federation.d.ts +11 -1
- package/dist/routes/federation.d.ts.map +1 -1
- package/dist/routes/inbox.d.ts +54 -19
- package/dist/routes/inbox.d.ts.map +1 -1
- package/dist/routes/index.d.ts +100 -1
- package/dist/routes/index.d.ts.map +1 -1
- package/dist/routes/integrations.d.ts +14 -4
- package/dist/routes/integrations.d.ts.map +1 -1
- package/dist/routes/presentations.d.ts +11 -1
- package/dist/routes/presentations.d.ts.map +1 -1
- package/dist/routes/profile-manager.d.ts +11 -1
- package/dist/routes/profile-manager.d.ts.map +1 -1
- package/dist/routes/profiles.d.ts +11 -1
- package/dist/routes/profiles.d.ts.map +1 -1
- package/dist/routes/public-share-links.d.ts +22 -2
- package/dist/routes/public-share-links.d.ts.map +1 -1
- package/dist/routes/share-links.d.ts +36 -16
- package/dist/routes/share-links.d.ts.map +1 -1
- package/dist/routes/skill-frameworks.d.ts +11 -1
- package/dist/routes/skill-frameworks.d.ts.map +1 -1
- package/dist/routes/skills.d.ts +11 -1
- package/dist/routes/skills.d.ts.map +1 -1
- package/dist/routes/storage.d.ts +37 -23
- package/dist/routes/storage.d.ts.map +1 -1
- package/dist/routes/test.d.ts +79 -3
- package/dist/routes/test.d.ts.map +1 -1
- package/dist/routes/utilities.d.ts +11 -1
- package/dist/routes/utilities.d.ts.map +1 -1
- package/dist/routes/workflows.d.ts +11 -1
- package/dist/routes/workflows.d.ts.map +1 -1
- package/dist/services/delivery/adapters/postmark.adapter.d.ts +3 -0
- package/dist/services/delivery/adapters/postmark.adapter.d.ts.map +1 -1
- package/dist/services/delivery/delivery.service.d.ts +10 -0
- package/dist/services/delivery/delivery.service.d.ts.map +1 -1
- package/dist/types/activity.d.ts +3 -3
- package/dist/types/app-manifest.d.ts +10 -10
- package/dist/types/campaign.d.ts +13 -1
- package/dist/types/campaign.d.ts.map +1 -1
- package/dist/types/consentflowcontract.d.ts +175 -7
- package/dist/types/consentflowcontract.d.ts.map +1 -1
- package/dist/types/credential-refresh.d.ts +27 -0
- package/dist/types/credential-refresh.d.ts.map +1 -1
- package/dist/types/credential.d.ts +5 -0
- package/dist/types/credential.d.ts.map +1 -1
- package/dist/types/integration.d.ts +3 -3
- package/lambda.ts +44 -14
- package/lambdaApp.ts +20 -0
- package/package.json +20 -19
- package/project.json +5 -0
- package/scripts/migrate-consent-flow-versions.ts +9 -0
- package/scripts/report-send-template-exposure.ts +47 -0
- package/scripts/verify-python-contracts.ts +16 -1
- package/serverless-local.yml +15 -2
- package/serverless.function-env.cjs +4 -0
- package/serverless.local.yml +12 -0
- package/serverless.yml +48 -3
- package/src/accesslayer/boost/create.ts +62 -7
- package/src/accesslayer/campaign/README.md +275 -6
- package/src/accesslayer/campaign/audience.ts +39 -7
- package/src/accesslayer/campaign/create.ts +4 -2
- package/src/accesslayer/campaign/delivery.ts +66 -0
- package/src/accesslayer/campaign/dispatch.test.ts +59 -0
- package/src/accesslayer/campaign/dispatch.ts +172 -0
- package/src/accesslayer/campaign/email.test.ts +73 -0
- package/src/accesslayer/campaign/email.ts +91 -0
- package/src/accesslayer/campaign/preferences.ts +53 -0
- package/src/accesslayer/campaign/read.ts +3 -1
- package/src/accesslayer/campaign/state.ts +1 -1
- package/src/accesslayer/campaign/test.test.ts +31 -0
- package/src/accesslayer/campaign/test.ts +75 -0
- package/src/accesslayer/campaign/update.ts +15 -4
- package/src/accesslayer/consentflowcontract/create.ts +20 -1
- package/src/accesslayer/consentflowcontract/delete.ts +121 -3
- package/src/accesslayer/consentflowcontract/read.ts +15 -9
- package/src/accesslayer/consentflowcontract/relationships/create.ts +70 -36
- package/src/accesslayer/consentflowcontract/relationships/read.ts +305 -73
- package/src/accesslayer/consentflowcontract/relationships/smartResume.ts +1 -1
- package/src/accesslayer/consentflowcontract/relationships/update.ts +269 -98
- package/src/accesslayer/consentflowcontract/versions.ts +790 -0
- package/src/accesslayer/credential/create.ts +33 -9
- package/src/accesslayer/credential/delete.ts +45 -0
- package/src/accesslayer/credential/relationships/create.ts +102 -47
- package/src/accesslayer/credential/relationships/update.ts +8 -116
- package/src/accesslayer/credential-refresh/read.ts +26 -2
- package/src/accesslayer/credential-refresh/update.ts +6 -2
- package/src/accesslayer/inbox-credential/create.ts +7 -1
- package/src/accesslayer/inbox-credential/refresh.ts +14 -4
- package/src/campaign-unsubscribe.test.ts +158 -0
- package/src/campaign-unsubscribe.ts +86 -0
- package/src/config/environment.test.ts +52 -0
- package/src/config/environment.ts +58 -0
- package/src/config/serverlessFunctionEnv.test.ts +59 -0
- package/src/credential-refresh.ts +1 -0
- package/src/docker-entry.ts +26 -1
- package/src/helpers/boost-storage.helpers.test.ts +107 -41
- package/src/helpers/boost.helpers.ts +88 -40
- package/src/helpers/campaign-app-origin.helpers.ts +25 -0
- package/src/helpers/campaign-email.helpers.ts +113 -0
- package/src/helpers/campaign-notifications.helpers.ts +127 -0
- package/src/helpers/campaign-queue.helpers.test.ts +442 -0
- package/src/helpers/campaign-queue.helpers.ts +122 -0
- package/src/helpers/campaign-unsubscribe-page.ts +107 -0
- package/src/helpers/campaign-unsubscribe.helpers.ts +56 -0
- package/src/helpers/campaign-webhook.helpers.test.ts +137 -0
- package/src/helpers/consentflow-deadline.helpers.ts +22 -0
- package/src/helpers/consentflow-legacy-migration.helpers.ts +179 -0
- package/src/helpers/contract-events.helpers.ts +35 -3
- package/src/helpers/contract-requests.helpers.ts +1 -1
- package/src/helpers/contract.helpers.ts +5 -1
- package/src/helpers/credential-display.helpers.test.ts +68 -0
- package/src/helpers/credential-display.helpers.ts +15 -0
- package/src/helpers/credential-refresh-email.helpers.ts +5 -12
- package/src/helpers/credential-refresh-materiality.helpers.test.ts +138 -1
- package/src/helpers/credential-refresh-materiality.helpers.ts +62 -1
- package/src/helpers/credential-refresh-proof.helpers.test.ts +1 -1
- package/src/helpers/credential-refresh-proof.helpers.ts +2 -2
- package/src/helpers/credential-refresh.helpers.ts +1166 -376
- package/src/helpers/credential-status-mutation.helpers.test.ts +155 -0
- package/src/helpers/credential-status-mutation.helpers.ts +166 -0
- package/src/helpers/credential.helpers.ts +19 -11
- package/src/helpers/finalize-inbox.helpers.ts +1 -1
- package/src/helpers/inbox-refresh.helpers.ts +34 -8
- package/src/helpers/inbox.helpers.ts +2 -0
- package/src/helpers/learnCard.helpers.ts +23 -0
- package/src/helpers/managed-status.helpers.test.ts +101 -0
- package/src/helpers/managed-status.helpers.ts +147 -0
- package/src/helpers/notification-delivery-boundary.helpers.test.ts +114 -0
- package/src/helpers/notification-webhook-fetch.helpers.test.ts +228 -0
- package/src/helpers/notification-webhook-fetch.helpers.ts +166 -0
- package/src/helpers/notificationQueue.helpers.ts +5 -0
- package/src/helpers/notifications.helpers.ts +19 -12
- package/src/helpers/refresh-send-intent.helpers.test.ts +60 -0
- package/src/helpers/refresh-send-intent.helpers.ts +81 -18
- package/src/helpers/send-contract.helpers.ts +36 -0
- package/src/helpers/status-list.helpers.test.ts +29 -12
- package/src/helpers/status-list.helpers.ts +329 -20
- package/src/models/ConsentFlowContract.ts +3 -0
- package/src/models/ConsentFlowContractVersion.ts +50 -0
- package/src/models/ConsentFlowTerms.ts +4 -0
- package/src/models/Credential.ts +2 -0
- package/src/models/CredentialRefresh.ts +2 -0
- package/src/models/Profile.ts +2 -0
- package/src/models/campaign-constraints.ts +13 -8
- package/src/models/index.ts +2 -0
- package/src/openapi.ts +21 -0
- package/src/routes/app-store.ts +2 -1
- package/src/routes/boosts.ts +219 -137
- package/src/routes/campaigns.ts +98 -19
- package/src/routes/contracts.ts +433 -115
- package/src/routes/credential-input-privacy.test.ts +92 -0
- package/src/routes/credential-refreshes.ts +273 -2
- package/src/routes/federation.ts +7 -2
- package/src/routes/inbox.ts +6 -1
- package/src/routes/index.ts +31 -2
- package/src/routes/workflows.ts +61 -46
- package/src/services/delivery/adapters/postmark.adapter.test.ts +70 -1
- package/src/services/delivery/adapters/postmark.adapter.ts +84 -11
- package/src/services/delivery/delivery.service.ts +4 -0
- package/src/types/campaign.ts +14 -1
- package/src/types/consentflowcontract.ts +43 -2
- package/src/types/credential-refresh.ts +11 -1
- package/src/types/credential.ts +2 -0
- package/test/boost-status-cleanup.spec.ts +123 -0
- package/test/campaigns-email.unit.spec.ts +268 -0
- package/test/campaigns.spec.ts +804 -30
- package/test/consent-autoboost-idempotency.spec.ts +432 -0
- package/test/consent-data-boundaries.spec.ts +322 -26
- package/test/consent-legacy-migration.spec.ts +315 -0
- package/test/consent-recipients.spec.ts +7 -7
- package/test/consent-version-updates.spec.ts +761 -0
- package/test/consentflow.spec.ts +698 -226
- package/test/contract-requests.spec.ts +55 -9
- package/test/credential-refresh-allocation.spec.ts +212 -14
- package/test/credential-refresh-email.spec.ts +6 -10
- package/test/credential-refresh-endpoint.spec.ts +3 -0
- package/test/credential-refresh-publication.spec.ts +52 -15
- package/test/credential-storage.neo4j.spec.ts +33 -6
- package/test/helpers/legacy-autoboost.ts +48 -0
- package/test/holder-mode-send.spec.ts +710 -0
- package/test/inbox-refresh.spec.ts +105 -0
- package/test/inbox-security.e2e.ts +18 -22
- package/test/managed-status-revocation.spec.ts +970 -0
- package/test/notifications-worker.spec.ts +31 -25
- package/test/refresh-send.spec.ts +334 -5
- package/test/smart-resume-consent.spec.ts +1 -1
- package/test/verification/campaigns.ts +40 -0
- package/vitest.campaigns.config.ts +2 -0
- package/vitest.config.ts +14 -0
|
@@ -1,6 +1,9 @@
|
|
|
1
|
-
import { environment } from '@environment';
|
|
1
|
+
import { environment, getCredentialRefreshRuntimeEnvironment } from '@environment';
|
|
2
2
|
import { gzipSync, gunzipSync } from 'node:zlib';
|
|
3
|
+
import { isDeepStrictEqual } from 'node:util';
|
|
4
|
+
import type { Transaction } from 'neo4j-driver';
|
|
3
5
|
import { v4 as uuid } from 'uuid';
|
|
6
|
+
import { TRPCError } from '@trpc/server';
|
|
4
7
|
import type {
|
|
5
8
|
AllocatedBitstringStatusListEntry,
|
|
6
9
|
BitstringStatusListEntry,
|
|
@@ -20,10 +23,17 @@ import {
|
|
|
20
23
|
} from '@learncard/helpers';
|
|
21
24
|
|
|
22
25
|
import { neogma } from '@instance';
|
|
23
|
-
import { Credential } from '@models';
|
|
24
26
|
import type { StatusListPurpose, StatusListType } from '@models';
|
|
25
27
|
import { getDidWebLearnCard, getLearnCard } from '@helpers/learnCard.helpers';
|
|
26
28
|
|
|
29
|
+
/** Resolve original deliveries and versions through anchored refresh lookups. */
|
|
30
|
+
export const CREDENTIAL_STATUS_ROOT_QUERY = `MATCH (credential:Credential {id: $credentialId})
|
|
31
|
+
OPTIONAL MATCH (rootRefresh:CredentialRefresh)-[:ROOT]->(credential)
|
|
32
|
+
OPTIONAL MATCH (versionRefresh:CredentialRefresh {refreshId: credential.refreshId})
|
|
33
|
+
WITH credential, coalesce(rootRefresh, versionRefresh) AS refresh
|
|
34
|
+
OPTIONAL MATCH (refresh)-[:ROOT]->(managedRoot:Credential)
|
|
35
|
+
WITH credential, refresh, coalesce(managedRoot, credential) AS root`;
|
|
36
|
+
|
|
27
37
|
const hasToNumber = (value: unknown): value is { toNumber: () => number } =>
|
|
28
38
|
!!value &&
|
|
29
39
|
typeof value === 'object' &&
|
|
@@ -35,6 +45,111 @@ const asNumber = (value: unknown): number => {
|
|
|
35
45
|
return Number(value);
|
|
36
46
|
};
|
|
37
47
|
|
|
48
|
+
/** Reserve local coordinates for an ordinary delivery under the same locks as managed binding. */
|
|
49
|
+
export const reserveCredentialStatusEntries = async (
|
|
50
|
+
entries: BitstringStatusListEntry[],
|
|
51
|
+
ownerProfileId: string,
|
|
52
|
+
credentialId: string,
|
|
53
|
+
transaction: Transaction,
|
|
54
|
+
options: { lenient?: boolean; credential?: VC | UnsignedVC } = {}
|
|
55
|
+
): Promise<void> => {
|
|
56
|
+
const domain = getCredentialRefreshRuntimeEnvironment().DOMAIN_NAME || 'localhost%3A3000';
|
|
57
|
+
for (const entry of [...entries].sort((a, b) =>
|
|
58
|
+
a.statusListCredential.localeCompare(b.statusListCredential)
|
|
59
|
+
)) {
|
|
60
|
+
const result = await transaction.run(
|
|
61
|
+
`MATCH (list:BitstringStatusList {statusListCredential: $url})
|
|
62
|
+
SET list._statusBindingLock = true REMOVE list._statusBindingLock
|
|
63
|
+
WITH list
|
|
64
|
+
OPTIONAL MATCH (list)-[:HAS_STATUS_BINDING]->(binding:CredentialStatusBinding {index: $index})
|
|
65
|
+
RETURN list, binding.refreshId AS refreshId, binding.credentialId AS credentialId`,
|
|
66
|
+
{ url: entry.statusListCredential, index: String(entry.statusListIndex) }
|
|
67
|
+
);
|
|
68
|
+
const row = result.records[0];
|
|
69
|
+
const list = row ? nodeToStatusList(row.get('list')) : null;
|
|
70
|
+
if (!list) {
|
|
71
|
+
if (
|
|
72
|
+
entry.statusListCredential.startsWith(
|
|
73
|
+
`${getStatusListBaseUrl(domain)}/status-lists/`
|
|
74
|
+
)
|
|
75
|
+
) {
|
|
76
|
+
throw new TRPCError({
|
|
77
|
+
code: 'BAD_REQUEST',
|
|
78
|
+
message: 'Local status list does not exist',
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
continue; // External status lists are not managed by this network.
|
|
82
|
+
}
|
|
83
|
+
// Forwarding does not confer authority over another issuer's local list.
|
|
84
|
+
if (options.lenient && list.ownerProfileId !== ownerProfileId) continue;
|
|
85
|
+
const index = Number(entry.statusListIndex);
|
|
86
|
+
if (
|
|
87
|
+
entry.statusListCredential !== getStatusListCredentialUrl(list.id, domain) ||
|
|
88
|
+
list.ownerProfileId !== ownerProfileId ||
|
|
89
|
+
list.statusPurpose !== entry.statusPurpose ||
|
|
90
|
+
!Number.isSafeInteger(index) ||
|
|
91
|
+
index < 0 ||
|
|
92
|
+
index >= list.size ||
|
|
93
|
+
index >= list.nextIndex ||
|
|
94
|
+
(typeof entry.statusListIndex === 'string' && String(index) !== entry.statusListIndex)
|
|
95
|
+
) {
|
|
96
|
+
throw new TRPCError({
|
|
97
|
+
code: 'BAD_REQUEST',
|
|
98
|
+
message: 'Invalid local credential status coordinates',
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
if (
|
|
102
|
+
row?.get('refreshId') ||
|
|
103
|
+
(row?.get('credentialId') && row.get('credentialId') !== credentialId)
|
|
104
|
+
) {
|
|
105
|
+
const boundId: unknown = row?.get('credentialId');
|
|
106
|
+
if (
|
|
107
|
+
options.lenient &&
|
|
108
|
+
!row?.get('refreshId') &&
|
|
109
|
+
typeof boundId === 'string' &&
|
|
110
|
+
options.credential?.proof &&
|
|
111
|
+
(!Array.isArray(options.credential.proof) || options.credential.proof.length > 0)
|
|
112
|
+
) {
|
|
113
|
+
const existing = await transaction.run(
|
|
114
|
+
'MATCH (credential:Credential {id: $id}) RETURN credential.credential AS body',
|
|
115
|
+
{ id: boundId }
|
|
116
|
+
);
|
|
117
|
+
const body: unknown = existing.records[0]?.get('body');
|
|
118
|
+
if (
|
|
119
|
+
typeof body === 'string' &&
|
|
120
|
+
isDeepStrictEqual(
|
|
121
|
+
JSON.parse(body),
|
|
122
|
+
JSON.parse(JSON.stringify(options.credential))
|
|
123
|
+
)
|
|
124
|
+
) {
|
|
125
|
+
await transaction.run(
|
|
126
|
+
`MATCH (:BitstringStatusList {statusListCredential: $url})-[:HAS_STATUS_BINDING]->(binding:CredentialStatusBinding {index: $index})
|
|
127
|
+
WITH binding, coalesce(binding.credentialIds, [binding.credentialId]) AS ids
|
|
128
|
+
SET binding.credentialIds = CASE WHEN $credentialId IN ids THEN ids ELSE ids + $credentialId END`,
|
|
129
|
+
{
|
|
130
|
+
url: entry.statusListCredential,
|
|
131
|
+
index: String(entry.statusListIndex),
|
|
132
|
+
credentialId,
|
|
133
|
+
}
|
|
134
|
+
);
|
|
135
|
+
continue; // Retain every delivery under the list lock, including legacy bindings.
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
throw new TRPCError({
|
|
139
|
+
code: 'BAD_REQUEST',
|
|
140
|
+
message: 'Status list index is already bound to another credential',
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
await transaction.run(
|
|
144
|
+
`MATCH (list:BitstringStatusList {statusListCredential: $url})
|
|
145
|
+
MERGE (list)-[:HAS_STATUS_BINDING]->(binding:CredentialStatusBinding {index: $index})
|
|
146
|
+
SET binding.credentialId = $credentialId,
|
|
147
|
+
binding.credentialIds = coalesce(binding.credentialIds, [$credentialId])`,
|
|
148
|
+
{ url: entry.statusListCredential, index: String(entry.statusListIndex), credentialId }
|
|
149
|
+
);
|
|
150
|
+
}
|
|
151
|
+
};
|
|
152
|
+
|
|
38
153
|
const nodeToStatusList = (node: unknown): StatusListType | null => {
|
|
39
154
|
const properties = (node as { properties?: Record<string, unknown> } | undefined)?.properties;
|
|
40
155
|
if (!properties) return null;
|
|
@@ -338,15 +453,25 @@ export const appendBitstringStatusListEntries = async (
|
|
|
338
453
|
|
|
339
454
|
export const setStatusListEntryBit = async (
|
|
340
455
|
entry: BitstringStatusListEntry,
|
|
341
|
-
value: boolean
|
|
456
|
+
value: boolean,
|
|
457
|
+
ownerProfileId: string,
|
|
458
|
+
transaction?: Transaction
|
|
342
459
|
): Promise<boolean> => {
|
|
343
460
|
const statusListIndex = Number(entry.statusListIndex);
|
|
344
461
|
|
|
345
|
-
if (
|
|
346
|
-
|
|
462
|
+
if (
|
|
463
|
+
!Number.isSafeInteger(statusListIndex) ||
|
|
464
|
+
statusListIndex < 0 ||
|
|
465
|
+
(typeof entry.statusListIndex === 'string' &&
|
|
466
|
+
String(statusListIndex) !== entry.statusListIndex)
|
|
467
|
+
) {
|
|
468
|
+
throw new TRPCError({
|
|
469
|
+
code: 'BAD_REQUEST',
|
|
470
|
+
message: 'Bitstring status list index is out of range',
|
|
471
|
+
});
|
|
347
472
|
}
|
|
348
473
|
|
|
349
|
-
return neogma.getTransaction(null, async tx => {
|
|
474
|
+
return neogma.getTransaction(transaction ?? null, async tx => {
|
|
350
475
|
const record = (
|
|
351
476
|
await tx.run(
|
|
352
477
|
`MATCH (list:BitstringStatusList {statusListCredential: $statusListCredential})
|
|
@@ -364,8 +489,43 @@ export const setStatusListEntryBit = async (
|
|
|
364
489
|
const list = record ? nodeToStatusList(record.get('list')) : null;
|
|
365
490
|
if (!list) return false;
|
|
366
491
|
|
|
367
|
-
|
|
368
|
-
|
|
492
|
+
const url = new URL(entry.statusListCredential);
|
|
493
|
+
if (
|
|
494
|
+
entry.statusListCredential !==
|
|
495
|
+
getStatusListCredentialUrl(
|
|
496
|
+
list.id,
|
|
497
|
+
getCredentialRefreshRuntimeEnvironment().DOMAIN_NAME || 'localhost%3A3000'
|
|
498
|
+
) ||
|
|
499
|
+
url.pathname !== `/status-lists/${list.id}` ||
|
|
500
|
+
url.search ||
|
|
501
|
+
url.hash ||
|
|
502
|
+
url.username ||
|
|
503
|
+
url.password
|
|
504
|
+
) {
|
|
505
|
+
throw new TRPCError({
|
|
506
|
+
code: 'BAD_REQUEST',
|
|
507
|
+
message: 'Status list URL must be canonical and local',
|
|
508
|
+
});
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
if (list.ownerProfileId !== ownerProfileId) {
|
|
512
|
+
throw new TRPCError({
|
|
513
|
+
code: 'FORBIDDEN',
|
|
514
|
+
message: 'Status list owner does not match the credential',
|
|
515
|
+
});
|
|
516
|
+
}
|
|
517
|
+
if (list.statusPurpose !== entry.statusPurpose) {
|
|
518
|
+
throw new TRPCError({
|
|
519
|
+
code: 'BAD_REQUEST',
|
|
520
|
+
message: 'Status list purpose does not match the credential',
|
|
521
|
+
});
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
if (statusListIndex >= list.size || statusListIndex >= list.nextIndex) {
|
|
525
|
+
throw new TRPCError({
|
|
526
|
+
code: 'BAD_REQUEST',
|
|
527
|
+
message: 'Bitstring status list index is out of range',
|
|
528
|
+
});
|
|
369
529
|
}
|
|
370
530
|
|
|
371
531
|
const bitstring = decodeBitstring(list.encodedList, list.size);
|
|
@@ -397,18 +557,9 @@ export const setCredentialBitstringStatus = async (
|
|
|
397
557
|
statusPurpose: BitstringStatusPurpose,
|
|
398
558
|
value: boolean
|
|
399
559
|
): Promise<boolean> => {
|
|
400
|
-
const credential = await Credential.findOne({ where: { id: credentialId } });
|
|
401
|
-
if (!credential) return false;
|
|
402
|
-
|
|
403
|
-
// Encrypted SA credentials retain only public status coordinates separately.
|
|
404
|
-
// Legacy plaintext/wrapped credentials continue using their embedded entries.
|
|
405
560
|
let statusEntries: BitstringStatusListEntry[];
|
|
406
561
|
try {
|
|
407
|
-
statusEntries =
|
|
408
|
-
credential.statusEntries
|
|
409
|
-
? JSON.parse(credential.statusEntries)
|
|
410
|
-
: getBitstringStatusListEntries(JSON.parse(credential.credential))
|
|
411
|
-
);
|
|
562
|
+
statusEntries = await getStatusEntriesForCredential(credentialId);
|
|
412
563
|
if (
|
|
413
564
|
statusEntries.some(
|
|
414
565
|
entry =>
|
|
@@ -424,14 +575,172 @@ export const setCredentialBitstringStatus = async (
|
|
|
424
575
|
});
|
|
425
576
|
return false;
|
|
426
577
|
}
|
|
427
|
-
|
|
578
|
+
if (statusEntries.length === 0) return false;
|
|
428
579
|
|
|
580
|
+
const owner = (
|
|
581
|
+
await neogma.queryRunner.run(
|
|
582
|
+
`${CREDENTIAL_STATUS_ROOT_QUERY}
|
|
583
|
+
OPTIONAL MATCH (issuer:Profile)-[:CREDENTIAL_SENT]->(root)
|
|
584
|
+
RETURN coalesce(refresh.issuerProfileId, root.statusOwnerProfileId, issuer.profileId) AS owner LIMIT 1`,
|
|
585
|
+
{ credentialId }
|
|
586
|
+
)
|
|
587
|
+
).records[0]?.get('owner');
|
|
588
|
+
if (typeof owner !== 'string') return false;
|
|
589
|
+
const entries = statusEntries.filter(entry => entry.statusPurpose === statusPurpose);
|
|
429
590
|
if (entries.length === 0) return false;
|
|
430
591
|
|
|
431
|
-
const updated = await Promise.all(
|
|
592
|
+
const updated = await Promise.all(
|
|
593
|
+
entries.map(entry => setStatusListEntryBit(entry, value, owner))
|
|
594
|
+
);
|
|
432
595
|
return updated.every(Boolean);
|
|
433
596
|
};
|
|
434
597
|
|
|
598
|
+
/** Public coordinates are shared by every immutable version of a managed credential. */
|
|
599
|
+
export const getStatusEntriesForCredential = async (
|
|
600
|
+
credentialId: string,
|
|
601
|
+
transaction?: Transaction
|
|
602
|
+
): Promise<BitstringStatusListEntry[]> => {
|
|
603
|
+
const query = `${CREDENTIAL_STATUS_ROOT_QUERY}
|
|
604
|
+
RETURN credential.statusEntries AS direct, credential.credential AS body,
|
|
605
|
+
refresh.statusEntries AS entries LIMIT 1`;
|
|
606
|
+
const result = transaction
|
|
607
|
+
? await transaction.run(query, { credentialId })
|
|
608
|
+
: await neogma.queryRunner.run(query, { credentialId });
|
|
609
|
+
const row = result.records[0];
|
|
610
|
+
if (!row) return [];
|
|
611
|
+
const direct: unknown = row.get('direct');
|
|
612
|
+
const body: unknown = row.get('body');
|
|
613
|
+
const directEntries = BitstringStatusListEntryValidator.array().parse(
|
|
614
|
+
typeof direct === 'string'
|
|
615
|
+
? JSON.parse(direct)
|
|
616
|
+
: typeof body === 'string'
|
|
617
|
+
? getBitstringStatusListEntries(JSON.parse(body))
|
|
618
|
+
: []
|
|
619
|
+
);
|
|
620
|
+
if (directEntries.length > 0) return directEntries;
|
|
621
|
+
const entries: unknown = row.get('entries');
|
|
622
|
+
return typeof entries === 'string'
|
|
623
|
+
? BitstringStatusListEntryValidator.array().parse(JSON.parse(entries))
|
|
624
|
+
: [];
|
|
625
|
+
};
|
|
626
|
+
|
|
627
|
+
/** Resolve lifecycle requests for any immutable version to its original delivery. */
|
|
628
|
+
export const getCredentialStatusRootId = async (credentialId: string): Promise<string> => {
|
|
629
|
+
const result = await neogma.queryRunner.run(
|
|
630
|
+
`${CREDENTIAL_STATUS_ROOT_QUERY}
|
|
631
|
+
RETURN root.id AS id LIMIT 1`,
|
|
632
|
+
{ credentialId }
|
|
633
|
+
);
|
|
634
|
+
return String(result.records[0]?.get('id') ?? credentialId);
|
|
635
|
+
};
|
|
636
|
+
|
|
637
|
+
/** Own-host URLs must pass local validation, even with a wrong path, port or scheme. */
|
|
638
|
+
export const isNetworkStatusEntry = (
|
|
639
|
+
entry: BitstringStatusListEntry,
|
|
640
|
+
domain = getCredentialRefreshRuntimeEnvironment().DOMAIN_NAME || 'localhost%3A3000'
|
|
641
|
+
): boolean => {
|
|
642
|
+
const base = getStatusListBaseUrl(domain);
|
|
643
|
+
let url: URL;
|
|
644
|
+
try {
|
|
645
|
+
url = new URL(entry.statusListCredential);
|
|
646
|
+
} catch (cause) {
|
|
647
|
+
throw new TRPCError({
|
|
648
|
+
code: 'BAD_REQUEST',
|
|
649
|
+
message: 'Invalid credential status URL',
|
|
650
|
+
cause,
|
|
651
|
+
});
|
|
652
|
+
}
|
|
653
|
+
return (
|
|
654
|
+
entry.statusListCredential.startsWith(`${base}/status-lists/`) ||
|
|
655
|
+
url.hostname === new URL(base).hostname
|
|
656
|
+
);
|
|
657
|
+
};
|
|
658
|
+
|
|
659
|
+
/** Issuer-facing information, separate from the signed refresh receipt. */
|
|
660
|
+
export const getStatusManagement = (
|
|
661
|
+
entries: BitstringStatusListEntry[],
|
|
662
|
+
domain: string
|
|
663
|
+
): 'network' | 'external' | 'mixed' | 'none' => {
|
|
664
|
+
if (!entries.length) return 'none';
|
|
665
|
+
const local = entries.filter(entry => isNetworkStatusEntry(entry, domain)).length;
|
|
666
|
+
return local === entries.length ? 'network' : local === 0 ? 'external' : 'mixed';
|
|
667
|
+
};
|
|
668
|
+
|
|
669
|
+
/** Validate external shapes and local, issuer-owned, allocated coordinates. */
|
|
670
|
+
export const validateStatusEntries = async (
|
|
671
|
+
entries: BitstringStatusListEntry[],
|
|
672
|
+
ownerProfileId: string,
|
|
673
|
+
domain: string
|
|
674
|
+
): Promise<void> => {
|
|
675
|
+
for (const entry of entries) {
|
|
676
|
+
if (!BitstringStatusListEntryValidator.safeParse(entry).success) {
|
|
677
|
+
throw new TRPCError({
|
|
678
|
+
code: 'BAD_REQUEST',
|
|
679
|
+
message: 'Invalid credential status entry',
|
|
680
|
+
});
|
|
681
|
+
}
|
|
682
|
+
if (!isNetworkStatusEntry(entry, domain)) {
|
|
683
|
+
const index = Number(entry.statusListIndex);
|
|
684
|
+
if (
|
|
685
|
+
new URL(entry.statusListCredential).protocol !== 'https:' ||
|
|
686
|
+
!Number.isSafeInteger(index) ||
|
|
687
|
+
index < 0 ||
|
|
688
|
+
(typeof entry.statusListIndex === 'string' && !/^\d+$/.test(entry.statusListIndex))
|
|
689
|
+
) {
|
|
690
|
+
throw new TRPCError({
|
|
691
|
+
code: 'BAD_REQUEST',
|
|
692
|
+
message: 'Invalid external credential status entry',
|
|
693
|
+
});
|
|
694
|
+
}
|
|
695
|
+
continue;
|
|
696
|
+
}
|
|
697
|
+
const prefix = `${getStatusListBaseUrl(domain)}/status-lists/`;
|
|
698
|
+
const id = entry.statusListCredential.slice(prefix.length);
|
|
699
|
+
if (!entry.statusListCredential.startsWith(prefix)) {
|
|
700
|
+
throw new TRPCError({
|
|
701
|
+
code: 'BAD_REQUEST',
|
|
702
|
+
message: 'Status list URL must be canonical and local',
|
|
703
|
+
});
|
|
704
|
+
}
|
|
705
|
+
if (!/^[a-zA-Z0-9-]+$/.test(id)) {
|
|
706
|
+
throw new TRPCError({
|
|
707
|
+
code: 'BAD_REQUEST',
|
|
708
|
+
message: 'Status list URL must be canonical and local',
|
|
709
|
+
});
|
|
710
|
+
}
|
|
711
|
+
const list = await getStatusListById(id);
|
|
712
|
+
const index = Number(entry.statusListIndex);
|
|
713
|
+
if (
|
|
714
|
+
!list ||
|
|
715
|
+
list.statusListCredential !== entry.statusListCredential ||
|
|
716
|
+
list.statusPurpose !== entry.statusPurpose
|
|
717
|
+
) {
|
|
718
|
+
throw new TRPCError({
|
|
719
|
+
code: 'BAD_REQUEST',
|
|
720
|
+
message: 'Status list URL or purpose does not match the credential',
|
|
721
|
+
});
|
|
722
|
+
}
|
|
723
|
+
if (list.ownerProfileId !== ownerProfileId) {
|
|
724
|
+
throw new TRPCError({
|
|
725
|
+
code: 'FORBIDDEN',
|
|
726
|
+
message: 'Status list owner does not match the credential',
|
|
727
|
+
});
|
|
728
|
+
}
|
|
729
|
+
if (
|
|
730
|
+
!Number.isSafeInteger(index) ||
|
|
731
|
+
index < 0 ||
|
|
732
|
+
index >= list.size ||
|
|
733
|
+
index >= list.nextIndex ||
|
|
734
|
+
(typeof entry.statusListIndex === 'string' && String(index) !== entry.statusListIndex)
|
|
735
|
+
) {
|
|
736
|
+
throw new TRPCError({
|
|
737
|
+
code: 'BAD_REQUEST',
|
|
738
|
+
message: 'Status list index is out of range',
|
|
739
|
+
});
|
|
740
|
+
}
|
|
741
|
+
}
|
|
742
|
+
};
|
|
743
|
+
|
|
435
744
|
export const getSignedStatusListCredential = async (id: string): Promise<VC | null> => {
|
|
436
745
|
const list = await getStatusListById(id);
|
|
437
746
|
if (!list) return null;
|
|
@@ -58,6 +58,9 @@ export const ConsentFlowContract = ModelFactory<FlatDbContractType, ConsentFlowR
|
|
|
58
58
|
createdAt: { type: 'string', required: true },
|
|
59
59
|
updatedAt: { type: 'string', required: true },
|
|
60
60
|
expiresAt: { type: 'string', required: false },
|
|
61
|
+
currentVersion: { type: 'number', required: false },
|
|
62
|
+
status: { type: 'string', required: false },
|
|
63
|
+
latestUpdate: { type: 'string', required: false },
|
|
61
64
|
audienceVersion: { type: 'number', required: false },
|
|
62
65
|
hasConsented: { type: 'boolean', required: false },
|
|
63
66
|
} as any,
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { ModelFactory, NeogmaInstance } from 'neogma';
|
|
2
|
+
import { neogma } from '@instance';
|
|
3
|
+
import type { DbContractVersionType } from 'types/consentflowcontract';
|
|
4
|
+
|
|
5
|
+
type StoredVersionProperties = Omit<
|
|
6
|
+
DbContractVersionType,
|
|
7
|
+
'contract' | 'autoboosts' | 'writers'
|
|
8
|
+
> & {
|
|
9
|
+
contract: string;
|
|
10
|
+
autoboosts?: string;
|
|
11
|
+
writers?: string;
|
|
12
|
+
};
|
|
13
|
+
type ContractVersionRelationships = Record<string, never>;
|
|
14
|
+
export type ConsentFlowContractVersionInstance = NeogmaInstance<
|
|
15
|
+
StoredVersionProperties,
|
|
16
|
+
ContractVersionRelationships
|
|
17
|
+
>;
|
|
18
|
+
|
|
19
|
+
export const ConsentFlowContractVersion = ModelFactory<
|
|
20
|
+
StoredVersionProperties,
|
|
21
|
+
ContractVersionRelationships
|
|
22
|
+
>(
|
|
23
|
+
{
|
|
24
|
+
label: 'ConsentFlowContractVersion',
|
|
25
|
+
schema: {
|
|
26
|
+
id: { type: 'string', required: true },
|
|
27
|
+
version: { type: 'number', required: true },
|
|
28
|
+
createdAt: { type: 'string', required: true },
|
|
29
|
+
contract: { type: 'string', required: true },
|
|
30
|
+
name: { type: 'string', required: true },
|
|
31
|
+
subtitle: { type: 'string', required: false },
|
|
32
|
+
description: { type: 'string', required: false },
|
|
33
|
+
reasonForAccessing: { type: 'string', required: false },
|
|
34
|
+
image: { type: 'string', required: false },
|
|
35
|
+
needsGuardianConsent: { type: 'boolean', required: false },
|
|
36
|
+
redirectUrl: { type: 'string', required: false },
|
|
37
|
+
frontDoorBoostUri: { type: 'string', required: false },
|
|
38
|
+
expiresAt: { type: 'string', required: false },
|
|
39
|
+
autoboosts: { type: 'string', required: false },
|
|
40
|
+
writers: { type: 'string', required: false },
|
|
41
|
+
reason: { type: 'string', required: false },
|
|
42
|
+
kind: { type: 'string', required: false },
|
|
43
|
+
},
|
|
44
|
+
relationships: {},
|
|
45
|
+
primaryKeyField: 'id',
|
|
46
|
+
},
|
|
47
|
+
neogma
|
|
48
|
+
);
|
|
49
|
+
|
|
50
|
+
export default ConsentFlowContractVersion;
|
|
@@ -33,6 +33,10 @@ export const ConsentFlowTerms = ModelFactory<FlatDbTermsType, ConsentFlowTermsRe
|
|
|
33
33
|
createdAt: { type: 'string', required: false },
|
|
34
34
|
updatedAt: { type: 'string', required: false },
|
|
35
35
|
mutationVersion: { type: 'number', required: false },
|
|
36
|
+
consentedVersion: { type: 'number', required: false },
|
|
37
|
+
lastReviewedVersion: { type: 'number', required: false },
|
|
38
|
+
reconsentDeadline: { type: 'string', required: false },
|
|
39
|
+
newOptionsUntil: { type: 'string', required: false },
|
|
36
40
|
expiresAt: { type: 'string', required: false },
|
|
37
41
|
oneTime: { type: 'boolean', required: false },
|
|
38
42
|
deniedWriters: { type: 'string[]', required: false },
|
package/src/models/Credential.ts
CHANGED
|
@@ -34,6 +34,8 @@ export const Credential = ModelFactory<CredentialType, CredentialRelationships>(
|
|
|
34
34
|
id: { type: 'string', required: true, uniqueItems: true },
|
|
35
35
|
credential: { type: 'string', required: true },
|
|
36
36
|
statusEntries: { type: 'string', required: false },
|
|
37
|
+
statusOwnerProfileId: { type: 'string', required: false },
|
|
38
|
+
statusReservationMode: { type: 'string', enum: ['strict', 'lenient'], required: false },
|
|
37
39
|
},
|
|
38
40
|
primaryKeyField: 'id',
|
|
39
41
|
},
|
|
@@ -54,6 +54,7 @@ export const CredentialRefresh = ModelFactory<
|
|
|
54
54
|
materialDigest: { type: 'string', required: false },
|
|
55
55
|
rootMaterialDigest: { type: 'string', required: false },
|
|
56
56
|
credentialStatusDigest: { type: 'string', required: false },
|
|
57
|
+
statusEntries: { type: 'string', required: false },
|
|
57
58
|
credentialSubjectDigest: { type: 'string', required: false },
|
|
58
59
|
boostId: { type: 'string', required: false },
|
|
59
60
|
signingMode: {
|
|
@@ -63,6 +64,7 @@ export const CredentialRefresh = ModelFactory<
|
|
|
63
64
|
},
|
|
64
65
|
idempotencyKey: { type: 'string', required: false },
|
|
65
66
|
updateSummary: { type: 'string', required: false },
|
|
67
|
+
updateReason: { type: 'string', required: false },
|
|
66
68
|
lastPublishedAt: { type: 'string', required: false },
|
|
67
69
|
notificationWindowKey: { type: 'string', required: false },
|
|
68
70
|
lastNotificationId: { type: 'string', required: false },
|
package/src/models/Profile.ts
CHANGED
|
@@ -126,6 +126,8 @@ export const Profile: any = ModelFactory<FlatProfileType, ProfileRelationships>(
|
|
|
126
126
|
notificationsWebhook: { type: 'string', required: false },
|
|
127
127
|
highlightedCredentials: { type: 'array', items: { type: 'string' }, required: false },
|
|
128
128
|
dob: { type: 'string', required: false },
|
|
129
|
+
announcementEmailOptOut: { type: 'boolean', required: false },
|
|
130
|
+
announcementPushOptOut: { type: 'boolean', required: false },
|
|
129
131
|
country: { type: 'string', required: false },
|
|
130
132
|
role: {
|
|
131
133
|
type: 'string',
|
|
@@ -4,17 +4,22 @@ let readiness: Promise<void> | undefined;
|
|
|
4
4
|
|
|
5
5
|
/** Await uniqueness before accepting creates, even during concurrent cold starts. */
|
|
6
6
|
export const ensureCampaignConstraints = (): Promise<void> => {
|
|
7
|
-
readiness ??=
|
|
8
|
-
|
|
9
|
-
'CREATE CONSTRAINT campaign_id_unique IF NOT EXISTS FOR (c:Campaign) REQUIRE c.id IS UNIQUE'
|
|
7
|
+
readiness ??= Promise.all(
|
|
8
|
+
[
|
|
9
|
+
'CREATE CONSTRAINT campaign_id_unique IF NOT EXISTS FOR (c:Campaign) REQUIRE c.id IS UNIQUE',
|
|
10
|
+
'CREATE CONSTRAINT campaign_unsubscribe_id_unique IF NOT EXISTS FOR (p:Profile) REQUIRE p.campaignUnsubscribeId IS UNIQUE',
|
|
11
|
+
].map(query =>
|
|
12
|
+
neogma.queryRunner.run(query).catch((error: unknown) => {
|
|
13
|
+
if (
|
|
14
|
+
(error as { code?: string })?.code !==
|
|
15
|
+
'Neo.ClientError.Schema.EquivalentSchemaRuleAlreadyExists'
|
|
16
|
+
)
|
|
17
|
+
throw error;
|
|
18
|
+
})
|
|
10
19
|
)
|
|
20
|
+
)
|
|
11
21
|
.then(() => undefined)
|
|
12
22
|
.catch((error: unknown) => {
|
|
13
|
-
if (
|
|
14
|
-
(error as { code?: string })?.code ===
|
|
15
|
-
'Neo.ClientError.Schema.EquivalentSchemaRuleAlreadyExists'
|
|
16
|
-
)
|
|
17
|
-
return;
|
|
18
23
|
readiness = undefined;
|
|
19
24
|
throw error;
|
|
20
25
|
});
|
package/src/models/index.ts
CHANGED
|
@@ -183,6 +183,7 @@ const indexQueries = [
|
|
|
183
183
|
'CREATE INDEX contract_updated_idx IF NOT EXISTS FOR (c:ConsentFlowContract) ON (c.updatedAt)',
|
|
184
184
|
'CREATE INDEX terms_updated_idx IF NOT EXISTS FOR (t:ConsentFlowTerms) ON (t.updatedAt)',
|
|
185
185
|
'CREATE INDEX terms_status_idx IF NOT EXISTS FOR (t:ConsentFlowTerms) ON (t.status)',
|
|
186
|
+
'CREATE INDEX consent_autoboost_claim_identity_idx IF NOT EXISTS FOR (c:ConsentFlowAutoBoostClaim) ON (c.termsId, c.boostId)',
|
|
186
187
|
'CREATE INDEX claim_hook_updated_idx IF NOT EXISTS FOR (c:ClaimHook) ON (c.updatedAt)',
|
|
187
188
|
'CREATE INDEX integration_id_idx IF NOT EXISTS FOR (i:Integration) ON (i.id)',
|
|
188
189
|
'CREATE INDEX integration_publishablekey_idx IF NOT EXISTS FOR (i:Integration) ON (i.publishableKey)',
|
|
@@ -306,6 +307,7 @@ export * from './DidMetadata';
|
|
|
306
307
|
export * from './SigningAuthority';
|
|
307
308
|
export * from './ConsentFlowTerms';
|
|
308
309
|
export * from './ConsentFlowContract';
|
|
310
|
+
export * from './ConsentFlowContractVersion';
|
|
309
311
|
export * from './ConsentFlowTransaction';
|
|
310
312
|
export * from './ContactMethod';
|
|
311
313
|
export * from './InboxCredential';
|
package/src/openapi.ts
CHANGED
|
@@ -57,6 +57,7 @@ if (publishRequestBody && !('$ref' in publishRequestBody)) {
|
|
|
57
57
|
refreshId: properties.refreshId,
|
|
58
58
|
notifyHolder: properties.notifyHolder!,
|
|
59
59
|
updateSummary: properties.updateSummary!,
|
|
60
|
+
updateReason: properties.updateReason!,
|
|
60
61
|
idempotencyKey: properties.idempotencyKey!,
|
|
61
62
|
};
|
|
62
63
|
// Generator 7.25.0 loses the OpenAPI 3.1 closed-object flag. Keep the
|
|
@@ -96,6 +97,26 @@ if (publishRequestBody && !('$ref' in publishRequestBody)) {
|
|
|
96
97
|
}
|
|
97
98
|
}
|
|
98
99
|
|
|
100
|
+
// Preserve the existing Python allocation identity contract without changing
|
|
101
|
+
// None/default serialization for unrelated optional request fields.
|
|
102
|
+
const allocationBody = openApiDocument.paths?.['/credential-refresh/allocate']?.post?.requestBody;
|
|
103
|
+
const allocationSchema =
|
|
104
|
+
allocationBody && !('$ref' in allocationBody)
|
|
105
|
+
? allocationBody.content?.['application/json']?.schema
|
|
106
|
+
: undefined;
|
|
107
|
+
const allocationHolder =
|
|
108
|
+
allocationSchema && !('$ref' in allocationSchema)
|
|
109
|
+
? allocationSchema.properties?.holder
|
|
110
|
+
: undefined;
|
|
111
|
+
const allocationProfile =
|
|
112
|
+
allocationHolder && !('$ref' in allocationHolder)
|
|
113
|
+
? allocationHolder.properties?.profileId
|
|
114
|
+
: undefined;
|
|
115
|
+
if (!allocationProfile || '$ref' in allocationProfile) {
|
|
116
|
+
throw new Error('Managed refresh allocation schema is missing its holder profile input');
|
|
117
|
+
}
|
|
118
|
+
Object.assign(allocationProfile, { 'x-python-preserve-explicit-null': true });
|
|
119
|
+
|
|
99
120
|
const SCALAR_HTML = `<!DOCTYPE html>
|
|
100
121
|
<html lang="en">
|
|
101
122
|
<head>
|
package/src/routes/app-store.ts
CHANGED
|
@@ -1721,11 +1721,12 @@ const handleRequestLearnerContextEvent = async (
|
|
|
1721
1721
|
|
|
1722
1722
|
// Use the same live/expiry/category/guardian evaluation as data-provider reads.
|
|
1723
1723
|
// An app's contract is not interchangeable with another app's or AI's grant.
|
|
1724
|
-
const
|
|
1724
|
+
const consentPage = await getConsentedDataBetweenProfiles(
|
|
1725
1725
|
owner.profileId,
|
|
1726
1726
|
profile.profileId,
|
|
1727
1727
|
{ query: { id: contractDetails.contract.id }, limit: 1, domain: ctx.domain }
|
|
1728
1728
|
);
|
|
1729
|
+
const consent = consentPage.records[0];
|
|
1729
1730
|
if (
|
|
1730
1731
|
!consent ||
|
|
1731
1732
|
consent.status !== 'live' ||
|