@learncard/network-brain-service 3.17.0 → 3.19.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 +4 -0
- package/AGENTS.md +177 -159
- package/CHANGELOG.md +148 -0
- package/README.md +26 -8
- package/compose.inbox.yml +7 -0
- package/dist/accesslayer/app-store-listing/relationships/read.d.ts.map +1 -1
- package/dist/accesslayer/auth-grant/read.d.ts +1 -0
- package/dist/accesslayer/auth-grant/read.d.ts.map +1 -1
- package/dist/accesslayer/consentflowcontract/relationships/create.d.ts +3 -2
- package/dist/accesslayer/consentflowcontract/relationships/create.d.ts.map +1 -1
- package/dist/accesslayer/consentflowcontract/relationships/read.d.ts +1 -0
- package/dist/accesslayer/consentflowcontract/relationships/read.d.ts.map +1 -1
- package/dist/accesslayer/consentflowcontract/relationships/update.d.ts +5 -3
- package/dist/accesslayer/consentflowcontract/relationships/update.d.ts.map +1 -1
- package/dist/accesslayer/credential/create.d.ts +2 -1
- package/dist/accesslayer/credential/create.d.ts.map +1 -1
- package/dist/accesslayer/credential/create.test.d.ts +2 -0
- package/dist/accesslayer/credential/create.test.d.ts.map +1 -0
- package/dist/accesslayer/credential/relationships/read.d.ts +1 -0
- package/dist/accesslayer/credential/relationships/read.d.ts.map +1 -1
- package/dist/accesslayer/credential-activity/create.d.ts.map +1 -1
- package/dist/accesslayer/credential-refresh/email-delivery.d.ts +18 -0
- package/dist/accesslayer/credential-refresh/email-delivery.d.ts.map +1 -0
- package/dist/accesslayer/credential-refresh/index.d.ts +1 -0
- package/dist/accesslayer/credential-refresh/index.d.ts.map +1 -1
- package/dist/accesslayer/credential-refresh/read.d.ts.map +1 -1
- package/dist/accesslayer/credential-refresh/update.d.ts.map +1 -1
- package/dist/accesslayer/inbox-batch/store.d.ts +33 -0
- package/dist/accesslayer/inbox-batch/store.d.ts.map +1 -0
- package/dist/accesslayer/inbox-credential/create.d.ts +5 -0
- package/dist/accesslayer/inbox-credential/create.d.ts.map +1 -1
- package/dist/accesslayer/inbox-credential/refresh.d.ts +71 -0
- package/dist/accesslayer/inbox-credential/refresh.d.ts.map +1 -0
- package/dist/accesslayer/inbox-credential/update.d.ts +1 -1
- package/dist/accesslayer/inbox-credential/update.d.ts.map +1 -1
- package/dist/cache/atomic-counter.test-helpers.d.ts +3 -0
- package/dist/cache/atomic-counter.test-helpers.d.ts.map +1 -0
- package/dist/cache/atomic-counter.test.d.ts +2 -0
- package/dist/cache/atomic-counter.test.d.ts.map +1 -0
- package/dist/cache/index.d.ts +1 -1
- package/dist/cache/index.d.ts.map +1 -1
- package/dist/config/environment.d.ts +46 -0
- package/dist/config/environment.d.ts.map +1 -1
- package/dist/credential-refresh.d.ts.map +1 -1
- package/dist/didkit_wasm_bg.wasm +0 -0
- package/dist/helpers/activity.helpers.d.ts +4 -0
- package/dist/helpers/activity.helpers.d.ts.map +1 -1
- package/dist/helpers/boost-storage.helpers.test.d.ts +2 -0
- package/dist/helpers/boost-storage.helpers.test.d.ts.map +1 -0
- package/dist/helpers/boost.helpers.d.ts +3 -7
- package/dist/helpers/boost.helpers.d.ts.map +1 -1
- package/dist/helpers/contact-method.helpers.d.ts.map +1 -1
- package/dist/helpers/contact-method.helpers.test.d.ts +2 -0
- package/dist/helpers/contact-method.helpers.test.d.ts.map +1 -0
- package/dist/helpers/credential-refresh-email-content.helpers.d.ts +44 -0
- package/dist/helpers/credential-refresh-email-content.helpers.d.ts.map +1 -0
- package/dist/helpers/credential-refresh-email-content.helpers.test.d.ts +2 -0
- package/dist/helpers/credential-refresh-email-content.helpers.test.d.ts.map +1 -0
- package/dist/helpers/credential-refresh-email.helpers.d.ts +17 -0
- package/dist/helpers/credential-refresh-email.helpers.d.ts.map +1 -0
- package/dist/helpers/credential-refresh-proof.helpers.d.ts +4 -0
- package/dist/helpers/credential-refresh-proof.helpers.d.ts.map +1 -0
- package/dist/helpers/credential-refresh-proof.helpers.test.d.ts +2 -0
- package/dist/helpers/credential-refresh-proof.helpers.test.d.ts.map +1 -0
- package/dist/helpers/credential-refresh.helpers.d.ts +45 -2
- package/dist/helpers/credential-refresh.helpers.d.ts.map +1 -1
- package/dist/helpers/credential.helpers.d.ts +4 -2
- package/dist/helpers/credential.helpers.d.ts.map +1 -1
- package/dist/helpers/finalize-inbox.helpers.d.ts.map +1 -1
- package/dist/helpers/inbox-batch-http.helpers.d.ts +15 -0
- package/dist/helpers/inbox-batch-http.helpers.d.ts.map +1 -0
- package/dist/helpers/inbox-batch-http.helpers.test.d.ts +2 -0
- package/dist/helpers/inbox-batch-http.helpers.test.d.ts.map +1 -0
- package/dist/helpers/inbox-batch-status.helpers.d.ts +3 -0
- package/dist/helpers/inbox-batch-status.helpers.d.ts.map +1 -0
- package/dist/helpers/inbox-batch.helpers.d.ts +10 -0
- package/dist/helpers/inbox-batch.helpers.d.ts.map +1 -0
- package/dist/helpers/inbox-batch.helpers.test.d.ts +2 -0
- package/dist/helpers/inbox-batch.helpers.test.d.ts.map +1 -0
- package/dist/helpers/inbox-issuance-error.helpers.d.ts +13 -0
- package/dist/helpers/inbox-issuance-error.helpers.d.ts.map +1 -0
- package/dist/helpers/inbox-queue.helpers.d.ts +11 -0
- package/dist/helpers/inbox-queue.helpers.d.ts.map +1 -0
- package/dist/helpers/inbox-queue.helpers.test.d.ts +2 -0
- package/dist/helpers/inbox-queue.helpers.test.d.ts.map +1 -0
- package/dist/helpers/inbox-refresh.helpers.d.ts +186 -0
- package/dist/helpers/inbox-refresh.helpers.d.ts.map +1 -0
- package/dist/helpers/inbox.helpers.d.ts +9 -1
- package/dist/helpers/inbox.helpers.d.ts.map +1 -1
- package/dist/helpers/issuedCredentialStatus.helpers.d.ts +5 -0
- package/dist/helpers/issuedCredentialStatus.helpers.d.ts.map +1 -0
- package/dist/helpers/notifications.helpers.d.ts.map +1 -1
- package/dist/helpers/rateLimit.helpers.d.ts.map +1 -1
- package/dist/helpers/refresh-send-intent.helpers.d.ts +51 -0
- package/dist/helpers/refresh-send-intent.helpers.d.ts.map +1 -0
- package/dist/helpers/shim.d.ts +1 -1
- package/dist/helpers/shim.d.ts.map +1 -1
- package/dist/helpers/signingAuthority.helpers.d.ts +3 -2
- package/dist/helpers/signingAuthority.helpers.d.ts.map +1 -1
- package/dist/helpers/signingAuthority.helpers.test.d.ts +2 -0
- package/dist/helpers/signingAuthority.helpers.test.d.ts.map +1 -0
- package/dist/helpers/status-list.helpers.d.ts.map +1 -1
- package/dist/helpers/status-list.helpers.test.d.ts +2 -0
- package/dist/helpers/status-list.helpers.test.d.ts.map +1 -0
- package/dist/inbox-queue-local.d.ts +2 -0
- package/dist/inbox-queue-local.d.ts.map +1 -0
- package/dist/index.js +2 -2
- package/dist/models/AuthGrant.d.ts.map +1 -1
- package/dist/models/ConsentFlowTerms.d.ts.map +1 -1
- package/dist/models/ConsentFlowTransaction.d.ts.map +1 -1
- package/dist/models/Credential.d.ts +1 -0
- package/dist/models/Credential.d.ts.map +1 -1
- package/dist/models/CredentialActivity.d.ts +1 -0
- package/dist/models/CredentialActivity.d.ts.map +1 -1
- package/dist/models/CredentialRefresh.d.ts +5 -2
- package/dist/models/CredentialRefresh.d.ts.map +1 -1
- package/dist/models/InboxCredential.d.ts +1 -0
- package/dist/models/InboxCredential.d.ts.map +1 -1
- package/dist/models/credential-refresh-constraints.d.ts.map +1 -1
- package/dist/openapi.d.ts.map +1 -1
- package/dist/routes/act-as.test.d.ts +2 -0
- package/dist/routes/act-as.test.d.ts.map +1 -0
- package/dist/routes/activity.d.ts +3 -0
- package/dist/routes/activity.d.ts.map +1 -1
- package/dist/routes/app-store.d.ts.map +1 -1
- package/dist/routes/auth-grants.d.ts +10 -0
- package/dist/routes/auth-grants.d.ts.map +1 -1
- package/dist/routes/boosts.d.ts +506 -12
- package/dist/routes/boosts.d.ts.map +1 -1
- package/dist/routes/contracts.d.ts +48 -2
- package/dist/routes/contracts.d.ts.map +1 -1
- package/dist/routes/credential-refreshes.d.ts +4 -4
- package/dist/routes/credential-refreshes.d.ts.map +1 -1
- package/dist/routes/credentials.d.ts +8 -2
- package/dist/routes/credentials.d.ts.map +1 -1
- package/dist/routes/did-metadata.d.ts +8 -8
- package/dist/routes/federation.d.ts +2 -2
- package/dist/routes/inbox.d.ts +973 -31
- package/dist/routes/inbox.d.ts.map +1 -1
- package/dist/routes/index.d.ts +47 -127
- package/dist/routes/index.d.ts.map +1 -1
- package/dist/routes/presentations.d.ts +6 -6
- package/dist/routes/storage.d.ts +63 -63
- package/dist/routes/test.d.ts +6 -0
- package/dist/routes/test.d.ts.map +1 -1
- package/dist/routes/workflows.d.ts +14 -14
- package/dist/routes/workflows.d.ts.map +1 -1
- package/dist/services/delivery/adapters/postmark.adapter.d.ts.map +1 -1
- package/dist/services/delivery/adapters/postmark.adapter.test.d.ts +2 -0
- package/dist/services/delivery/adapters/postmark.adapter.test.d.ts.map +1 -0
- package/dist/services/registry/registry.factory.d.ts.map +1 -1
- package/dist/types/activity.d.ts +4 -0
- package/dist/types/activity.d.ts.map +1 -1
- package/dist/types/consentflowcontract.d.ts +22 -8
- package/dist/types/consentflowcontract.d.ts.map +1 -1
- package/dist/types/credential-refresh.d.ts +10 -1
- package/dist/types/credential-refresh.d.ts.map +1 -1
- package/dist/types/credential.d.ts +7 -0
- package/dist/types/credential.d.ts.map +1 -1
- package/dist/types/did-metadata.d.ts +2 -2
- package/dist/types/inbox-batch.d.ts +38 -0
- package/dist/types/inbox-batch.d.ts.map +1 -0
- package/inbox-e2e-setup.ts +53 -1
- package/inbox-elasticmq.conf +10 -0
- package/lambda.ts +61 -16
- package/package.json +18 -16
- package/scripts/setup-inbox-queues.ts +60 -0
- package/serverless-local.yml +2 -0
- package/serverless.yml +75 -0
- package/src/accesslayer/app-store-listing/relationships/read.ts +4 -3
- package/src/accesslayer/auth-grant/read.ts +2 -1
- package/src/accesslayer/consentflowcontract/relationships/create.ts +15 -5
- package/src/accesslayer/consentflowcontract/relationships/read.ts +161 -63
- package/src/accesslayer/consentflowcontract/relationships/update.ts +43 -10
- package/src/accesslayer/credential/create.test.ts +141 -0
- package/src/accesslayer/credential/create.ts +27 -3
- package/src/accesslayer/credential-activity/create.ts +1 -0
- package/src/accesslayer/credential-refresh/email-delivery.ts +116 -0
- package/src/accesslayer/credential-refresh/index.ts +1 -0
- package/src/accesslayer/credential-refresh/read.ts +8 -2
- package/src/accesslayer/credential-refresh/update.ts +4 -0
- package/src/accesslayer/inbox-batch/store.ts +469 -0
- package/src/accesslayer/inbox-credential/create.ts +51 -19
- package/src/accesslayer/inbox-credential/refresh.ts +221 -0
- package/src/accesslayer/inbox-credential/update.ts +1 -1
- package/src/cache/atomic-counter.test-helpers.ts +27 -0
- package/src/cache/atomic-counter.test.ts +6 -0
- package/src/cache/index.ts +25 -13
- package/src/config/environment.ts +26 -0
- package/src/credential-refresh.ts +5 -1
- package/src/docker-entry.ts +8 -0
- package/src/helpers/activity.helpers.ts +4 -0
- package/src/helpers/app-counter.helpers.ts +1 -1
- package/src/helpers/boost-storage.helpers.test.ts +242 -0
- package/src/helpers/boost.helpers.ts +58 -437
- package/src/helpers/contact-method.helpers.test.ts +43 -0
- package/src/helpers/contact-method.helpers.ts +3 -4
- package/src/helpers/credential-refresh-email-content.helpers.test.ts +222 -0
- package/src/helpers/credential-refresh-email-content.helpers.ts +155 -0
- package/src/helpers/credential-refresh-email.helpers.ts +201 -0
- package/src/helpers/credential-refresh-proof.helpers.test.ts +103 -0
- package/src/helpers/credential-refresh-proof.helpers.ts +43 -0
- package/src/helpers/credential-refresh.helpers.ts +385 -107
- package/src/helpers/credential.helpers.ts +6 -2
- package/src/helpers/finalize-inbox.helpers.ts +39 -22
- package/src/helpers/inbox-batch-http.helpers.test.ts +188 -0
- package/src/helpers/inbox-batch-http.helpers.ts +150 -0
- package/src/helpers/inbox-batch-status.helpers.ts +11 -0
- package/src/helpers/inbox-batch.helpers.test.ts +668 -0
- package/src/helpers/inbox-batch.helpers.ts +374 -0
- package/src/helpers/inbox-issuance-error.helpers.ts +25 -0
- package/src/helpers/inbox-queue.helpers.test.ts +253 -0
- package/src/helpers/inbox-queue.helpers.ts +372 -0
- package/src/helpers/inbox-refresh.helpers.ts +432 -0
- package/src/helpers/inbox.helpers.ts +301 -50
- package/src/helpers/issuedCredentialStatus.helpers.ts +11 -0
- package/src/helpers/notifications.helpers.ts +4 -2
- package/src/helpers/rateLimit.helpers.ts +2 -5
- package/src/helpers/refresh-send-intent.helpers.ts +273 -0
- package/src/helpers/shim.ts +32 -31
- package/src/helpers/signingAuthority.helpers.test.ts +198 -0
- package/src/helpers/signingAuthority.helpers.ts +41 -18
- package/src/helpers/status-list.helpers.test.ts +77 -0
- package/src/helpers/status-list.helpers.ts +31 -8
- package/src/inbox-queue-local.ts +28 -0
- package/src/models/AuthGrant.ts +1 -0
- package/src/models/ConsentFlowTerms.ts +4 -0
- package/src/models/ConsentFlowTransaction.ts +4 -0
- package/src/models/Credential.ts +1 -0
- package/src/models/CredentialActivity.ts +3 -5
- package/src/models/CredentialRefresh.ts +4 -2
- package/src/models/InboxCredential.ts +2 -0
- package/src/models/credential-refresh-constraints.test.ts +8 -3
- package/src/models/credential-refresh-constraints.ts +5 -0
- package/src/openapi.ts +8 -0
- package/src/routes/act-as.test.ts +239 -0
- package/src/routes/app-store.ts +69 -52
- package/src/routes/auth-grants.ts +13 -11
- package/src/routes/boosts.ts +943 -120
- package/src/routes/contracts.ts +55 -7
- package/src/routes/credential-refreshes.test.ts +8 -2
- package/src/routes/credential-refreshes.ts +4 -1
- package/src/routes/credentials.ts +2 -1
- package/src/routes/inbox.ts +95 -49
- package/src/routes/index.ts +164 -88
- package/src/routes/skills.ts +1 -1
- package/src/routes/workflows.ts +80 -31
- package/src/services/delivery/adapters/postmark.adapter.test.ts +54 -0
- package/src/services/delivery/adapters/postmark.adapter.ts +12 -0
- package/src/services/registry/registry.factory.ts +2 -1
- package/src/types/activity.ts +2 -0
- package/src/types/consentflowcontract.ts +21 -3
- package/src/types/credential-refresh.ts +21 -2
- package/src/types/credential.ts +13 -1
- package/src/types/inbox-batch.ts +46 -0
- package/test/app-store-listing.spec.ts +66 -30
- package/test/atomic-counter.e2e.ts +12 -0
- package/test/auth-grant.spec.ts +21 -21
- package/test/bitstring-status-list.spec.ts +12 -9
- package/test/consentflow.spec.ts +453 -26
- package/test/credential-refresh-email.spec.ts +663 -0
- package/test/credential-refresh-publication.spec.ts +13 -5
- package/test/guardian-gated-routes.spec.ts +337 -180
- package/test/helpers/inbox-e2e-environment.ts +6 -0
- package/test/inbox-batch.spec.ts +1338 -0
- package/test/inbox-refresh.spec.ts +534 -0
- package/test/inbox-security.e2e.ts +48 -3
- package/test/openapi.spec.ts +41 -1
- package/test/refresh-send.spec.ts +1799 -0
- package/test/registry.spec.ts +56 -11
- package/test/revoke-suspend-per-instance.spec.ts +9 -1
- package/vitest.config.ts +13 -0
- package/vitest.inbox-e2e.config.ts +5 -1
- package/vitest.integration.config.ts +2 -0
package/.env.example
CHANGED
|
@@ -22,6 +22,10 @@ OIDC_EXPECTED_AUDIENCE=
|
|
|
22
22
|
|
|
23
23
|
AWS_REGION=
|
|
24
24
|
NOTIFICATIONS_QUEUE_URL=
|
|
25
|
+
INBOX_QUEUE_URL=
|
|
26
|
+
INBOX_DEAD_LETTER_QUEUE_URL=
|
|
27
|
+
# Set only for a local SQS emulator; production uses the AWS endpoint and IAM role.
|
|
28
|
+
INBOX_QUEUE_ENDPOINT=
|
|
25
29
|
NOTIFICATIONS_QUEUE_POLL_URL=
|
|
26
30
|
NOTIFICATIONS_SERVICE_WEBHOOK_URL=
|
|
27
31
|
NOTIFICATIONS_SERVICE_PORT=5100
|
package/AGENTS.md
CHANGED
|
@@ -2,54 +2,54 @@
|
|
|
2
2
|
|
|
3
3
|
## Build & Development Commands
|
|
4
4
|
|
|
5
|
-
-
|
|
6
|
-
-
|
|
7
|
-
-
|
|
8
|
-
-
|
|
9
|
-
-
|
|
5
|
+
- Build: `bun run build` or `bun run build:docker`
|
|
6
|
+
- Dev: `bun run dev` - Watches files and rebuilds
|
|
7
|
+
- Start server: `bun run start` - Runs local server on port 3000
|
|
8
|
+
- Start on port 4000: `bun run start-p-4000`
|
|
9
|
+
- Deploy: `bun run serverless-deploy`
|
|
10
10
|
|
|
11
11
|
### Skill Framework Seeding
|
|
12
12
|
|
|
13
|
-
-
|
|
14
|
-
-
|
|
15
|
-
-
|
|
16
|
-
-
|
|
17
|
-
-
|
|
18
|
-
-
|
|
19
|
-
-
|
|
13
|
+
- Auto-bootstrap seeds default public skill frameworks in non-production when the database has no `SkillFramework` nodes.
|
|
14
|
+
- Disable auto-bootstrap with `SKIP_SKILL_FRAMEWORK_SEED=true`.
|
|
15
|
+
- Re-seed manually with `bun run skill-frameworks seed [local|staging]` from `services/learn-card-network/brain-service`.
|
|
16
|
+
- Grant framework admin access to an existing profile with `bun run skill-frameworks add-admin [local|staging]` and enter the profile id when prompted.
|
|
17
|
+
- If no stage is provided, the CLI defaults to `local` and prints a note.
|
|
18
|
+
- Use `SKILL_FRAMEWORK_SEED_OWNER_PROFILE_ID` to override the default `network-seed` owner profile when needed.
|
|
19
|
+
- The seed command is idempotent and will not duplicate framework or skill nodes.
|
|
20
20
|
|
|
21
21
|
## Test Commands
|
|
22
22
|
|
|
23
|
-
-
|
|
24
|
-
-
|
|
25
|
-
-
|
|
26
|
-
-
|
|
27
|
-
-
|
|
28
|
-
-
|
|
23
|
+
- Run all tests: `bun run test`
|
|
24
|
+
- Run all tests once (non-watch): `bun run test -- run` (equivalent to `vitest run`)
|
|
25
|
+
- Watch mode: `bun run test:watch`
|
|
26
|
+
- Run single test: `bun run test test/consentflow.spec.ts`
|
|
27
|
+
- Run single test once (non-watch): `bun run test -- run test/consentflow.spec.ts`
|
|
28
|
+
- Run specific test: `bun run test -t "should allow setting and retrieving the image field for a contract"`
|
|
29
29
|
|
|
30
30
|
## Code Style Guidelines
|
|
31
31
|
|
|
32
|
-
-
|
|
33
|
-
-
|
|
34
|
-
-
|
|
35
|
-
-
|
|
36
|
-
-
|
|
37
|
-
-
|
|
38
|
-
-
|
|
39
|
-
-
|
|
40
|
-
-
|
|
41
|
-
-
|
|
42
|
-
-
|
|
32
|
+
- **TypeScript**: Strict typing with interfaces/types defined in `src/types/`
|
|
33
|
+
- **Imports**: Use path aliases (`@helpers/*`, `@models`, `@routes`, etc.)
|
|
34
|
+
- **Error Handling**: Use trpc error codes (`UNAUTHORIZED`, `BAD_REQUEST`, etc.)
|
|
35
|
+
- **Naming**:
|
|
36
|
+
- PascalCase for classes, types, interfaces
|
|
37
|
+
- camelCase for variables, functions, methods
|
|
38
|
+
- **Models Structure**: Database models in `src/models/` with relationships defined
|
|
39
|
+
- **Access Layer**: Database operations go in `src/accesslayer/` folders
|
|
40
|
+
- **Routes**: API endpoints defined in `src/routes/` using tRPC
|
|
41
|
+
- **Variable Exports**: Avoid one-line default exports; use named exports
|
|
42
|
+
- **Return Types**: Always specify explicit return types for functions
|
|
43
43
|
|
|
44
44
|
## ConsentFlow Architecture
|
|
45
45
|
|
|
46
46
|
ConsentFlow is a consent management system where:
|
|
47
47
|
|
|
48
|
-
-
|
|
49
|
-
-
|
|
50
|
-
-
|
|
51
|
-
-
|
|
52
|
-
-
|
|
48
|
+
- **Profiles** can create and consent to **Contracts**
|
|
49
|
+
- **Contracts** define data access requirements (read/write permissions)
|
|
50
|
+
- **Terms** record a profile's consent to a contract
|
|
51
|
+
- **Transactions** record actions taken against Terms (consent, withdraw, update, sync)
|
|
52
|
+
- **Credentials** can be issued through contract consent or synced by users from their existing credentials
|
|
53
53
|
|
|
54
54
|
### Relationship Flow
|
|
55
55
|
|
|
@@ -62,62 +62,62 @@ ConsentFlow is a consent management system where:
|
|
|
62
62
|
|
|
63
63
|
### Transaction Types
|
|
64
64
|
|
|
65
|
-
-
|
|
66
|
-
-
|
|
67
|
-
-
|
|
68
|
-
-
|
|
69
|
-
-
|
|
65
|
+
- **Consent Transaction**: Created when a user initially consents to a contract
|
|
66
|
+
- **Update Transaction**: Created when a user updates their terms
|
|
67
|
+
- **Withdraw Transaction**: Created when a user withdraws consent
|
|
68
|
+
- **Sync Transaction**: Created when a user syncs credentials to specific categories
|
|
69
|
+
- **Write Transaction**: Created when credentials are issued through a contract (usually via auto-boosts)
|
|
70
70
|
|
|
71
71
|
### Neo4j & Neogma Integration
|
|
72
72
|
|
|
73
|
-
-
|
|
74
|
-
-
|
|
75
|
-
-
|
|
76
|
-
-
|
|
77
|
-
-
|
|
73
|
+
- Neo4j is used as the graph database for storing entities and relationships
|
|
74
|
+
- Neogma serves as an ORM providing model definitions with schemas
|
|
75
|
+
- Models define node labels, properties, relationships, and primary keys
|
|
76
|
+
- Relationships are defined with direction, name, and target model
|
|
77
|
+
- QueryBuilder is used for complex queries with relationship traversal
|
|
78
78
|
|
|
79
79
|
### Handling Nested Objects
|
|
80
80
|
|
|
81
|
-
-
|
|
82
|
-
-
|
|
83
|
-
-
|
|
84
|
-
-
|
|
85
|
-
-
|
|
81
|
+
- Neo4j can't store deeply nested objects directly
|
|
82
|
+
- The system uses:
|
|
83
|
+
- `flattenObject()`: Converts nested structures to dot-notation keys
|
|
84
|
+
- `inflateObject()`: Restores flattened objects to nested structure
|
|
85
|
+
- Example conversion:
|
|
86
86
|
```json
|
|
87
87
|
// Original
|
|
88
88
|
{ "contract": { "read": { "personal": { "name": true } } } }
|
|
89
89
|
// Flattened
|
|
90
90
|
{ "contract.read.personal.name": true }
|
|
91
91
|
```
|
|
92
|
-
-
|
|
92
|
+
- This pattern is essential for storing complex objects in Neo4j
|
|
93
93
|
|
|
94
94
|
### Common Data Flow Patterns
|
|
95
95
|
|
|
96
|
-
-
|
|
97
|
-
-
|
|
98
|
-
-
|
|
99
|
-
-
|
|
96
|
+
- **Data Queries**: Use `convertDataQueryToNeo4jQuery()` for Neo4j compatibility
|
|
97
|
+
- **Category Filtering**: Use `shouldIncludeCategory()` to filter credentials by category
|
|
98
|
+
- **Relationship Creation**: Follow the pattern shown in the relationship creation functions
|
|
99
|
+
- **Transaction Recording**: Create transactions for all important actions
|
|
100
100
|
|
|
101
101
|
### Signing Authorities
|
|
102
102
|
|
|
103
|
-
-
|
|
104
|
-
-
|
|
105
|
-
-
|
|
106
|
-
-
|
|
107
|
-
-
|
|
108
|
-
-
|
|
109
|
-
-
|
|
103
|
+
- Signing Authorities are external services that sign credentials on behalf of profiles
|
|
104
|
+
- Each profile can register multiple signing authorities via `usesSigningAuthority` relationships
|
|
105
|
+
- Signing authorities are stored in the SigningAuthority model with an endpoint URL
|
|
106
|
+
- The relationship between Profile and SigningAuthority contains:
|
|
107
|
+
- `name`: A name identifier for the signing authority (e.g., "default")
|
|
108
|
+
- `did`: The DID that the signing authority will use to sign credentials
|
|
109
|
+
- When issuing credentials through a signing authority:
|
|
110
110
|
1. The system makes a request to the signing authority's endpoint
|
|
111
111
|
2. It passes the unsigned credential, owner DID, and signing authority details
|
|
112
112
|
3. The signing authority signs the credential and returns it
|
|
113
113
|
4. The `issueCredentialWithSigningAuthority()` helper handles this flow
|
|
114
|
-
-
|
|
114
|
+
- A common pattern is to get a signing authority with `getSigningAuthorityForUserByName(profile, endpoint, name)`
|
|
115
115
|
|
|
116
116
|
### AutoBoosts
|
|
117
117
|
|
|
118
|
-
-
|
|
119
|
-
-
|
|
120
|
-
-
|
|
118
|
+
- AutoBoosts are credentials automatically issued when a user consents to a contract
|
|
119
|
+
- An existing Boost is attached to a Contract via the `AUTO_RECEIVE` relationship
|
|
120
|
+
- When creating a contract with autoboosts, an array of autoboosts must be provided, each with a boost URI and signing authority:
|
|
121
121
|
```typescript
|
|
122
122
|
{
|
|
123
123
|
// other contract fields
|
|
@@ -139,24 +139,24 @@ ConsentFlow is a consent management system where:
|
|
|
139
139
|
];
|
|
140
140
|
}
|
|
141
141
|
```
|
|
142
|
-
-
|
|
143
|
-
-
|
|
142
|
+
- The signing authority information is stored on each `AUTO_RECEIVE` relationship
|
|
143
|
+
- When a user consents to a contract, all attached AutoBoosts are:
|
|
144
144
|
1. Issued using the specified signing authority of the contract owner
|
|
145
145
|
2. Sent to the consenting user
|
|
146
146
|
3. Recorded with a 'write' action transaction on the terms
|
|
147
|
-
-
|
|
148
|
-
-
|
|
149
|
-
-
|
|
150
|
-
-
|
|
151
|
-
-
|
|
152
|
-
-
|
|
153
|
-
-
|
|
147
|
+
- AutoBoosts are processed during:
|
|
148
|
+
- Initial consent via `consentToContract()`
|
|
149
|
+
- Re-consent via `reconsentTerms()`
|
|
150
|
+
- Terms update via `updateTerms()`
|
|
151
|
+
- The contract owner MUST have the specified signing authority configured for each autoboost to work correctly
|
|
152
|
+
- If a specified signing authority doesn't exist, that specific autoboost will be skipped with an error message
|
|
153
|
+
- Multiple autoboosts can use the same signing authority or different ones
|
|
154
154
|
|
|
155
155
|
### Credential Syncing
|
|
156
156
|
|
|
157
|
-
-
|
|
158
|
-
-
|
|
159
|
-
-
|
|
157
|
+
- Users can sync their existing credentials to a contract they've consented to
|
|
158
|
+
- Syncing uses the `syncCredentialsToContract` API endpoint
|
|
159
|
+
- The sync process:
|
|
160
160
|
1. User identifies which credentials to share in which categories
|
|
161
161
|
2. User calls `syncCredentialsToContract` with `termsUri` and a map of categories to credential URIs:
|
|
162
162
|
```typescript
|
|
@@ -175,38 +175,56 @@ ConsentFlow is a consent management system where:
|
|
|
175
175
|
4. System creates a 'sync' transaction with the categorized credentials
|
|
176
176
|
5. System updates the terms by adding the synced credentials to the shared arrays for each category
|
|
177
177
|
6. Contract owner receives a notification about the sync action
|
|
178
|
-
-
|
|
179
|
-
-
|
|
180
|
-
-
|
|
178
|
+
- The transaction and terms data use the same structure format for categories
|
|
179
|
+
- All synced credentials are added to the existing shared arrays (if any) for each category
|
|
180
|
+
- Duplicate credential URIs are automatically deduplicated
|
|
181
181
|
|
|
182
182
|
## Service-Level Boost Sending via Signing Authority
|
|
183
183
|
|
|
184
|
+
New Boost issuance always stores the issuer's payload unchanged: `sendBoost()` does
|
|
185
|
+
not decrypt or counter-sign credentials. There is no rollout flag or legacy issuance
|
|
186
|
+
branch. Signing-authority encryption targets the subject and issuing owner, excluding
|
|
187
|
+
brain; the trusted signing authority still has access. Claim links and consent
|
|
188
|
+
autoboosts also request encryption. INSTANCE_OF, sent/received, contract/listing,
|
|
189
|
+
acceptance, and notification behavior is preserved.
|
|
190
|
+
|
|
191
|
+
VerifyBoost supports both existing wrapped credentials and new unwrapped credentials.
|
|
192
|
+
Display and unwrap-if-present helpers retain legacy support. The post-message handler
|
|
193
|
+
only passes `boostCredential` as a modal prop; it does not construct or sign a wrapper.
|
|
194
|
+
Compatibility tests create legacy fixtures independently of production issuance.
|
|
195
|
+
|
|
196
|
+
For encrypted storage, public status-list coordinates are retained from the unsigned SA request in an
|
|
197
|
+
in-memory sidecar and persisted as `Credential.statusEntries`. Revocation and suspension
|
|
198
|
+
use these coordinates without decrypting the credential. Issuers must allocate status
|
|
199
|
+
entries before signing; the SDK's `sendBoost` already does this. Inbox finalization and
|
|
200
|
+
VC-API presentations still explicitly request plaintext for their existing protocols.
|
|
201
|
+
|
|
184
202
|
### Context
|
|
185
203
|
|
|
186
204
|
For certain workflows, such as sending a boost (credential) to another profile via HTTP service routes, the server cannot access the user's key material for security reasons. To enable credential issuance in these cases, the system supports **Signing Authorities**.
|
|
187
205
|
|
|
188
206
|
### Signing Authorities & Credential Issuance
|
|
189
207
|
|
|
190
|
-
-
|
|
191
|
-
-
|
|
192
|
-
-
|
|
208
|
+
- **Signing Authority**: An external service registered by a user/profile that can sign credentials on their behalf.
|
|
209
|
+
- Users register signing authorities by specifying an endpoint and a name.
|
|
210
|
+
- When sending a boost via HTTP, the service uses the registered signing authority to sign the credential and deliver it to the recipient.
|
|
193
211
|
|
|
194
212
|
### Example: `/boost/send/via-signing-authority` Route
|
|
195
213
|
|
|
196
|
-
-
|
|
197
|
-
-
|
|
214
|
+
- **Input:** `{ profileId, boostUri, signingAuthority: { name, endpoint } }`
|
|
215
|
+
- **Flow:**
|
|
198
216
|
1. Fetch the boost by URI.
|
|
199
217
|
2. Fetch the recipient profile.
|
|
200
218
|
3. Prepare an unsigned VC for the recipient.
|
|
201
219
|
4. Use the specified signing authority to sign the VC.
|
|
202
220
|
5. Deliver the signed VC to the recipient via the network.
|
|
203
|
-
-
|
|
221
|
+
- **Testing:** E2E tests should POST directly to the route using fetch, after registering a signing authority for the sender.
|
|
204
222
|
|
|
205
223
|
### Best Practices for AI Assistants
|
|
206
224
|
|
|
207
|
-
-
|
|
208
|
-
-
|
|
209
|
-
-
|
|
225
|
+
- Always check for the availability of key material before issuing credentials server-side. If unavailable, require a signing authority.
|
|
226
|
+
- Document the expected flow, security implications, and how to set up signing authorities in both code and tests.
|
|
227
|
+
- Prefer direct HTTP calls in E2E tests for service-only routes, and demonstrate signing authority setup in test code.
|
|
210
228
|
|
|
211
229
|
## Profile Connection Invites
|
|
212
230
|
|
|
@@ -214,25 +232,25 @@ LearnCard supports creating and managing profile connection invitation links wit
|
|
|
214
232
|
|
|
215
233
|
### Overview
|
|
216
234
|
|
|
217
|
-
-
|
|
235
|
+
- Invites are identified by a `challenge` string and are scoped to the profile that created them.
|
|
218
236
|
|
|
219
|
-
-
|
|
237
|
+
- Each invite tracks `maxUses` and `usesRemaining`.
|
|
220
238
|
|
|
221
|
-
-
|
|
239
|
+
- Unlimited invites have `maxUses = null` and `usesRemaining = null`.
|
|
222
240
|
|
|
223
|
-
-
|
|
241
|
+
- Expiration is expressed in seconds. `0` means no expiration.
|
|
224
242
|
|
|
225
243
|
### Routes
|
|
226
244
|
|
|
227
245
|
#### Generate Invite
|
|
228
246
|
|
|
229
|
-
-
|
|
247
|
+
- Method: `POST`
|
|
230
248
|
|
|
231
|
-
-
|
|
249
|
+
- Path: `/profile/generate-invite`
|
|
232
250
|
|
|
233
|
-
-
|
|
251
|
+
- Scope: `connections:write`
|
|
234
252
|
|
|
235
|
-
-
|
|
253
|
+
- Input:
|
|
236
254
|
|
|
237
255
|
```json
|
|
238
256
|
{
|
|
@@ -242,13 +260,13 @@ LearnCard supports creating and managing profile connection invitation links wit
|
|
|
242
260
|
}
|
|
243
261
|
```
|
|
244
262
|
|
|
245
|
-
-
|
|
263
|
+
- `expiration` (number, optional): Seconds until expiration. Defaults to 30 days. Use `0` for no expiration.
|
|
246
264
|
|
|
247
|
-
-
|
|
265
|
+
- `challenge` (string, optional): Custom challenge; if omitted, a UUID is generated.
|
|
248
266
|
|
|
249
|
-
-
|
|
267
|
+
- `maxUses` (integer, optional): Minimum `0`. Defaults to `1` (single-use). Use `0` for unlimited usage.
|
|
250
268
|
|
|
251
|
-
-
|
|
269
|
+
- Response:
|
|
252
270
|
|
|
253
271
|
```json
|
|
254
272
|
{
|
|
@@ -260,13 +278,13 @@ LearnCard supports creating and managing profile connection invitation links wit
|
|
|
260
278
|
|
|
261
279
|
#### List Invites
|
|
262
280
|
|
|
263
|
-
-
|
|
281
|
+
- Method: `GET`
|
|
264
282
|
|
|
265
|
-
-
|
|
283
|
+
- Path: `/profile/invites`
|
|
266
284
|
|
|
267
|
-
-
|
|
285
|
+
- Scope: `connections:read`
|
|
268
286
|
|
|
269
|
-
-
|
|
287
|
+
- Output: Array of invites
|
|
270
288
|
|
|
271
289
|
```json
|
|
272
290
|
[
|
|
@@ -285,55 +303,55 @@ LearnCard supports creating and managing profile connection invitation links wit
|
|
|
285
303
|
]
|
|
286
304
|
```
|
|
287
305
|
|
|
288
|
-
-
|
|
306
|
+
- Notes:
|
|
289
307
|
|
|
290
|
-
-
|
|
308
|
+
- Exhausted invites (where `usesRemaining === 0`) are omitted.
|
|
291
309
|
|
|
292
|
-
-
|
|
310
|
+
- `expiresIn` is `null` if the invite never expires.
|
|
293
311
|
|
|
294
312
|
#### Invalidate Invite
|
|
295
313
|
|
|
296
|
-
-
|
|
314
|
+
- Method: `POST`
|
|
297
315
|
|
|
298
|
-
-
|
|
316
|
+
- Path: `/profile/invite/{challenge}/invalidate`
|
|
299
317
|
|
|
300
|
-
-
|
|
318
|
+
- Scope: `connections:write`
|
|
301
319
|
|
|
302
|
-
-
|
|
320
|
+
- Output: `true` on success.
|
|
303
321
|
|
|
304
322
|
#### Connect Using Invite
|
|
305
323
|
|
|
306
|
-
-
|
|
324
|
+
- Method: `POST`
|
|
307
325
|
|
|
308
|
-
-
|
|
326
|
+
- Path: `/profile/{profileId}/connect/{challenge}`
|
|
309
327
|
|
|
310
|
-
-
|
|
328
|
+
- Scope: `profiles:write`
|
|
311
329
|
|
|
312
|
-
-
|
|
330
|
+
- Behavior:
|
|
313
331
|
|
|
314
|
-
-
|
|
332
|
+
- Validates that the invite exists, is not expired, and has remaining uses (or is unlimited).
|
|
315
333
|
|
|
316
|
-
-
|
|
334
|
+
- On successful connection, consumes one use for finite invites (decrements `usesRemaining`).
|
|
317
335
|
|
|
318
|
-
-
|
|
336
|
+
- Returns `404` if invite is invalid or expired.
|
|
319
337
|
|
|
320
338
|
### Behavior & Storage Details
|
|
321
339
|
|
|
322
|
-
-
|
|
340
|
+
- Cache key format: `inviteChallenge:{profileId}:{challenge}`.
|
|
323
341
|
|
|
324
|
-
-
|
|
342
|
+
- Stored value is JSON with `{ maxUses, usesRemaining }`.
|
|
325
343
|
|
|
326
|
-
-
|
|
344
|
+
- Unlimited usage: `maxUses = null` and `usesRemaining = null`.
|
|
327
345
|
|
|
328
|
-
-
|
|
346
|
+
- Expiration TTL:
|
|
329
347
|
|
|
330
|
-
-
|
|
348
|
+
- `expiration = 0` stores without TTL (never expires); `expiresIn` will be `null`.
|
|
331
349
|
|
|
332
|
-
-
|
|
350
|
+
- Positive `expiration` stores the TTL in seconds; listing returns remaining seconds as `expiresIn`.
|
|
333
351
|
|
|
334
|
-
-
|
|
352
|
+
- When `usesRemaining` reaches `0`, the invite is considered exhausted and will not be listed. The key may be deleted during consumption.
|
|
335
353
|
|
|
336
|
-
-
|
|
354
|
+
- Backward compatibility: Older invites stored as `'valid'` or `'never'` are parsed and treated as single-use or unlimited, respectively.
|
|
337
355
|
|
|
338
356
|
### Examples
|
|
339
357
|
|
|
@@ -372,7 +390,7 @@ curl -X POST \
|
|
|
372
390
|
|
|
373
391
|
### Testing
|
|
374
392
|
|
|
375
|
-
-
|
|
393
|
+
- See `test/profiles.spec.ts` for unit/integration coverage of generation, listing, consumption, unlimited behavior, and invalidation.
|
|
376
394
|
|
|
377
395
|
## Tracing & Performance Monitoring
|
|
378
396
|
|
|
@@ -380,9 +398,9 @@ The brain service includes a modular tracing system for performance monitoring a
|
|
|
380
398
|
|
|
381
399
|
### Architecture
|
|
382
400
|
|
|
383
|
-
-
|
|
384
|
-
-
|
|
385
|
-
-
|
|
401
|
+
- **TracingManager**: Central coordinator that manages providers and span lifecycle using `AsyncLocalStorage` for request-scoped trace context
|
|
402
|
+
- **TracingProvider**: Interface for output backends (console, JSON, Sentry)
|
|
403
|
+
- **Spans**: Represent timed operations with `traceId`, `spanId`, `parentSpanId`, timing, and status
|
|
386
404
|
|
|
387
405
|
### Trace Functions
|
|
388
406
|
|
|
@@ -573,10 +591,10 @@ ON MATCH SET received.status = "revoked", received.revokedAt = $revokedAt
|
|
|
573
591
|
|
|
574
592
|
### Connection Source Keys
|
|
575
593
|
|
|
576
|
-
-
|
|
577
|
-
-
|
|
578
|
-
-
|
|
579
|
-
-
|
|
594
|
+
- Pattern: `boost:${boostId}`
|
|
595
|
+
- Connections can have multiple sources (from multiple boosts)
|
|
596
|
+
- On revoke, only the source key from the revoked boost is removed
|
|
597
|
+
- Connection deleted only when all source keys are removed
|
|
580
598
|
|
|
581
599
|
### Parent Boost Handling
|
|
582
600
|
|
|
@@ -616,16 +634,16 @@ Apps can send notifications to users via the `APP_NOTIFICATION` type. There are
|
|
|
616
634
|
|
|
617
635
|
When the user has the app open in LearnCard, the Partner Connect SDK can trigger a self-notification via the `send-notification` app event type. This is handled by `handleSendNotificationEvent` in `src/routes/app-store.ts`.
|
|
618
636
|
|
|
619
|
-
-
|
|
620
|
-
-
|
|
637
|
+
- **Rate limit**: 10 notifications per user per app per hour (Redis key: `app-notif-rate:{listingId}:{profileId}`)
|
|
638
|
+
- **Auth**: User must have the app installed or own the integration
|
|
621
639
|
|
|
622
640
|
### 2. Server-to-Server Route (Primary)
|
|
623
641
|
|
|
624
642
|
App backends send notifications to arbitrary users who have the app installed.
|
|
625
643
|
|
|
626
|
-
-
|
|
627
|
-
-
|
|
628
|
-
-
|
|
644
|
+
- **Route**: `POST /app-store/listing/{listingId}/notify`
|
|
645
|
+
- **Scope**: `app-store:write`
|
|
646
|
+
- **Auth**: Caller must own the listing's integration (`verifyListingOwnership`)
|
|
629
647
|
|
|
630
648
|
**Input:**
|
|
631
649
|
|
|
@@ -641,16 +659,16 @@ App backends send notifications to arbitrary users who have the app installed.
|
|
|
641
659
|
}
|
|
642
660
|
```
|
|
643
661
|
|
|
644
|
-
-
|
|
645
|
-
-
|
|
646
|
-
-
|
|
647
|
-
-
|
|
648
|
-
-
|
|
662
|
+
- `recipient`: Accepts a profileId, `did:web:...`, or `did:key:...` (resolved via `getProfileIdFromString`)
|
|
663
|
+
- `title` / `body`: At least one required
|
|
664
|
+
- `actionPath`: Deep link path within the app
|
|
665
|
+
- `category`: Grouping category (e.g. `'reward'`, `'announcement'`)
|
|
666
|
+
- `priority`: `'normal'` (default) or `'high'`
|
|
649
667
|
|
|
650
668
|
**Guards:**
|
|
651
669
|
|
|
652
|
-
-
|
|
653
|
-
-
|
|
670
|
+
- Recipient must have the app installed (`hasProfileInstalledApp`)
|
|
671
|
+
- Rate limit: 60 notifications per listing per hour (Redis key: `app-notif-server-rate:{listingId}`)
|
|
654
672
|
|
|
655
673
|
**Notification shape:**
|
|
656
674
|
|
|
@@ -708,10 +726,10 @@ Multiple parallel `APP_COUNTER` relationships (one per key) can exist between th
|
|
|
708
726
|
|
|
709
727
|
### Limits
|
|
710
728
|
|
|
711
|
-
-
|
|
712
|
-
-
|
|
713
|
-
-
|
|
714
|
-
-
|
|
729
|
+
- Max **50 counter keys** per user per app (`MAX_COUNTER_KEYS_PER_USER_PER_APP`)
|
|
730
|
+
- Max **100 writes** per user per app per minute (Redis rate limit key: `app-counter-rate:{listingId}:{profileId}`)
|
|
731
|
+
- Counter keys: alphanumeric + `_` / `-`, max 64 chars
|
|
732
|
+
- Amount: any finite integer
|
|
715
733
|
|
|
716
734
|
### Key Files
|
|
717
735
|
|
|
@@ -799,10 +817,10 @@ bun run seed:dev-app -- --promotion FEATURED_CAROUSEL
|
|
|
799
817
|
|
|
800
818
|
### Prerequisites
|
|
801
819
|
|
|
802
|
-
-
|
|
803
|
-
-
|
|
804
|
-
-
|
|
805
|
-
-
|
|
820
|
+
- Neo4j running (`bun run dev:services` from `apps/learn-card-app` or local Docker)
|
|
821
|
+
- Redis running (same)
|
|
822
|
+
- MongoDB running (same)
|
|
823
|
+
- Falls back to local docker-compose defaults if no `.env` is present
|
|
806
824
|
|
|
807
825
|
### Re-running
|
|
808
826
|
|
|
@@ -857,10 +875,10 @@ Tenant is resolved once per request in `createContext` via `resolveTenantFromReq
|
|
|
857
875
|
|
|
858
876
|
### Rules for Route Authors
|
|
859
877
|
|
|
860
|
-
-
|
|
861
|
-
-
|
|
862
|
-
-
|
|
863
|
-
-
|
|
878
|
+
- **Always pass `branding: ctx.tenant?.emailBranding`** to `deliveryService.send()`. Emails sent without it render with LearnCard defaults regardless of the caller's tenant.
|
|
879
|
+
- **Use `getFrom({ mailbox, branding })`** for the from-address so the domain matches the tenant (e.g. `recovery@vetpass.app` for VetPass).
|
|
880
|
+
- **Use the local template ID as the `templateAlias`** (e.g. `'inbox-claim'`, `'guardian-approval'`). These are pre-registered as sentinels in `LOCAL_TEMPLATE_MAP` — env-var overrides are optional, never required.
|
|
881
|
+
- **Do not write plain-text fallbacks.** If local rendering fails for a sentinel alias, the adapter re-throws so the error surfaces — don't silently send unbranded text.
|
|
864
882
|
|
|
865
883
|
### Adding a New Template
|
|
866
884
|
|