@learncard/network-brain-service 4.0.4 → 4.1.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 (154) hide show
  1. package/.env.example +14 -0
  2. package/CHANGELOG.md +41 -0
  3. package/config/config.dev.json +5 -0
  4. package/config/config.example.json +5 -0
  5. package/config/config.production.json +5 -0
  6. package/config/config.scouts.dev.json +5 -0
  7. package/config/config.scouts.production.json +5 -0
  8. package/dist/accesslayer/campaign/audience.d.ts +7 -0
  9. package/dist/accesslayer/campaign/audience.d.ts.map +1 -0
  10. package/dist/accesslayer/campaign/create.d.ts +3 -0
  11. package/dist/accesslayer/campaign/create.d.ts.map +1 -0
  12. package/dist/accesslayer/campaign/delete.d.ts +2 -0
  13. package/dist/accesslayer/campaign/delete.d.ts.map +1 -0
  14. package/dist/accesslayer/campaign/read.d.ts +6 -0
  15. package/dist/accesslayer/campaign/read.d.ts.map +1 -0
  16. package/dist/accesslayer/campaign/shared.d.ts +16 -0
  17. package/dist/accesslayer/campaign/shared.d.ts.map +1 -0
  18. package/dist/accesslayer/campaign/state.d.ts +4 -0
  19. package/dist/accesslayer/campaign/state.d.ts.map +1 -0
  20. package/dist/accesslayer/campaign/update.d.ts +3 -0
  21. package/dist/accesslayer/campaign/update.d.ts.map +1 -0
  22. package/dist/accesslayer/credential-share-previews.d.ts +5 -0
  23. package/dist/accesslayer/credential-share-previews.d.ts.map +1 -0
  24. package/dist/accesslayer/profile/delete.d.ts.map +1 -1
  25. package/dist/accesslayer/skill/read.d.ts +12 -2
  26. package/dist/accesslayer/skill/read.d.ts.map +1 -1
  27. package/dist/accesslayer/skill/update.d.ts +11 -1
  28. package/dist/accesslayer/skill/update.d.ts.map +1 -1
  29. package/dist/app.d.ts +4 -0
  30. package/dist/app.d.ts.map +1 -1
  31. package/dist/cache/app-store.caches.d.ts +2 -1
  32. package/dist/cache/app-store.caches.d.ts.map +1 -1
  33. package/dist/config/environment.d.ts +268 -178
  34. package/dist/config/environment.d.ts.map +1 -1
  35. package/dist/config/skillEmbedding.d.ts +70 -0
  36. package/dist/config/skillEmbedding.d.ts.map +1 -0
  37. package/dist/config/skillEmbedding.neo4j.test.d.ts +2 -0
  38. package/dist/config/skillEmbedding.neo4j.test.d.ts.map +1 -0
  39. package/dist/config/stageConfig.d.ts +20 -0
  40. package/dist/config/stageConfig.d.ts.map +1 -1
  41. package/dist/constants/campaigns.d.ts +3 -0
  42. package/dist/constants/campaigns.d.ts.map +1 -0
  43. package/dist/didkit_wasm_bg.wasm +0 -0
  44. package/dist/helpers/bedrock-embedding.helpers.d.ts +40 -0
  45. package/dist/helpers/bedrock-embedding.helpers.d.ts.map +1 -0
  46. package/dist/helpers/bedrock-embedding.helpers.test.d.ts +2 -0
  47. package/dist/helpers/bedrock-embedding.helpers.test.d.ts.map +1 -0
  48. package/dist/helpers/bedrock-embedding.smoke.test.d.ts +2 -0
  49. package/dist/helpers/bedrock-embedding.smoke.test.d.ts.map +1 -0
  50. package/dist/helpers/skill-embedding.helpers.d.ts +65 -7
  51. package/dist/helpers/skill-embedding.helpers.d.ts.map +1 -1
  52. package/dist/helpers/skill-embedding.helpers.test.d.ts +2 -0
  53. package/dist/helpers/skill-embedding.helpers.test.d.ts.map +1 -0
  54. package/dist/index.js +2 -2
  55. package/dist/models/Campaign.d.ts +10 -0
  56. package/dist/models/Campaign.d.ts.map +1 -0
  57. package/dist/models/CredentialSharePreview.d.ts +14 -0
  58. package/dist/models/CredentialSharePreview.d.ts.map +1 -0
  59. package/dist/models/Integration.d.ts +1 -1
  60. package/dist/models/Profile.d.ts.map +1 -1
  61. package/dist/models/campaign-constraints.d.ts +2 -0
  62. package/dist/models/campaign-constraints.d.ts.map +1 -0
  63. package/dist/models/index.d.ts +2 -0
  64. package/dist/models/index.d.ts.map +1 -1
  65. package/dist/publish-metafile.json +1 -1
  66. package/dist/routes/app-store.d.ts +1 -1
  67. package/dist/routes/boosts.d.ts +24 -4
  68. package/dist/routes/boosts.d.ts.map +1 -1
  69. package/dist/routes/campaigns.d.ts +1411 -0
  70. package/dist/routes/campaigns.d.ts.map +1 -0
  71. package/dist/routes/contracts.d.ts +19 -6
  72. package/dist/routes/contracts.d.ts.map +1 -1
  73. package/dist/routes/credential-share-previews.d.ts +39 -0
  74. package/dist/routes/credential-share-previews.d.ts.map +1 -0
  75. package/dist/routes/credentials.d.ts +4 -1
  76. package/dist/routes/credentials.d.ts.map +1 -1
  77. package/dist/routes/inbox.d.ts +5 -5
  78. package/dist/routes/index.d.ts +4 -0
  79. package/dist/routes/index.d.ts.map +1 -1
  80. package/dist/routes/integrations.d.ts +3 -3
  81. package/dist/routes/profile-manager.d.ts +4 -0
  82. package/dist/routes/profile-manager.d.ts.map +1 -1
  83. package/dist/routes/profiles.d.ts +44 -0
  84. package/dist/routes/profiles.d.ts.map +1 -1
  85. package/dist/routes/skill-frameworks.d.ts +1 -0
  86. package/dist/routes/skill-frameworks.d.ts.map +1 -1
  87. package/dist/routes/skills-semantic-search.test.d.ts +2 -0
  88. package/dist/routes/skills-semantic-search.test.d.ts.map +1 -0
  89. package/dist/routes/skills.d.ts.map +1 -1
  90. package/dist/routes/test.d.ts +3 -1
  91. package/dist/routes/test.d.ts.map +1 -1
  92. package/dist/seed/seedSkillFrameworks.d.ts +1 -0
  93. package/dist/seed/seedSkillFrameworks.d.ts.map +1 -1
  94. package/dist/seed/seedSkillFrameworks.embeddings.test.d.ts +2 -0
  95. package/dist/seed/seedSkillFrameworks.embeddings.test.d.ts.map +1 -0
  96. package/dist/types/app-manifest.d.ts +2 -2
  97. package/dist/types/campaign.d.ts +19 -0
  98. package/dist/types/campaign.d.ts.map +1 -0
  99. package/dist/types/consentflowcontract.d.ts +1 -1
  100. package/dist/types/integration.d.ts +3 -3
  101. package/dist/types/profile.d.ts +9 -0
  102. package/dist/types/profile.d.ts.map +1 -1
  103. package/dist/types/skill.d.ts.map +1 -1
  104. package/package.json +20 -17
  105. package/project.json +14 -0
  106. package/scripts/skill-embeddings-backfill.ts +161 -0
  107. package/serverless-local.yml +4 -0
  108. package/serverless.function-env.cjs +3 -0
  109. package/serverless.local.yml +4 -0
  110. package/serverless.yml +12 -1
  111. package/src/accesslayer/campaign/README.md +174 -0
  112. package/src/accesslayer/campaign/audience.ts +152 -0
  113. package/src/accesslayer/campaign/create.ts +47 -0
  114. package/src/accesslayer/campaign/delete.ts +18 -0
  115. package/src/accesslayer/campaign/read.ts +125 -0
  116. package/src/accesslayer/campaign/shared.ts +92 -0
  117. package/src/accesslayer/campaign/state.ts +80 -0
  118. package/src/accesslayer/campaign/update.ts +50 -0
  119. package/src/accesslayer/credential-share-previews.ts +76 -0
  120. package/src/accesslayer/profile/delete.ts +6 -0
  121. package/src/accesslayer/skill/read.ts +56 -16
  122. package/src/accesslayer/skill/update.ts +43 -1
  123. package/src/app.ts +9 -0
  124. package/src/config/environment.test.ts +208 -0
  125. package/src/config/environment.ts +166 -11
  126. package/src/config/skillEmbedding.neo4j.test.ts +138 -0
  127. package/src/config/skillEmbedding.ts +202 -0
  128. package/src/constants/campaigns.ts +9 -0
  129. package/src/helpers/bedrock-embedding.helpers.test.ts +208 -0
  130. package/src/helpers/bedrock-embedding.helpers.ts +243 -0
  131. package/src/helpers/bedrock-embedding.smoke.test.ts +53 -0
  132. package/src/helpers/skill-embedding.helpers.test.ts +1044 -0
  133. package/src/helpers/skill-embedding.helpers.ts +611 -123
  134. package/src/models/Campaign.ts +32 -0
  135. package/src/models/CredentialSharePreview.ts +30 -0
  136. package/src/models/Profile.ts +1 -0
  137. package/src/models/campaign-constraints.ts +22 -0
  138. package/src/models/index.ts +29 -1
  139. package/src/routes/campaigns.ts +179 -0
  140. package/src/routes/credential-share-previews.ts +74 -0
  141. package/src/routes/profiles.ts +7 -0
  142. package/src/routes/skills-semantic-search.test.ts +168 -0
  143. package/src/routes/skills.ts +42 -6
  144. package/src/seed/seedSkillFrameworks.embeddings.test.ts +82 -0
  145. package/src/seed/seedSkillFrameworks.test.ts +6 -0
  146. package/src/seed/seedSkillFrameworks.ts +83 -18
  147. package/src/types/campaign.ts +17 -0
  148. package/src/types/skill.ts +6 -0
  149. package/test/campaigns.spec.ts +789 -0
  150. package/test/credential-share-previews.spec.ts +97 -0
  151. package/test/verification/campaigns.ts +49 -0
  152. package/vitest.campaigns.config.ts +21 -0
  153. package/vitest.config.ts +10 -0
  154. package/vitest.integration.config.ts +1 -0
@@ -0,0 +1,97 @@
1
+ import { beforeEach, describe, expect, it, vi } from 'vitest';
2
+ import { TRPCError } from '@trpc/server';
3
+
4
+ const owner = { profileId: 'owner-1', did: 'did:key:owner', displayName: 'Owner' };
5
+ const storage = vi.hoisted(() => ({
6
+ create: vi.fn(
7
+ async (ownerId: string, preview: { id?: string }): Promise<string | null> =>
8
+ preview.id ?? '5b58ff0c-bb55-4f25-a2a5-b4de3778c649'
9
+ ),
10
+ get: vi.fn(async (_id: string, _shareUri: string) => null as unknown),
11
+ delete: vi.fn(async (_ownerId: string, _id: string) => false),
12
+ }));
13
+ const sentry = vi.hoisted(() => ({
14
+ trpcMiddleware: vi.fn(() => async (options: { next: () => unknown }) => options.next()),
15
+ setUser: vi.fn(),
16
+ configureScope: vi.fn(),
17
+ }));
18
+ vi.mock('@accesslayer/credential-share-previews', () => ({
19
+ createCredentialSharePreview: storage.create,
20
+ getCredentialSharePreview: storage.get,
21
+ deleteCredentialSharePreview: storage.delete,
22
+ }));
23
+ vi.mock('@accesslayer/profile/read', () => ({ getProfileByDid: vi.fn(async () => owner) }));
24
+ vi.mock('@sentry/serverless', () => ({
25
+ Handlers: { trpcMiddleware: sentry.trpcMiddleware },
26
+ setUser: sentry.setUser,
27
+ configureScope: sentry.configureScope,
28
+ }));
29
+ import { credentialSharePreviewsRouter } from '../src/routes/credential-share-previews';
30
+
31
+ const caller = (user?: { did: string; isChallengeValid: boolean }) =>
32
+ credentialSharePreviewsRouter.createCaller({
33
+ domain: 'network.example.com',
34
+ tenant: { id: 'default' },
35
+ ...(user ? { user } : {}),
36
+ } as never);
37
+ const authenticated = { did: owner.did, isChallengeValid: true };
38
+ const id = '5b58ff0c-bb55-4f25-a2a5-b4de3778c649';
39
+ const shareUri = 'urn:encrypted:opaque';
40
+
41
+ describe('credential share previews route', () => {
42
+ beforeEach(() => {
43
+ vi.clearAllMocks();
44
+ storage.get.mockResolvedValue(null);
45
+ storage.delete.mockResolvedValue(false);
46
+ storage.create.mockImplementation(async (_owner, preview) => preview.id ?? id);
47
+ });
48
+
49
+ it('requires authentication and rejects private fields in published metadata', async () => {
50
+ const preview = {
51
+ name: 'Badge',
52
+ issuer: 'Issuer',
53
+ image: 'https://cdn.example/badge.png',
54
+ shareUri,
55
+ };
56
+ await expect(caller().publish(preview)).rejects.toMatchObject({ code: 'UNAUTHORIZED' });
57
+ await expect(
58
+ caller(authenticated).publish({ ...preview, credential: { id: 'private' } } as never)
59
+ ).rejects.toBeInstanceOf(TRPCError);
60
+ });
61
+
62
+ it('rejects unauthenticated deletion', async () => {
63
+ await expect(caller().delete({ id })).rejects.toMatchObject({ code: 'UNAUTHORIZED' });
64
+ });
65
+
66
+ it('reports missing refreshes with the machine-readable recovery code', async () => {
67
+ storage.create.mockResolvedValueOnce(null);
68
+ await expect(
69
+ caller(authenticated).publish({ id, name: 'Badge', issuer: 'Issuer', shareUri })
70
+ ).rejects.toMatchObject({ code: 'NOT_FOUND' });
71
+ });
72
+
73
+ it('fails closed if storage returns non-public fields', async () => {
74
+ storage.get.mockResolvedValueOnce({
75
+ name: 'Badge',
76
+ issuer: 'Issuer',
77
+ seed: 'must-never-be-returned',
78
+ shareUri,
79
+ ownerProfileId: owner.profileId,
80
+ createdAt: new Date(0).toISOString(),
81
+ updatedAt: new Date(0).toISOString(),
82
+ });
83
+ await expect(caller().get({ id, shareUri })).rejects.toMatchObject({
84
+ code: 'INTERNAL_SERVER_ERROR',
85
+ });
86
+ });
87
+
88
+ it('requires both public lookup keys and reports missing previews', async () => {
89
+ await expect(caller().get({ id } as never)).rejects.toBeInstanceOf(TRPCError);
90
+ await expect(caller().get({ id: 'not-a-uuid', shareUri })).rejects.toBeInstanceOf(
91
+ TRPCError
92
+ );
93
+ await expect(caller().get({ id, shareUri })).rejects.toMatchObject({
94
+ code: 'NOT_FOUND',
95
+ });
96
+ });
97
+ });
@@ -0,0 +1,49 @@
1
+ /**
2
+ * Real signed SDK requests against an owned Linux verification stack.
3
+ * Positive admin/data coverage lives in campaigns.spec.ts (disposable Neo4j): the
4
+ * default stack intentionally grants no campaign administration to test actors.
5
+ *
6
+ * bun run --cwd apps/learn-card-app lc verify script \
7
+ * services/learn-card-network/brain-service/test/verification/campaigns.ts --dir <owned-dir>
8
+ */
9
+ import type { VerificationScript } from '../../../../../apps/learn-card-app/scripts/verification/script';
10
+
11
+ const script: VerificationScript = async v => {
12
+ const learner = await v.sdk(v.actors.learner);
13
+ const other = await v.sdk(v.actors.recipient);
14
+ const campaignId = 'verification-denied-campaign';
15
+
16
+ await v.step(null, 'campaign-sdk-authorization', async () => {
17
+ v.expect(await learner.invoke.campaignsIsAdmin()).toBe(false);
18
+ await v
19
+ .expect(
20
+ learner.invoke.campaignsCreate({
21
+ id: campaignId,
22
+ name: 'Not authorized',
23
+ channels: ['inApp'],
24
+ message: { title: 'Denied' },
25
+ })
26
+ )
27
+ .rejects.toThrow(/Campaign administrator access required/);
28
+ await v
29
+ .expect(other.invoke.campaignsList())
30
+ .rejects.toThrow(/Campaign administrator access required/);
31
+ });
32
+
33
+ await v.step(null, 'campaign-sdk-user-isolation', async () => {
34
+ v.expect(await learner.invoke.getMyCampaigns()).toEqual([]);
35
+ v.expect(await other.invoke.getMyCampaigns()).toEqual([]);
36
+ v.expect(await learner.invoke.recordCampaignEvent(campaignId, 'shown')).toBe(false);
37
+ await v
38
+ .expect(learner.invoke.enrollInCampaign(campaignId))
39
+ .rejects.toThrow(/Campaign not found/);
40
+ });
41
+
42
+ return {
43
+ authorization: 'passed',
44
+ userIsolation: 'passed',
45
+ adminDataCoverage: 'separate Neo4j integration suite',
46
+ };
47
+ };
48
+
49
+ export default script;
@@ -0,0 +1,21 @@
1
+ import { createRequire } from 'node:module';
2
+
3
+ import { createVitestConfig, serviceIntegrationPreset } from '../../../vitest.shared';
4
+
5
+ const require = createRequire(import.meta.url);
6
+
7
+ export default createVitestConfig(serviceIntegrationPreset, {
8
+ test: {
9
+ globalSetup: './test-setup.ts',
10
+ include: ['test/campaigns.spec.ts'],
11
+ alias: { '@instance': require.resolve('./test/helpers/mock-instance.ts') },
12
+ env: {
13
+ IS_E2E_TEST: 'true',
14
+ DOMAIN_NAME: 'localhost%3A3000',
15
+ SEED: 'a'.repeat(64),
16
+ CAMPAIGN_ADMIN_PROFILE_IDS: 'campaign-admin',
17
+ TRACE_CONSOLE: 'false',
18
+ NEO4J_SKIP_INDICES: 'true',
19
+ },
20
+ },
21
+ });
package/vitest.config.ts CHANGED
@@ -16,6 +16,7 @@ export default createVitestConfig(brainServicePreset, {
16
16
  NEO4J_SKIP_INDICES: 'true',
17
17
  },
18
18
  include: [
19
+ 'test/credential-share-previews.spec.ts',
19
20
  'test/share-link-*.unit.spec.ts',
20
21
  'test/share-content-client.*.spec.ts',
21
22
  'test/uri-helpers.spec.ts',
@@ -57,6 +58,15 @@ export default createVitestConfig(brainServicePreset, {
57
58
  'src/helpers/contact-method.helpers.test.ts',
58
59
  'src/services/delivery/adapters/postmark.adapter.test.ts',
59
60
  'src/helpers/inbox-maintenance.helpers.test.ts',
61
+ 'src/helpers/bedrock-embedding.helpers.test.ts',
62
+ 'src/helpers/skill-embedding.helpers.test.ts',
63
+ 'src/seed/seedSkillFrameworks.embeddings.test.ts',
64
+ // Opt-in, local-only Neo4j check of the embedding compare-and-set; skipped unless
65
+ // SKILL_EMBEDDING_TEST_NEO4J_URI points at localhost.
66
+ 'src/config/skillEmbedding.neo4j.test.ts',
67
+ 'src/routes/skills-semantic-search.test.ts',
68
+ // Opt-in, paid, real-AWS smoke test; skipped unless SKILL_EMBEDDING_BEDROCK_SMOKE=true.
69
+ 'src/helpers/bedrock-embedding.smoke.test.ts',
60
70
  ],
61
71
  },
62
72
  });
@@ -19,6 +19,7 @@ export default createVitestConfig(serviceIntegrationPreset, {
19
19
  CREDENTIAL_REFRESH_ENABLED: 'true',
20
20
  LOGIN_PROVIDER_DID: 'did:key:z6Mko9uYxDPk2BetRRziLz1xHN8nR5zQWdNjytKNDPcygHJP',
21
21
  APP_STORE_ADMIN_PROFILE_IDS: 'app-store-admin',
22
+ CAMPAIGN_ADMIN_PROFILE_IDS: 'campaign-admin',
22
23
  TRACE_CONSOLE: 'false',
23
24
  },
24
25
  },