@learncard/lca-api-service 1.6.2 → 1.7.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.
Files changed (81) hide show
  1. package/.env.example +2 -0
  2. package/AGENTS.md +1 -1
  3. package/CHANGELOG.md +132 -0
  4. package/config/config.dev.json +1 -0
  5. package/config/config.example.json +1 -0
  6. package/config/config.production.json +1 -0
  7. package/config/config.scouts.dev.json +1 -0
  8. package/config/config.scouts.production.json +1 -0
  9. package/dist/accesslayer/notifications/create.d.ts +2 -1
  10. package/dist/app.d.ts +70 -8
  11. package/dist/config/environment.d.ts +6 -0
  12. package/dist/config/stageConfig.d.ts +4 -0
  13. package/dist/helpers/analyticsEligibility.d.ts +6 -0
  14. package/dist/helpers/brainUrl.d.ts +1 -0
  15. package/dist/helpers/brainUrl.test.d.ts +1 -0
  16. package/dist/helpers/currentProfile.d.ts +6 -0
  17. package/dist/helpers/currentProfile.test.d.ts +1 -0
  18. package/dist/helpers/retryingOnce.d.ts +1 -0
  19. package/dist/helpers/retryingOnce.test.d.ts +1 -0
  20. package/dist/index.js +2 -2
  21. package/dist/models/EscrowHold.d.ts +1 -0
  22. package/dist/models/EscrowHold.indexes.test.d.ts +1 -0
  23. package/dist/models/Notification.d.ts +114 -2
  24. package/dist/models/SigningAuthority.d.ts +1 -1
  25. package/dist/models/UserKey.d.ts +23 -0
  26. package/dist/publish-metafile.json +1 -1
  27. package/dist/routes/credentials.d.ts +1 -1
  28. package/dist/routes/index.d.ts +5 -0
  29. package/dist/routes/keys.d.ts +1 -0
  30. package/dist/routes/notifications.d.ts +65 -4
  31. package/dist/routes/preferences.test.d.ts +1 -0
  32. package/dist/routes/signing-authority.d.ts +3 -3
  33. package/dist/services/escrow-enclave/index.d.ts +2 -0
  34. package/dist/services/escrow-enclave/logFailure.d.ts +1 -0
  35. package/dist/types/credentials.d.ts +1 -1
  36. package/dist/types/notifications.d.ts +118 -2
  37. package/docs/bedrock-generation.md +7 -6
  38. package/lambdaApp.test.ts +114 -0
  39. package/lambdaApp.ts +23 -11
  40. package/package.json +11 -11
  41. package/scripts/escrow-lambda-http.ts +74 -0
  42. package/scripts/verify-escrow-startup.mjs +463 -0
  43. package/serverless-local.yml +1 -1
  44. package/serverless.yml +1 -1
  45. package/src/accesslayer/notifications/create.test.ts +80 -0
  46. package/src/accesslayer/notifications/create.ts +97 -2
  47. package/src/config/environment.test.ts +27 -1
  48. package/src/config/environment.ts +16 -0
  49. package/src/config/escrowDeployment.test.ts +1 -0
  50. package/src/docker-entry.ts +1 -2
  51. package/src/helpers/analyticsEligibility.ts +15 -0
  52. package/src/helpers/brainUrl.test.ts +13 -0
  53. package/src/helpers/brainUrl.ts +3 -0
  54. package/src/helpers/currentProfile.test.ts +55 -0
  55. package/src/helpers/currentProfile.ts +58 -0
  56. package/src/helpers/locale.helpers.test.ts +4 -1
  57. package/src/helpers/locale.helpers.ts +4 -2
  58. package/src/helpers/retryingOnce.test.ts +33 -0
  59. package/src/helpers/retryingOnce.ts +18 -0
  60. package/src/models/EscrowHold.indexes.test.ts +208 -0
  61. package/src/models/EscrowHold.ts +106 -51
  62. package/src/models/UserKey.ts +69 -1
  63. package/src/routes/escrow.ts +30 -12
  64. package/src/routes/index.ts +5 -8
  65. package/src/routes/keys.ts +8 -0
  66. package/src/routes/notifications.ts +34 -1
  67. package/src/routes/preferences.test.ts +216 -0
  68. package/src/routes/preferences.ts +54 -4
  69. package/src/services/escrow-enclave/index.ts +38 -1
  70. package/src/services/escrow-enclave/logFailure.ts +5 -1
  71. package/src/services/escrow-enclave/production.test.ts +64 -0
  72. package/src/services/escrow-enclave/softwareEnclave.test.ts +14 -2
  73. package/test/jobs/escrowHoldReminders.spec.ts +21 -4
  74. package/test/keys-escrow.spec.ts +149 -3
  75. package/test/keys-lifecycle-invariants.spec.ts +235 -0
  76. package/test/keys-pin-sign-in.spec.ts +34 -2
  77. package/test/notifications.spec.ts +102 -0
  78. package/tsconfig.entrypoints.json +1 -1
  79. package/vitest.config.ts +1 -0
  80. package/scripts/node24-runtime.cjs +0 -9
  81. package/scripts/verify-node24-layer.cjs +0 -49
package/.env.example CHANGED
@@ -127,6 +127,8 @@ ESCROW_HOLD_RESTART_MIN_AGE_MS=
127
127
  ESCROW_ENCLAVE_REMOTE_URL=
128
128
  ESCROW_ENCLAVE_REMOTE_TOKEN=
129
129
  ESCROW_ENCLAVE_REMOTE_TIMEOUT_MS=
130
+ # Match the remote enclave's authority tenant label. Defaults to learncard.
131
+ ESCROW_AUTHORITY_TENANT=
130
132
 
131
133
  # Emergency stop: true blocks every escrow release (read per request, no restart needed).
132
134
  # Deploy toggle, forwarded even in runtime-secrets bundle mode.
package/AGENTS.md CHANGED
@@ -143,7 +143,7 @@ Lost-login recovery issues single-use, 15-minute tokens (`src/cache/recoverySess
143
143
  - The revoke re-splits the same key; it does not evict a device that already holds the private key (see `services/escrow-enclave-app/SECURITY.md` D19). After the commit it emails the contact and verified recovery addresses (`account-secured`, awaited within a 3 s budget, fail-open); the copy assumes the reader may not have run it and may have lost their login, says the recovery methods were reset, and sends them to the tenant support address (or the organization, when there is none) rather than to lost-login recovery, which the revoke itself disabled.
144
144
  - A successful PIN release sends the `escrow-pin-sign-in` notice ("Not you? Secure your account", link to `/account/secure` on the tenant app URL) instead of `escrow-hold-released`. It is sent fail-open within the escrow notification budget.
145
145
  - `getEscrowPinStatus().wrongAttempts` counts verified wrong entries only and is omitted for legacy records that cannot tell (until a reservation initialises the verified counter to 0; clients therefore also read the status before a release).
146
- - `escrow.enroll` accepts `expectedPinCharges: { attemptsRemaining, wrongAttempts }` with a new `pinSalt` (the keep-mode same-PIN refresh). It is checked against the current PIN, then (software mode) again in the write's filter, or (authority mode) by the lease claim below, so a guess reserved or resolved after the client's check makes it CONFLICT instead of being wiped by the fresh generation. A non-zero `wrongAttempts` is always refused.
146
+ - `escrow.enroll` accepts `expectedPinCharges: { attemptsRemaining, wrongAttempts }` with a new `pinSalt` (the keep-mode same-PIN refresh). It is checked against the current PIN, then (software mode) again in the write's filter, or (authority mode) by the lease claim below, so a guess reserved or resolved after the client's check makes it CONFLICT instead of being wiped by the fresh generation. A non-zero `wrongAttempts` is accepted (own-typos rule, D19: the client sends it only when every recorded wrong entry is its own from the same sign-in flow) but must match the stored verified count exactly and can never exceed the charges; lca-api cannot tell whose entries they are, it only guarantees nothing changed since the client's check. Because clients count every "Incorrect PIN." response as their own recorded entry, a PIN mismatch whose failure write matched no record (re-enrolled/rotated/disabled under the compare) returns CONFLICT (`escrowChangedMessage`), never the mismatch text.
147
147
  - **PIN-operation lease (authority mode).** Every relayed owner-signed intent (`escrow.enroll`, `escrow.disable`/`remove`) runs under `applyEnrollmentIntentUnderLease` (`src/routes/escrow.ts`): `claimEscrowEnrollmentLease` sets `userKey.escrowEnrollmentLease` in one write conditioned on the read blob + PIN record, no other lease and no live `escrowPinCompares` marker; then the authority call; then `commitEscrowEnrollmentLease`, conditioned on the lease id and the replaced `blobHash` (not on charges), which also removes the lease. `reserveEscrowPinAttempt` refuses while any lease exists and pushes a compare marker (`compareId`) that `completeRecovery` pulls once the authority answers (kept until its TTL after a transport failure). `startRecovery` and `completeRecovery` answer 429 `Automatic recovery is being updated…` under a live lease, before creating a hold or charging; `completeRecovery` also refuses (FORBIDDEN, uncharged) a PIN hold whose `holdRecord.hold.blobHash` is not the current blob's. The rewrap job (`src/jobs/escrowBlobRewrap.ts`) claims the same lease (`action: 'rewrap'`) around `rewrapEscrowBlob` in authority mode. Releasing a compare marker is best effort (its TTL covers a miss). TTLs: `ESCROW_ENROLLMENT_LEASE_TTL_MS` / `ESCROW_PIN_COMPARE_TTL_MS` = 120 s (4x the 30 s max `ESCROW_ENCLAVE_REMOTE_TIMEOUT_MS`).
148
148
  - **Reconciliation.** If the commit write misses or the authority call fails ambiguously, `settleEscrowEnrollmentLease` (`src/services/escrow-enclave/enrollmentLease.ts`) reads the authority state and adopts it when it is exactly the lease's commit (enrollment version +1, epoch, PIN generation, share version, returned envelope identity), using the salt/PIN action recorded in the lease; a definite refusal releases the lease; an unknown outcome keeps it until expiry. `settleExpiredEscrowLease` runs on start/complete/enroll/disable/authority-state, so a crash converges on the next request. A settle that finds the same commit already persisted counts as adopted. A lost response still fails the request even when its commit is adopted (no receipt to return). `/keys/auth-share` reports `escrowBlobIdentity` so authority-mode clients can detect and repair any copy mismatch that is left, and `escrowUpdating` while a lease is held so they do not mistake an in-progress operation for a stale copy. Live-Mongo coverage: `test/keys-pin-lease.spec.ts`.
149
149
  - Software-mode PIN carry ignores releases completed before `escrowPin.enabledAt`, so a PIN re-set at the same version after a PIN sign-in survives the next rotation.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,137 @@
1
1
  # @welibraryos/lca-api-service
2
2
 
3
+ ## 1.7.0
4
+
5
+ ### Minor Changes
6
+
7
+ - [#1819](https://github.com/learningeconomy/LearnCard/pull/1819) [`784f0c9b346a5f7e4e2c618a9b976dcaa90fafe3`](https://github.com/learningeconomy/LearnCard/commit/784f0c9b346a5f7e4e2c618a9b976dcaa90fafe3) Thanks [@smurflo2](https://github.com/smurflo2)! - feat: [LC-2194] Support versioned ConsentFlow updates and user review.
8
+
9
+ Keep a connection's URI and history while publishing immutable access updates. New optional read/write choices stay off; required additions pause writes until the user reviews the changes. Add explicit update acceptance, review notifications, deadlines, once-only AutoBoost delivery, and safe retirement. Unsaved request previews no longer require a URI.
10
+
11
+ Preserve existing grants in partial scope updates, gate Lambda startup on legacy snapshots, and retain once-only delivery during explicit one-time updates. Localize review and history content, recover from cancelled guardian checks and concurrent updates, and keep handled notifications safe to reopen.
12
+
13
+ Retry transient Lambda migration failures without poisoning warm application imports. Record completed migrations for fast cold starts, report malformed request IDs without credential data, and identify retained ambiguous AutoBoost claims for reconciliation. Load runtime secrets before importing the scheduled deadline worker, including secrets-only Lambda deployments. Keep inbox maintenance coverage behavioral rather than pinning Lambda wrapper source text.
14
+
15
+ Recover valid, unversioned contracts created after the migration marker through scoped version-1 initialization. Atomically backfill only their existing connections, preserve global migration completion, and keep missing history on already-versioned or malformed contracts fail-closed.
16
+
17
+ Settle guardian PIN actions on external dismissal or unmount without cancelling valid submissions. Cover reapproval, signing failures and referral cancellation with the current PIN modal contract. Clear stacked overlays before opening a contract-update review. Cancel delayed modal-removal timers when their provider unmounts while preserving closing animations. Use stable localization keys independent of English labels and stop proposing optional defaults after their window expires.
18
+
19
+ Route pending and suspended connections from ordinary consent entry to explicit update review. Recover the same way when the latest-approval error arrives during an open consent dialog, without treating the connection as approved or bypassing guardian checks.
20
+
21
+ Reject overlapping top-level and nested SDK scope patches instead of silently dropping changes. Restore strict type-only imports, declare the runnable update snippet's SDK contract, and narrow unsuccessful notification reads in end-to-end consumers without raising the typecheck allowance.
22
+
23
+ Include cumulative changes in consent records so users can approve all required additions across multiple unreviewed versions. Clear the pending optional-choice marker only when the holder successfully saves their choices.
24
+
25
+ Resolve built-in request categories and personal fields to localized labels and descriptions in English, Spanish, French, and Arabic, including learning history and career/AI categories. Recognize the legitimate French cognates “Image” and “Qualifications” in the existing untranslated-value guard.
26
+
27
+ Dismiss Alerts when opening a contract-update review without waiting for read-status writes. Give unread indicators their own layout space so they cannot overlap notification dates.
28
+
29
+ Use one shared request form with the original creation controls for creating and editing requests, without per-item purpose fields or added default-enabled checkboxes. Keep the overall update explanation separate. Replace JSON authoring with readable changes and Preview Update / Publish Update actions in the Contracts tab. Show an accessible help icon and meaningful existing-access/defaults disclosures.
30
+
31
+ Start new optional review controls from their Opt-in/Opt-out settings without silently granting access. Use the edited request's defaults in previews, refresh saved choices when reopening without overwriting active edits, and reduce the extra spacing below the current version.
32
+
33
+ Separate Advanced Options from changes, published feedback, and actions. Add space below the current version while keeping the image's remove control in its original outside corner.
34
+
35
+ Show a clear unavailable state for networks that omit contract version metadata, including cached responses. Keep publishing disabled until a versioned response is available, and never assume an unversioned response is version 1.
36
+
37
+ Use direct Mongo connections for the local Docker APIs so the single-node replica's localhost address does not prevent them from starting. Gate Mongo-dependent services on a writable primary so they do not exit during replica initialization or election.
38
+
39
+ Pre-bundle the remaining workspace crypto and cloud-client dependencies before serving the local app, avoiding dependency-discovery reloads during browser startup.
40
+
41
+ Open an on-demand Update contract modal from each configured request in the Contracts tab, rather than the Testing tab. Reveal the update explanation only after edits, separate it from grouped current/next values, and keep user-review previews in the shared modal stack for correct focus and Escape behavior.
42
+
43
+ Breaking SDK/API changes: use `updateContract` instead of the removed `addAutoBoostsToContract` and `removeAutoBoostsFromContract` SDK methods. One-time consent status is now `consumed`, not `stale`. The deprecated HTTP AutoBoost routes remain available for one compatibility release and use the versioned update path. Legacy AutoBoost additions also pause writes and synchronization pending explicit holder review; coordinate partner upgrades and support `getConsentStatus` / `reviewUrl` before rollout.
44
+
45
+ Reject stale editor drafts with their original expected version, treat equivalent updates as no-ops, require future reconsent deadlines, and restrict legacy AutoBoost publications to contract owners. Paginate tied consent-data timestamps with stable opaque cursors and preserve all unreviewed optional additions across later updates.
46
+
47
+ Track each holder's last explicit review, backfill that baseline from transaction history, and timestamp legacy snapshots at the revision actually captured. Require notification and approval when an existing item's reason changes; keep redirect-only changes quiet. Retain ambiguous initial AutoBoost claims and reject mismatched concurrent initial credential deliveries.
48
+
49
+ Show material metadata, writer, and automatic-delivery changes alongside scope updates. Refresh holder terms before enabling review edits, populate newly enabled personal fields from the holder profile, and use a structured approval-required reason instead of matching error text.
50
+
51
+ Regenerate the Python API client from the current router graph, including all consent lifecycle states, versioned updates, and review metadata. Omit `None` for ordinary non-nullable optional inputs, preserve nullable values, and retain the allocation holder's explicit-null rejection instead of silently treating it as an omission.
52
+
53
+ Preserve existing delegated AutoBoost issuers when owners publish unchanged configurations, while keeping new configurations owner-issued and rejecting changes to an existing delegated signer. Defer new cross-account setup until an explicit issuer-authorization flow is available. Use pre-upgrade persisted fixtures for legacy-delegation regressions without reopening writer publication, and fix the signer mock's recursive self-call in consent regressions.
54
+
55
+ Keep saves from already handled reviews in ordinary sharing-update history, and preserve the saved one-time setting when update or reconsent inputs omit it.
56
+
57
+ Use opaque timestamp-and-ID cursors for holder consent lists, between-profile data, transaction history, and holder exports as well as owner data pages. Preserve legacy timestamp fallbacks and reject malformed or pre-upgrade date-only cursors with `BAD_REQUEST`; restart pagination without a cursor after upgrading.
58
+
59
+ Pin owned verification review links to the allocated app origin so real consent review URLs never escape to an unrelated localhost development server.
60
+
61
+ Explain approval notifications above Publish Update without changing checked-by-default Opt-out selections. Synchronize the CLI method catalog with the versioned contract SDK, removing obsolete AutoBoost commands and exposing update, history, status, and acceptance methods.
62
+
63
+ Use neutral changed-request summaries and Updated badges for existing-scope edits, including reason-only reviews. Reserve new-access headlines and new-item counts for pure scope additions without material request-detail edits, and retain existing optional choices during approval.
64
+
65
+ ### Patch Changes
66
+
67
+ - [#1872](https://github.com/learningeconomy/LearnCard/pull/1872) [`3505f004376543ff4c723921868e17c400142884`](https://github.com/learningeconomy/LearnCard/commit/3505f004376543ff4c723921868e17c400142884) Thanks [@TaylorBeeston](https://github.com/TaylorBeeston)! - Install python3, make, and g++ during `bun install` in the shared monorepo Docker image so linux/arm64 builds can compile native addons (utf-8-validate) via node-gyp.
68
+
69
+ - [#1839](https://github.com/learningeconomy/LearnCard/pull/1839) [`0caa0e478bce246793893e488f37ee90c9b00a1a`](https://github.com/learningeconomy/LearnCard/commit/0caa0e478bce246793893e488f37ee90c9b00a1a) Thanks [@gerardopar](https://github.com/gerardopar)! - Add campaign test-to-me and paced audience notification dispatch, ANNOUNCEMENT metadata, delivery acknowledgement bookkeeping, and retry-safe inbox deduplication.
70
+
71
+ Deploy lca-api's enum and campaign deduplication support before enabling brain-service dispatch. Push retains the existing best-effort delivery behavior.
72
+
73
+ - [#1952](https://github.com/learningeconomy/LearnCard/pull/1952) [`6404c1106a5b9b35c3f6ee34d021b0b596f334fd`](https://github.com/learningeconomy/LearnCard/commit/6404c1106a5b9b35c3f6ee34d021b0b596f334fd) Thanks [@Custard7](https://github.com/Custard7)! - lca-api: when `ESCROW_ENCLAVE_MODE=remote` but `ESCROW_ENCLAVE_REMOTE_URL`/`ESCROW_ENCLAVE_REMOTE_TOKEN` is missing, log a distinctive error once per container (cold start included, values never logged) and emit `LearnCard/Escrow EnclaveFailure` with `Reason=remote_config_missing`. Smoketests probe `/api/keys/escrow/attestation` on staging and production. Canary docs: both environments use the 7-day hold (`tier3_hold_duration_ms` now validated), observed drill timings, rollout record.
74
+
75
+ - [#1925](https://github.com/learningeconomy/LearnCard/pull/1925) [`13e0809239cd73c5153a3ff82b268afb21b665b2`](https://github.com/learningeconomy/LearnCard/commit/13e0809239cd73c5153a3ff82b268afb21b665b2) Thanks [@Custard7](https://github.com/Custard7)! - Let the escrow enclave's own recovery emails (code, recovery started, release warning) carry the app's brand. The owner's device seals a display name into the escrow blob at enrollment (`recoveryBrand`), the same way it seals the recovery email and language. This needs the brand-aware enclave image, and stays off until a tenant turns it on (SECURITY.md D21).
76
+
77
+ - **sss-key-manager:** new optional `escrow.emailBrand`. It is sealed as `recoveryBrand` only when the recovery contact is sealed (nitro enroll/replace), never on a carry. `createSSSStrategy` throws if the value is invalid. New export `canonicalEscrowEmailBrand` (ASCII letters, digits, space, `-`, `'`, `&`; 1-32 characters, starting with a letter; at most 3 digits in total and no `http`/`www`), plus `MAX_ESCROW_EMAIL_BRAND_LENGTH` and `MAX_ESCROW_EMAIL_BRAND_DIGITS`. `EscrowBlobPlaintext` gains `recoveryBrand?`. The blob and release parsers keep a valid brand and drop an invalid one without failing; before this change they dropped the field entirely. `violatesCarryContactRule` also flags a target that claims a brand.
78
+ - **learn-card-base:** new tenant config flag `auth.sss.escrowSealEmailBrand` (default false). When it is on, `getEscrowStrategyConfig(sss, { appBrandName })` forwards the canonical app name as `emailBrand`. An invalid name is logged and omitted, and escrow stays enabled. `getEscrowEmailSender` now returns `mixed`, which is true when this app seals its brand. New optional `auth.sss.escrowSharedEmailBrands` (the other apps sharing this app's DIDs): with sealing on, an app whose fallback brand is its own (LearnCard) gets a `shared` note naming them, since a shared DID's brand is "last writer wins". New `getTenantEscrowStrategyConfig(sss, tenant)`, used by both apps' `sss` factories, forwards `branding.name`.
79
+ - **learn-card-app:** passes `branding.name` through `getTenantEscrowStrategyConfig`. While sealing is on, the recovery screens say the code and updates may come from either brand, or, in LearnCard, from VetPass if the user also uses it (4 new keys in each of the 4 locales).
80
+ - **lca-api:** no code change. The software enclave now keeps the sealed brand through carry and rewrap because it uses the updated parser (covered by a test).
81
+
82
+ - [#1945](https://github.com/learningeconomy/LearnCard/pull/1945) [`fea9c25ec76f230944a1713500a280c80e2a9cc0`](https://github.com/learningeconomy/LearnCard/commit/fea9c25ec76f230944a1713500a280c80e2a9cc0) Thanks [@smurflo2](https://github.com/smurflo2)! - Enforce current profile age rules in analytics preferences and keep optional telemetry off until account and tenant permission resolve. Retire SDK sessions on opt-out without replaying queued data, while preserving necessary security error reporting.
83
+
84
+ Keep ordinary preferences available during profile-authority outages without erasing saved analytics consent. Share session-bound preference reads, preserve legacy ISO birth-date formats and avoid initializing web Firebase merely to disable collection.
85
+
86
+ Report unavailable analytics opt-in instead of silently accepting it, keep age-enforced reads available when a repair cannot be saved, and ensure preference writes target the newly selected account.
87
+
88
+ Use checked-in Lambda stage configuration for the profile authority URL instead of forwarding stale deployment-environment values as secrets.
89
+
90
+ Normalize the shared Brain URL for login-email locale lookups and preserve onboarding AI and bug-report choices when analytics authority is unavailable. Keep the Firebase logger import independent of the wallet barrel.
91
+
92
+ - [#1972](https://github.com/learningeconomy/LearnCard/pull/1972) [`bccffa77515a9c9d17225b7296d252bd144b3b51`](https://github.com/learningeconomy/LearnCard/commit/bccffa77515a9c9d17225b7296d252bd144b3b51) Thanks [@Custard7](https://github.com/Custard7)! - lca-api Lambdas no longer cache a failed cold start. If startup work (WASM load, Mongo index setup) fails, only the requests waiting on it fail and the next request retries, instead of the container returning errors until it is recycled.
93
+
94
+ - [#1973](https://github.com/learningeconomy/LearnCard/pull/1973) [`15931126d1cae3ff52684f3b16b5c6b6336f62ac`](https://github.com/learningeconomy/LearnCard/commit/15931126d1cae3ff52684f3b16b5c6b6336f62ac) Thanks [@TaylorBeeston](https://github.com/TaylorBeeston)! - Isolate escrow index migration failures from unrelated API startup, retain fail-closed escrow writes, and log operation-specific Mongo diagnostics without duplicate-key identity values.
95
+
96
+ - [#1925](https://github.com/learningeconomy/LearnCard/pull/1925) [`13e0809239cd73c5153a3ff82b268afb21b665b2`](https://github.com/learningeconomy/LearnCard/commit/13e0809239cd73c5153a3ff82b268afb21b665b2) Thanks [@Custard7](https://github.com/Custard7)! - Prepare VetPass to use escrow recovery on LearnCard's escrow enclave. The enclave serves one tenant: it signs and checks its own authority tenant label (`learncard`). Before this change VetPass sent its app tenant (`vetpass`) instead, so its first authority-state read failed.
97
+
98
+ - **sss-key-manager:** new optional `escrow.authorityTenant`, used for everything the enclave signs or checks (intent tenant, authority state, hold and code-delivery records, persisted recovery expectations). `tenantId` is still sent as `X-Tenant-Id` and used for branding. It defaults to `tenantId`, so LearnCard and ScoutPass behave as before. If signed evidence carries another label, the client throws the new `EscrowAuthorityTenantMismatchError` (a subclass of `EscrowEvidenceError` that names both labels) before rotating shares or taking any recovery step. A malformed label throws in `createSSSStrategy`.
99
+ - **learn-card-base:** new tenant config field `auth.sss.escrowAuthorityTenant` (validated), forwarded with the escrow trust policy by `getEscrowStrategyConfig`. Also adds the helpers `getEscrowAuthorityTenant`, `escrowAuthorityBrandName`, `getEscrowEmailSenderBrand` and `getEscrowEmailSender` (compares the app's `branding.name` with the enclave brand).
100
+ - **lca-api:** checks escrow intents and authority state against the new `ESCROW_AUTHORITY_TENANT` setting (default `learncard`, so no infrastructure change), not the client-supplied `X-Tenant-Id`. It also refuses an enroll when the authority state it reads carries a different tenant. Holds, emails and links still use the app tenant. The default is global, so the ScoutPass deployment (escrow off) must set its own value before it turns escrow on.
101
+ - **learn-card-app:** no VetPass config change: VetPass escrow stays off at every stage until the SSS switch turns it on together with LearnCard production. A new test keeps it off until then, and afterwards fails if VetPass and LearnCard differ in key derivation, escrow mode, pins or authority tenant at any deployed stage. When the enclave's email brand differs from the app's, the recovery screens explain that the code and some other emails come from LearnCard (4 locales).
102
+ - **escrow-canary:** tier 3 reports any `EscrowEvidenceError` subclass, including the new tenant mismatch, as `evidence` rather than `unexpected`.
103
+
104
+ - [#1971](https://github.com/learningeconomy/LearnCard/pull/1971) [`03e81d9908d3ba9eae473103ead4fff6099b7f02`](https://github.com/learningeconomy/LearnCard/commit/03e81d9908d3ba9eae473103ead4fff6099b7f02) Thanks [@Custard7](https://github.com/Custard7)! - Web3Auth sign-in now checks which method the account signs in with before deriving a key. If the account uses SSS and this login would derive a different key, sign-in stops and asks the user to update the app instead of creating a different, empty identity. Accounts migrated from Web3Auth keep signing in with Web3Auth through the login they migrated with: lca-api records that login at `/keys/migrate` (`web3AuthLogin`, only when it is the account's sole login) and reports `isWeb3AuthLogin` from `/keys/auth-share`. Migrations from before this change have no recorded login and are stopped on Web3Auth builds.
105
+
106
+ - Updated dependencies [[`e9d738eea3b477dbd82bde171d3c5f8ffa2efbc5`](https://github.com/learningeconomy/LearnCard/commit/e9d738eea3b477dbd82bde171d3c5f8ffa2efbc5), [`69b80366c48e0f458b3bb50f25166c90ecaa2d6d`](https://github.com/learningeconomy/LearnCard/commit/69b80366c48e0f458b3bb50f25166c90ecaa2d6d), [`69b80366c48e0f458b3bb50f25166c90ecaa2d6d`](https://github.com/learningeconomy/LearnCard/commit/69b80366c48e0f458b3bb50f25166c90ecaa2d6d), [`0caa0e478bce246793893e488f37ee90c9b00a1a`](https://github.com/learningeconomy/LearnCard/commit/0caa0e478bce246793893e488f37ee90c9b00a1a), [`13e0809239cd73c5153a3ff82b268afb21b665b2`](https://github.com/learningeconomy/LearnCard/commit/13e0809239cd73c5153a3ff82b268afb21b665b2), [`fea9c25ec76f230944a1713500a280c80e2a9cc0`](https://github.com/learningeconomy/LearnCard/commit/fea9c25ec76f230944a1713500a280c80e2a9cc0), [`bde3fe0f15a780d9fafc699096a9a0307502f529`](https://github.com/learningeconomy/LearnCard/commit/bde3fe0f15a780d9fafc699096a9a0307502f529), [`784f0c9b346a5f7e4e2c618a9b976dcaa90fafe3`](https://github.com/learningeconomy/LearnCard/commit/784f0c9b346a5f7e4e2c618a9b976dcaa90fafe3), [`751579d9c14116dbf6d0e7f7d62f489c81c39db2`](https://github.com/learningeconomy/LearnCard/commit/751579d9c14116dbf6d0e7f7d62f489c81c39db2), [`2c60b1a760958cbfe2525bc951d2381332e1aa48`](https://github.com/learningeconomy/LearnCard/commit/2c60b1a760958cbfe2525bc951d2381332e1aa48), [`4caf6a99c7a204d7f211737190d85eeebe736eb0`](https://github.com/learningeconomy/LearnCard/commit/4caf6a99c7a204d7f211737190d85eeebe736eb0), [`13e0809239cd73c5153a3ff82b268afb21b665b2`](https://github.com/learningeconomy/LearnCard/commit/13e0809239cd73c5153a3ff82b268afb21b665b2)]:
107
+ - @learncard/types@6.0.0
108
+ - @learncard/email-templates@1.3.0
109
+ - @learncard/sss-key-manager@0.3.0
110
+ - @learncard/helpers@1.8.0
111
+ - @learncard/core@9.4.44
112
+ - @learncard/init@2.5.10
113
+ - @learncard/did-web-plugin@1.1.44
114
+ - @learncard/didkit-plugin@1.10.10
115
+ - @learncard/didkit-plugin-node@0.3.10
116
+
117
+ ## 1.6.3
118
+
119
+ ### Patch Changes
120
+
121
+ - [#1923](https://github.com/learningeconomy/LearnCard/pull/1923) [`27be1a449118c3c9b69f1fd961cf0b7fe3cbcdb6`](https://github.com/learningeconomy/LearnCard/commit/27be1a449118c3c9b69f1fd961cf0b7fe3cbcdb6) Thanks [@Custard7](https://github.com/Custard7)! - PIN sign-in: wrong PIN entries made during the same sign-in on the same device are now treated as the user's own typos (own-typos rule, approved). They no longer trigger the "someone may have tried your PIN" warning, and they no longer block the same-PIN budget refresh. Only wrong entries recorded before the sign-in started, or entries from anywhere else during it, still count. The coordinator records the PIN status when the flow starts, counts its own mismatch responses, and passes both to the strategy (`input.pinAttempts`). The strategy refreshes the PIN only when nothing was recorded before the flow and every status read shows exactly the flow's own entries. lca-api now accepts a non-zero `expectedPinCharges.wrongAttempts`, but still writes only while the PIN shows exactly those charges, so a foreign entry that lands at any point still blocks the refresh. A flow is bound to one PIN generation and ends at any sign-in, and lca-api answers "Incorrect PIN." only for a wrong entry it actually recorded (otherwise CONFLICT). Sign-up now requires a recovery PIN ("Create your PIN": "You'll use it to sign in on new devices."), but it fails open. The step is skipped when escrow is not confirmed available in time. A save that errors or hangs (30s deadline) shows the error and allows a retry, and "Skip for now" appears from the second failed save (the announced error then says so, and focus returns to the PIN boxes); that skip writes no prompt flag, so the first-time PIN prompt asks again on a later start.
122
+
123
+ App copy and layout: the new-device PIN screen is now "Confirm it's you" ("Enter your 6-digit PIN to finish signing in."), without the tinted panel behind the PIN boxes, with "Other ways to confirm", "Approve from another device", "Show N more", and one quiet "Trouble signing in?" link to the 7-day path (accounts without a PIN keep their existing screen). The post-PIN-sign-in sheet replaces its yes/no question with "You're signed in" / "Done" plus a quiet "Lost a device? Secure your account" link, or, when wrong entries other than the user's own typos are recorded, an amber "Someone may have tried your PIN" warning leading with "Choose a new PIN" (with "Not now"). Account Recovery settings replace the boxed "Signed in somewhere you don't recognize?" card with a quiet "Sign out of other devices" row under "Security" at the end, marked with an amber dot while wrong PIN entries other than the user's excused own typos are recorded against the current PIN (`ready.escrowPinAttention`); the secure flow's confirm screen keeps the full explanation, now uses a red destructive button, and focuses Cancel first. Translated into es, fr and ar.
124
+
125
+ - Updated dependencies [[`27be1a449118c3c9b69f1fd961cf0b7fe3cbcdb6`](https://github.com/learningeconomy/LearnCard/commit/27be1a449118c3c9b69f1fd961cf0b7fe3cbcdb6)]:
126
+ - @learncard/sss-key-manager@0.2.7
127
+ - @learncard/types@5.24.2
128
+ - @learncard/core@9.4.43
129
+ - @learncard/helpers@1.7.3
130
+ - @learncard/init@2.5.9
131
+ - @learncard/did-web-plugin@1.1.43
132
+ - @learncard/didkit-plugin@1.10.9
133
+ - @learncard/didkit-plugin-node@0.3.9
134
+
3
135
  ## 1.6.2
4
136
 
5
137
  ### Patch Changes
@@ -1,6 +1,7 @@
1
1
  {
2
2
  "APPLE_OAUTH_CLIENT_IDS": "com.learncard.app,app.learncard.www",
3
3
  "DOMAIN_NAME": "staging.api.learncard.app",
4
+ "NETWORK_BRAIN_SERVICE_URL": "https://staging.network.learncard.com/trpc",
4
5
  "ESCROW_ENCLAVE_ACTIVE_KEY_ID": "escrow-staging-v1",
5
6
  "ESCROW_ENCLAVE_REMOTE_TIMEOUT_MS": "15000",
6
7
  "ESCROW_HOLD_DURATION_MS": "600000",
@@ -1,6 +1,7 @@
1
1
  {
2
2
  "APPLE_OAUTH_CLIENT_IDS": "com.example.app,app.example.www",
3
3
  "DOMAIN_NAME": "api.example.com",
4
+ "NETWORK_BRAIN_SERVICE_URL": "https://network.example.com/trpc",
4
5
  "ESCROW_ENCLAVE_ACTIVE_KEY_ID": "example-2026-01",
5
6
  "ESCROW_HOLD_DURATION_MS": "604800000",
6
7
  "ESCROW_HOLD_RESTART_MIN_AGE_MS": "86400000",
@@ -1,5 +1,6 @@
1
1
  {
2
2
  "DOMAIN_NAME": "api.learncard.app",
3
+ "NETWORK_BRAIN_SERVICE_URL": "https://network.learncard.com/trpc",
3
4
  "POSTMARK_BRAND_NAME": "LearnCard",
4
5
  "POSTMARK_ENDORSEMENT_REQUEST_TEMPLATE_ALIAS": "universal-inbox-claim-1",
5
6
  "POSTMARK_FROM_EMAIL": "learncard.com",
@@ -1,5 +1,6 @@
1
1
  {
2
2
  "DOMAIN_NAME": "staging.api.scoutnetwork.org",
3
+ "NETWORK_BRAIN_SERVICE_URL": "https://staging.scoutnetwork.org/trpc",
3
4
  "POSTMARK_BRAND_NAME": "ScoutPass",
4
5
  "POSTMARK_FROM_EMAIL": "staging.scoutnetwork.org",
5
6
  "POSTMARK_LOGIN_CODE_TEMPLATE_ALIAS": "sp-login-verification",
@@ -1,5 +1,6 @@
1
1
  {
2
2
  "DOMAIN_NAME": "api.scoutnetwork.org",
3
+ "NETWORK_BRAIN_SERVICE_URL": "https://scoutnetwork.org/trpc",
3
4
  "POSTMARK_BRAND_NAME": "ScoutPass",
4
5
  "POSTMARK_FROM_EMAIL": "scoutnetwork.org",
5
6
  "POSTMARK_LOGIN_CODE_TEMPLATE_ALIAS": "sp-login-verification",
@@ -1,7 +1,8 @@
1
- import { LCNNotification } from '@learncard/types';
1
+ import { type LCNNotification } from '@learncard/types';
2
2
  export type NotificationCreationResult = {
3
3
  created: boolean;
4
4
  };
5
5
  export declare const isManagedCredentialRefreshNotification: (notification: LCNNotification) => boolean;
6
+ export declare const upsertConsentFlowContractUpdateNotification: (notification: LCNNotification) => Promise<NotificationCreationResult | false>;
6
7
  export declare const createNotification: (notification: LCNNotification) => Promise<NotificationCreationResult | false>;
7
8
  export declare const upsertCredentialRefreshNotification: (notification: LCNNotification) => Promise<NotificationCreationResult | false>;
package/dist/app.d.ts CHANGED
@@ -23,7 +23,7 @@ export declare const appRouter: import("@trpc/server").TRPCBuiltRouter<{
23
23
  archived?: boolean | undefined;
24
24
  read?: boolean | undefined;
25
25
  actionStatus?: "PENDING" | "COMPLETED" | "REJECTED" | undefined;
26
- type?: "GUARDIAN_APPROVED" | "GUARDIAN_REJECTED" | "CONNECTION_REQUEST" | "CONNECTION_REQUEST_EXPIRED_INVITE" | "CONNECTION_ACCEPTED" | "CREDENTIAL_RECEIVED" | "CREDENTIAL_ACCEPTED" | "BOOST_RECEIVED" | "BOOST_ACCEPTED" | "PRESENTATION_REQUEST" | "PRESENTATION_RECEIVED" | "CONSENT_FLOW_TRANSACTION" | "ISSUANCE_CLAIMED" | "ISSUANCE_DELIVERED" | "ISSUANCE_ERROR" | "PROFILE_PARENT_APPROVED" | "APP_LISTING_SUBMITTED" | "APP_LISTING_APPROVED" | "APP_LISTING_REJECTED" | "APP_LISTING_WITHDRAWN" | "DEVICE_LINK_REQUEST" | "GUARDIAN_APPROVAL_PENDING" | "APP_NOTIFICATION" | "CREDENTIAL_REVOKED" | "CREDENTIAL_SUSPENDED" | "CREDENTIAL_UNSUSPENDED" | "CREDENTIAL_REFRESHED" | undefined;
26
+ type?: "GUARDIAN_APPROVED" | "GUARDIAN_REJECTED" | "CONNECTION_REQUEST" | "CONNECTION_REQUEST_EXPIRED_INVITE" | "CONNECTION_ACCEPTED" | "CREDENTIAL_RECEIVED" | "CREDENTIAL_ACCEPTED" | "BOOST_RECEIVED" | "BOOST_ACCEPTED" | "PRESENTATION_REQUEST" | "PRESENTATION_RECEIVED" | "CONSENT_FLOW_TRANSACTION" | "ISSUANCE_CLAIMED" | "ISSUANCE_DELIVERED" | "ISSUANCE_ERROR" | "PROFILE_PARENT_APPROVED" | "APP_LISTING_SUBMITTED" | "APP_LISTING_APPROVED" | "APP_LISTING_REJECTED" | "APP_LISTING_WITHDRAWN" | "DEVICE_LINK_REQUEST" | "GUARDIAN_APPROVAL_PENDING" | "APP_NOTIFICATION" | "ANNOUNCEMENT" | "CREDENTIAL_REVOKED" | "CREDENTIAL_SUSPENDED" | "CREDENTIAL_UNSUSPENDED" | "CREDENTIAL_REFRESHED" | "CONSENT_FLOW_CONTRACT_UPDATED" | undefined;
27
27
  } | undefined;
28
28
  };
29
29
  output: {
@@ -36,7 +36,7 @@ export declare const appRouter: import("@trpc/server").TRPCBuiltRouter<{
36
36
  queryNotifications: import("@trpc/server").TRPCQueryProcedure<{
37
37
  input: {
38
38
  query: {
39
- type?: "GUARDIAN_APPROVED" | "GUARDIAN_REJECTED" | "CONNECTION_REQUEST" | "CONNECTION_REQUEST_EXPIRED_INVITE" | "CONNECTION_ACCEPTED" | "CREDENTIAL_RECEIVED" | "CREDENTIAL_ACCEPTED" | "BOOST_RECEIVED" | "BOOST_ACCEPTED" | "PRESENTATION_REQUEST" | "PRESENTATION_RECEIVED" | "CONSENT_FLOW_TRANSACTION" | "ISSUANCE_CLAIMED" | "ISSUANCE_DELIVERED" | "ISSUANCE_ERROR" | "PROFILE_PARENT_APPROVED" | "APP_LISTING_SUBMITTED" | "APP_LISTING_APPROVED" | "APP_LISTING_REJECTED" | "APP_LISTING_WITHDRAWN" | "DEVICE_LINK_REQUEST" | "GUARDIAN_APPROVAL_PENDING" | "APP_NOTIFICATION" | "CREDENTIAL_REVOKED" | "CREDENTIAL_SUSPENDED" | "CREDENTIAL_UNSUSPENDED" | "CREDENTIAL_REFRESHED" | undefined;
39
+ type?: "GUARDIAN_APPROVED" | "GUARDIAN_REJECTED" | "CONNECTION_REQUEST" | "CONNECTION_REQUEST_EXPIRED_INVITE" | "CONNECTION_ACCEPTED" | "CREDENTIAL_RECEIVED" | "CREDENTIAL_ACCEPTED" | "BOOST_RECEIVED" | "BOOST_ACCEPTED" | "PRESENTATION_REQUEST" | "PRESENTATION_RECEIVED" | "CONSENT_FLOW_TRANSACTION" | "ISSUANCE_CLAIMED" | "ISSUANCE_DELIVERED" | "ISSUANCE_ERROR" | "PROFILE_PARENT_APPROVED" | "APP_LISTING_SUBMITTED" | "APP_LISTING_APPROVED" | "APP_LISTING_REJECTED" | "APP_LISTING_WITHDRAWN" | "DEVICE_LINK_REQUEST" | "GUARDIAN_APPROVAL_PENDING" | "APP_NOTIFICATION" | "ANNOUNCEMENT" | "CREDENTIAL_REVOKED" | "CREDENTIAL_SUSPENDED" | "CREDENTIAL_UNSUSPENDED" | "CREDENTIAL_REFRESHED" | "CONSENT_FLOW_CONTRACT_UPDATED" | undefined;
40
40
  'from.did'?: string | undefined;
41
41
  'from.profileId'?: string | undefined;
42
42
  'data.vcUris'?: string | string[] | undefined;
@@ -92,7 +92,7 @@ export declare const appRouter: import("@trpc/server").TRPCBuiltRouter<{
92
92
  }>;
93
93
  sendNotification: import("@trpc/server").TRPCMutationProcedure<{
94
94
  input: {
95
- type: "GUARDIAN_APPROVED" | "GUARDIAN_REJECTED" | "CONNECTION_REQUEST" | "CONNECTION_REQUEST_EXPIRED_INVITE" | "CONNECTION_ACCEPTED" | "CREDENTIAL_RECEIVED" | "CREDENTIAL_ACCEPTED" | "BOOST_RECEIVED" | "BOOST_ACCEPTED" | "PRESENTATION_REQUEST" | "PRESENTATION_RECEIVED" | "CONSENT_FLOW_TRANSACTION" | "ISSUANCE_CLAIMED" | "ISSUANCE_DELIVERED" | "ISSUANCE_ERROR" | "PROFILE_PARENT_APPROVED" | "APP_LISTING_SUBMITTED" | "APP_LISTING_APPROVED" | "APP_LISTING_REJECTED" | "APP_LISTING_WITHDRAWN" | "DEVICE_LINK_REQUEST" | "GUARDIAN_APPROVAL_PENDING" | "APP_NOTIFICATION" | "CREDENTIAL_REVOKED" | "CREDENTIAL_SUSPENDED" | "CREDENTIAL_UNSUSPENDED" | "CREDENTIAL_REFRESHED";
95
+ type: "GUARDIAN_APPROVED" | "GUARDIAN_REJECTED" | "CONNECTION_REQUEST" | "CONNECTION_REQUEST_EXPIRED_INVITE" | "CONNECTION_ACCEPTED" | "CREDENTIAL_RECEIVED" | "CREDENTIAL_ACCEPTED" | "BOOST_RECEIVED" | "BOOST_ACCEPTED" | "PRESENTATION_REQUEST" | "PRESENTATION_RECEIVED" | "CONSENT_FLOW_TRANSACTION" | "ISSUANCE_CLAIMED" | "ISSUANCE_DELIVERED" | "ISSUANCE_ERROR" | "PROFILE_PARENT_APPROVED" | "APP_LISTING_SUBMITTED" | "APP_LISTING_APPROVED" | "APP_LISTING_REJECTED" | "APP_LISTING_WITHDRAWN" | "DEVICE_LINK_REQUEST" | "GUARDIAN_APPROVAL_PENDING" | "APP_NOTIFICATION" | "ANNOUNCEMENT" | "CREDENTIAL_REVOKED" | "CREDENTIAL_SUSPENDED" | "CREDENTIAL_UNSUSPENDED" | "CREDENTIAL_REFRESHED" | "CONSENT_FLOW_CONTRACT_UPDATED";
96
96
  to: {
97
97
  profileId?: string | undefined;
98
98
  displayName?: string | undefined;
@@ -183,7 +183,7 @@ export declare const appRouter: import("@trpc/server").TRPCBuiltRouter<{
183
183
  vpUris?: string[] | undefined;
184
184
  transaction?: {
185
185
  id: string;
186
- action: "write" | "update" | "consent" | "sync" | "withdraw";
186
+ action: "write" | "consent" | "update" | "sync" | "withdraw" | "reconsent";
187
187
  date: string;
188
188
  expiresAt?: string | undefined;
189
189
  oneTime?: boolean | undefined;
@@ -221,7 +221,64 @@ export declare const appRouter: import("@trpc/server").TRPCBuiltRouter<{
221
221
  requestedBy: string;
222
222
  externalReferenceId?: string | undefined;
223
223
  } | undefined;
224
+ kind?: "relaxed" | "expanded_optional" | "requires_consent" | undefined;
224
225
  uris?: string[] | undefined;
226
+ consentedVersion?: number | undefined;
227
+ reconsentDeadline?: string | undefined;
228
+ newOptionsUntil?: string | undefined;
229
+ } | undefined;
230
+ contractUri?: string | undefined;
231
+ contractName?: string | undefined;
232
+ fromVersion?: number | undefined;
233
+ toVersion?: number | undefined;
234
+ kind?: "relaxed" | "expanded_optional" | "requires_consent" | undefined;
235
+ reason?: string | undefined;
236
+ diff?: {
237
+ added: {
238
+ area: "read" | "write";
239
+ type: "personal" | "credential";
240
+ key: string;
241
+ required: boolean;
242
+ reason?: string | undefined;
243
+ }[];
244
+ removed: {
245
+ area: "read" | "write";
246
+ type: "personal" | "credential";
247
+ key: string;
248
+ required: boolean;
249
+ reason?: string | undefined;
250
+ }[];
251
+ changed: {
252
+ area: "read" | "write";
253
+ type: "personal" | "credential";
254
+ key: string;
255
+ required: boolean;
256
+ reason?: string | undefined;
257
+ }[];
258
+ details?: {
259
+ field: "expiresAt" | "name" | "description" | "image" | "subtitle" | "reasonForAccessing" | "needsGuardianConsent" | "redirectUrl" | "frontDoorBoostUri" | "writers" | "autoboosts";
260
+ before?: string | boolean | string[] | undefined;
261
+ after?: string | boolean | string[] | undefined;
262
+ }[] | undefined;
263
+ } | undefined;
264
+ reconsentDeadline?: string | undefined;
265
+ reviewUrl?: string | undefined;
266
+ preview?: {
267
+ headline: string;
268
+ subhead: string;
269
+ rows: {
270
+ scope: {
271
+ area: "read" | "write";
272
+ type: "personal" | "credential";
273
+ key: string;
274
+ required: boolean;
275
+ reason?: string | undefined;
276
+ };
277
+ change: "added" | "removed" | "changed";
278
+ label: string;
279
+ subtitle: string;
280
+ }[];
281
+ primaryLabel: string;
225
282
  } | undefined;
226
283
  inbox?: {
227
284
  issuanceId: string;
@@ -241,6 +298,10 @@ export declare const appRouter: import("@trpc/server").TRPCBuiltRouter<{
241
298
  promptId: string;
242
299
  counterpartProfileId: string;
243
300
  } | undefined;
301
+ campaignId?: string | undefined;
302
+ campaignCreatedAt?: string | undefined;
303
+ campaignHasInApp?: boolean | undefined;
304
+ campaignTestId?: string | undefined;
244
305
  } | undefined;
245
306
  } | undefined;
246
307
  sent?: string | undefined;
@@ -304,8 +365,8 @@ export declare const appRouter: import("@trpc/server").TRPCBuiltRouter<{
304
365
  ownerDid?: string | undefined;
305
366
  };
306
367
  output: {
307
- ownerDid: string;
308
368
  name: string;
369
+ ownerDid: string;
309
370
  did: string;
310
371
  endpoint: string;
311
372
  _id?: string | undefined;
@@ -318,8 +379,8 @@ export declare const appRouter: import("@trpc/server").TRPCBuiltRouter<{
318
379
  ownerDid: string;
319
380
  };
320
381
  output: {
321
- ownerDid: string;
322
382
  name: string;
383
+ ownerDid: string;
323
384
  did: string;
324
385
  endpoint: string;
325
386
  _id?: string | undefined;
@@ -329,8 +390,8 @@ export declare const appRouter: import("@trpc/server").TRPCBuiltRouter<{
329
390
  signingAuthorities: import("@trpc/server").TRPCQueryProcedure<{
330
391
  input: void;
331
392
  output: {
332
- ownerDid: string;
333
393
  name: string;
394
+ ownerDid: string;
334
395
  did: string;
335
396
  endpoint: string;
336
397
  _id?: string | undefined;
@@ -505,8 +566,8 @@ export declare const appRouter: import("@trpc/server").TRPCBuiltRouter<{
505
566
  };
506
567
  signingAuthority: {
507
568
  did: string;
508
- ownerDid: string;
509
569
  name: string;
570
+ ownerDid: string;
510
571
  };
511
572
  options?: {
512
573
  created?: string | undefined;
@@ -1166,6 +1227,7 @@ export declare const appRouter: import("@trpc/server").TRPCBuiltRouter<{
1166
1227
  };
1167
1228
  priorEscrowEnrollment: boolean;
1168
1229
  sssActivationState: "active" | "provisional";
1230
+ isWeb3AuthLogin: boolean;
1169
1231
  escrowStale?: "mode-mismatch" | "key-rotated" | undefined;
1170
1232
  escrowBlobIdentity?: string | undefined;
1171
1233
  escrowUpdating?: boolean | undefined;
@@ -1,5 +1,6 @@
1
1
  import 'dotenv/config';
2
2
  import { z } from 'zod';
3
+ export declare const ESCROW_AUTHORITY_TENANT_LABEL: RegExp;
3
4
  export declare const lcaApiEnvironmentShape: {
4
5
  NODE_ENV: z.ZodDefault<z.ZodEnum<{
5
6
  development: "development";
@@ -71,6 +72,7 @@ export declare const lcaApiEnvironmentShape: {
71
72
  ESCROW_ENCLAVE_ACTIVE_KEY_ID: z.ZodPreprocess<z.ZodOptional<z.ZodString>, unknown>;
72
73
  ESCROW_ENCLAVE_REMOTE_URL: z.ZodPreprocess<z.ZodOptional<z.ZodURL>, unknown>;
73
74
  ESCROW_ENCLAVE_REMOTE_TOKEN: z.ZodPreprocess<z.ZodOptional<z.ZodString>, unknown>;
75
+ ESCROW_AUTHORITY_TENANT: z.ZodPipe<z.ZodPipe<z.ZodPreprocess<z.ZodOptional<z.ZodString>, unknown>, z.ZodTransform<string, string | undefined>>, z.ZodString>;
74
76
  ESCROW_RELEASE_KILL_SWITCH: z.ZodPreprocess<z.ZodOptional<z.ZodPipe<z.ZodEnum<{
75
77
  0: "0";
76
78
  1: "1";
@@ -190,6 +192,7 @@ export declare const lcaApiEnvironmentSchema: z.ZodPipe<z.ZodObject<{
190
192
  ESCROW_ENCLAVE_ACTIVE_KEY_ID: z.ZodPreprocess<z.ZodOptional<z.ZodString>, unknown>;
191
193
  ESCROW_ENCLAVE_REMOTE_URL: z.ZodPreprocess<z.ZodOptional<z.ZodURL>, unknown>;
192
194
  ESCROW_ENCLAVE_REMOTE_TOKEN: z.ZodPreprocess<z.ZodOptional<z.ZodString>, unknown>;
195
+ ESCROW_AUTHORITY_TENANT: z.ZodPipe<z.ZodPipe<z.ZodPreprocess<z.ZodOptional<z.ZodString>, unknown>, z.ZodTransform<string, string | undefined>>, z.ZodString>;
193
196
  ESCROW_RELEASE_KILL_SWITCH: z.ZodPreprocess<z.ZodOptional<z.ZodPipe<z.ZodEnum<{
194
197
  0: "0";
195
198
  1: "1";
@@ -249,6 +252,7 @@ export declare const lcaApiEnvironmentSchema: z.ZodPipe<z.ZodObject<{
249
252
  BEDROCK_IMAGE_REGION: "us-west-2";
250
253
  BEDROCK_IMAGE_MODEL_ID: "stability.stable-image-core-v1:1";
251
254
  IMAGE_TIMEOUT_MS: number;
255
+ ESCROW_AUTHORITY_TENANT: string;
252
256
  ESCROW_ENCLAVE_REMOTE_TIMEOUT_MS: number;
253
257
  ESCROW_HOLD_DURATION_MS: number;
254
258
  ESCROW_HOLD_RESTART_MIN_AGE_MS: number;
@@ -315,6 +319,7 @@ export declare const lcaApiEnvironmentSchema: z.ZodPipe<z.ZodObject<{
315
319
  BEDROCK_IMAGE_REGION: "us-west-2";
316
320
  BEDROCK_IMAGE_MODEL_ID: "stability.stable-image-core-v1:1";
317
321
  IMAGE_TIMEOUT_MS: number;
322
+ ESCROW_AUTHORITY_TENANT: string;
318
323
  ESCROW_ENCLAVE_REMOTE_TIMEOUT_MS: number;
319
324
  ESCROW_HOLD_DURATION_MS: number;
320
325
  ESCROW_HOLD_RESTART_MIN_AGE_MS: number;
@@ -390,6 +395,7 @@ export declare const environment: {
390
395
  BEDROCK_IMAGE_REGION: "us-west-2";
391
396
  BEDROCK_IMAGE_MODEL_ID: "stability.stable-image-core-v1:1";
392
397
  IMAGE_TIMEOUT_MS: number;
398
+ ESCROW_AUTHORITY_TENANT: string;
393
399
  ESCROW_ENCLAVE_REMOTE_TIMEOUT_MS: number;
394
400
  ESCROW_HOLD_DURATION_MS: number;
395
401
  ESCROW_HOLD_RESTART_MIN_AGE_MS: number;
@@ -4,6 +4,7 @@ export declare const stages: {
4
4
  dev: {
5
5
  APPLE_OAUTH_CLIENT_IDS: string;
6
6
  DOMAIN_NAME: string;
7
+ NETWORK_BRAIN_SERVICE_URL: string;
7
8
  ESCROW_ENCLAVE_ACTIVE_KEY_ID: string;
8
9
  ESCROW_ENCLAVE_REMOTE_TIMEOUT_MS: string;
9
10
  ESCROW_HOLD_DURATION_MS: string;
@@ -26,6 +27,7 @@ export declare const stages: {
26
27
  };
27
28
  production: {
28
29
  DOMAIN_NAME: string;
30
+ NETWORK_BRAIN_SERVICE_URL: string;
29
31
  POSTMARK_BRAND_NAME: string;
30
32
  POSTMARK_ENDORSEMENT_REQUEST_TEMPLATE_ALIAS: string;
31
33
  POSTMARK_FROM_EMAIL: string;
@@ -38,6 +40,7 @@ export declare const stages: {
38
40
  };
39
41
  'scouts.dev': {
40
42
  DOMAIN_NAME: string;
43
+ NETWORK_BRAIN_SERVICE_URL: string;
41
44
  POSTMARK_BRAND_NAME: string;
42
45
  POSTMARK_FROM_EMAIL: string;
43
46
  POSTMARK_LOGIN_CODE_TEMPLATE_ALIAS: string;
@@ -49,6 +52,7 @@ export declare const stages: {
49
52
  };
50
53
  'scouts.production': {
51
54
  DOMAIN_NAME: string;
55
+ NETWORK_BRAIN_SERVICE_URL: string;
52
56
  POSTMARK_BRAND_NAME: string;
53
57
  POSTMARK_FROM_EMAIL: string;
54
58
  POSTMARK_LOGIN_CODE_TEMPLATE_ALIAS: string;
@@ -0,0 +1,6 @@
1
+ export type TrustedProfileAge = {
2
+ dob?: string | null;
3
+ country?: string | null;
4
+ type?: string | null;
5
+ };
6
+ export declare const isAnalyticsRestricted: (profile: TrustedProfileAge, now?: Date) => boolean;
@@ -0,0 +1 @@
1
+ export declare const getBrainOpenApiBase: (serviceUrl: string) => string;
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,6 @@
1
+ import type { Context } from '@routes';
2
+ export declare const getCurrentProfile: (ctx: Pick<Context, "authorizationHeader" | "tenant" | "user">) => Promise<{
3
+ dob?: string | null | undefined;
4
+ country?: string | null | undefined;
5
+ type?: string | null | undefined;
6
+ } | null>;
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1 @@
1
+ export declare const createRetryingOnce: <T>(task: () => Promise<T>) => (() => Promise<T>);
@@ -0,0 +1 @@
1
+ export {};
package/dist/index.js CHANGED
@@ -1,4 +1,4 @@
1
1
  [diffend] Oversized file quarantined before diffing.
2
2
  name: package/dist/index.js
3
- size: 167146968 bytes
4
- sha256: e837ca38f23dbcecdd16d4cc36272db9d19b6ffac4d5142e91b4a5fd4c9af1cd
3
+ size: 179678813 bytes
4
+ sha256: 1f8a341fa28f633e719f9c44c9f3aa0bcd110dc7a94cba612f33f034d7de4b2e
@@ -154,6 +154,7 @@ export type EscrowHold = z.infer<typeof EscrowHoldValidator>;
154
154
  export type CreateEscrowHoldInput = Omit<z.input<typeof EscrowHoldValidator>, '_id' | 'status' | 'createdAt' | 'updatedAt' | 'notifications' | 'cancelledAt' | 'cancelledBy' | 'completedAt' | 'cancelReason' | 'cancelTokenUsedAt' | 'enclaveCancelPendingAt' | 'enclaveCancelConfirmedAt' | 'enclaveCancelAttempts'>;
155
155
  export declare const getEscrowHoldsCollection: () => Collection<EscrowHold>;
156
156
  export declare const createEscrowHoldsIndexes: () => Promise<void>;
157
+ export declare const ensureEscrowHoldsIndexes: () => Promise<void>;
157
158
  export declare const createEscrowHold: (input: CreateEscrowHoldInput) => Promise<EscrowHold>;
158
159
  export declare const findPendingEscrowHoldByAuthProvider: (authProvider: AuthProviderMapping, releasePolicy?: EscrowHold["releasePolicy"]) => Promise<EscrowHold | null>;
159
160
  export declare const findEscrowHoldById: (id: string) => Promise<EscrowHold | null>;
@@ -0,0 +1 @@
1
+ export {};