@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.
- package/.env.example +14 -0
- package/CHANGELOG.md +41 -0
- package/config/config.dev.json +5 -0
- package/config/config.example.json +5 -0
- package/config/config.production.json +5 -0
- package/config/config.scouts.dev.json +5 -0
- package/config/config.scouts.production.json +5 -0
- package/dist/accesslayer/campaign/audience.d.ts +7 -0
- package/dist/accesslayer/campaign/audience.d.ts.map +1 -0
- package/dist/accesslayer/campaign/create.d.ts +3 -0
- package/dist/accesslayer/campaign/create.d.ts.map +1 -0
- package/dist/accesslayer/campaign/delete.d.ts +2 -0
- package/dist/accesslayer/campaign/delete.d.ts.map +1 -0
- package/dist/accesslayer/campaign/read.d.ts +6 -0
- package/dist/accesslayer/campaign/read.d.ts.map +1 -0
- package/dist/accesslayer/campaign/shared.d.ts +16 -0
- package/dist/accesslayer/campaign/shared.d.ts.map +1 -0
- package/dist/accesslayer/campaign/state.d.ts +4 -0
- package/dist/accesslayer/campaign/state.d.ts.map +1 -0
- package/dist/accesslayer/campaign/update.d.ts +3 -0
- package/dist/accesslayer/campaign/update.d.ts.map +1 -0
- package/dist/accesslayer/credential-share-previews.d.ts +5 -0
- package/dist/accesslayer/credential-share-previews.d.ts.map +1 -0
- package/dist/accesslayer/profile/delete.d.ts.map +1 -1
- package/dist/accesslayer/skill/read.d.ts +12 -2
- package/dist/accesslayer/skill/read.d.ts.map +1 -1
- package/dist/accesslayer/skill/update.d.ts +11 -1
- package/dist/accesslayer/skill/update.d.ts.map +1 -1
- package/dist/app.d.ts +4 -0
- package/dist/app.d.ts.map +1 -1
- package/dist/cache/app-store.caches.d.ts +2 -1
- package/dist/cache/app-store.caches.d.ts.map +1 -1
- package/dist/config/environment.d.ts +268 -178
- package/dist/config/environment.d.ts.map +1 -1
- package/dist/config/skillEmbedding.d.ts +70 -0
- package/dist/config/skillEmbedding.d.ts.map +1 -0
- package/dist/config/skillEmbedding.neo4j.test.d.ts +2 -0
- package/dist/config/skillEmbedding.neo4j.test.d.ts.map +1 -0
- package/dist/config/stageConfig.d.ts +20 -0
- package/dist/config/stageConfig.d.ts.map +1 -1
- package/dist/constants/campaigns.d.ts +3 -0
- package/dist/constants/campaigns.d.ts.map +1 -0
- package/dist/didkit_wasm_bg.wasm +0 -0
- package/dist/helpers/bedrock-embedding.helpers.d.ts +40 -0
- package/dist/helpers/bedrock-embedding.helpers.d.ts.map +1 -0
- package/dist/helpers/bedrock-embedding.helpers.test.d.ts +2 -0
- package/dist/helpers/bedrock-embedding.helpers.test.d.ts.map +1 -0
- package/dist/helpers/bedrock-embedding.smoke.test.d.ts +2 -0
- package/dist/helpers/bedrock-embedding.smoke.test.d.ts.map +1 -0
- package/dist/helpers/skill-embedding.helpers.d.ts +65 -7
- package/dist/helpers/skill-embedding.helpers.d.ts.map +1 -1
- package/dist/helpers/skill-embedding.helpers.test.d.ts +2 -0
- package/dist/helpers/skill-embedding.helpers.test.d.ts.map +1 -0
- package/dist/index.js +2 -2
- package/dist/models/Campaign.d.ts +10 -0
- package/dist/models/Campaign.d.ts.map +1 -0
- package/dist/models/CredentialSharePreview.d.ts +14 -0
- package/dist/models/CredentialSharePreview.d.ts.map +1 -0
- package/dist/models/Integration.d.ts +1 -1
- package/dist/models/Profile.d.ts.map +1 -1
- package/dist/models/campaign-constraints.d.ts +2 -0
- package/dist/models/campaign-constraints.d.ts.map +1 -0
- package/dist/models/index.d.ts +2 -0
- package/dist/models/index.d.ts.map +1 -1
- package/dist/publish-metafile.json +1 -1
- package/dist/routes/app-store.d.ts +1 -1
- package/dist/routes/boosts.d.ts +24 -4
- package/dist/routes/boosts.d.ts.map +1 -1
- package/dist/routes/campaigns.d.ts +1411 -0
- package/dist/routes/campaigns.d.ts.map +1 -0
- package/dist/routes/contracts.d.ts +19 -6
- package/dist/routes/contracts.d.ts.map +1 -1
- package/dist/routes/credential-share-previews.d.ts +39 -0
- package/dist/routes/credential-share-previews.d.ts.map +1 -0
- package/dist/routes/credentials.d.ts +4 -1
- package/dist/routes/credentials.d.ts.map +1 -1
- package/dist/routes/inbox.d.ts +5 -5
- package/dist/routes/index.d.ts +4 -0
- package/dist/routes/index.d.ts.map +1 -1
- package/dist/routes/integrations.d.ts +3 -3
- package/dist/routes/profile-manager.d.ts +4 -0
- package/dist/routes/profile-manager.d.ts.map +1 -1
- package/dist/routes/profiles.d.ts +44 -0
- package/dist/routes/profiles.d.ts.map +1 -1
- package/dist/routes/skill-frameworks.d.ts +1 -0
- package/dist/routes/skill-frameworks.d.ts.map +1 -1
- package/dist/routes/skills-semantic-search.test.d.ts +2 -0
- package/dist/routes/skills-semantic-search.test.d.ts.map +1 -0
- package/dist/routes/skills.d.ts.map +1 -1
- package/dist/routes/test.d.ts +3 -1
- package/dist/routes/test.d.ts.map +1 -1
- package/dist/seed/seedSkillFrameworks.d.ts +1 -0
- package/dist/seed/seedSkillFrameworks.d.ts.map +1 -1
- package/dist/seed/seedSkillFrameworks.embeddings.test.d.ts +2 -0
- package/dist/seed/seedSkillFrameworks.embeddings.test.d.ts.map +1 -0
- package/dist/types/app-manifest.d.ts +2 -2
- package/dist/types/campaign.d.ts +19 -0
- package/dist/types/campaign.d.ts.map +1 -0
- package/dist/types/consentflowcontract.d.ts +1 -1
- package/dist/types/integration.d.ts +3 -3
- package/dist/types/profile.d.ts +9 -0
- package/dist/types/profile.d.ts.map +1 -1
- package/dist/types/skill.d.ts.map +1 -1
- package/package.json +20 -17
- package/project.json +14 -0
- package/scripts/skill-embeddings-backfill.ts +161 -0
- package/serverless-local.yml +4 -0
- package/serverless.function-env.cjs +3 -0
- package/serverless.local.yml +4 -0
- package/serverless.yml +12 -1
- package/src/accesslayer/campaign/README.md +174 -0
- package/src/accesslayer/campaign/audience.ts +152 -0
- package/src/accesslayer/campaign/create.ts +47 -0
- package/src/accesslayer/campaign/delete.ts +18 -0
- package/src/accesslayer/campaign/read.ts +125 -0
- package/src/accesslayer/campaign/shared.ts +92 -0
- package/src/accesslayer/campaign/state.ts +80 -0
- package/src/accesslayer/campaign/update.ts +50 -0
- package/src/accesslayer/credential-share-previews.ts +76 -0
- package/src/accesslayer/profile/delete.ts +6 -0
- package/src/accesslayer/skill/read.ts +56 -16
- package/src/accesslayer/skill/update.ts +43 -1
- package/src/app.ts +9 -0
- package/src/config/environment.test.ts +208 -0
- package/src/config/environment.ts +166 -11
- package/src/config/skillEmbedding.neo4j.test.ts +138 -0
- package/src/config/skillEmbedding.ts +202 -0
- package/src/constants/campaigns.ts +9 -0
- package/src/helpers/bedrock-embedding.helpers.test.ts +208 -0
- package/src/helpers/bedrock-embedding.helpers.ts +243 -0
- package/src/helpers/bedrock-embedding.smoke.test.ts +53 -0
- package/src/helpers/skill-embedding.helpers.test.ts +1044 -0
- package/src/helpers/skill-embedding.helpers.ts +611 -123
- package/src/models/Campaign.ts +32 -0
- package/src/models/CredentialSharePreview.ts +30 -0
- package/src/models/Profile.ts +1 -0
- package/src/models/campaign-constraints.ts +22 -0
- package/src/models/index.ts +29 -1
- package/src/routes/campaigns.ts +179 -0
- package/src/routes/credential-share-previews.ts +74 -0
- package/src/routes/profiles.ts +7 -0
- package/src/routes/skills-semantic-search.test.ts +168 -0
- package/src/routes/skills.ts +42 -6
- package/src/seed/seedSkillFrameworks.embeddings.test.ts +82 -0
- package/src/seed/seedSkillFrameworks.test.ts +6 -0
- package/src/seed/seedSkillFrameworks.ts +83 -18
- package/src/types/campaign.ts +17 -0
- package/src/types/skill.ts +6 -0
- package/test/campaigns.spec.ts +789 -0
- package/test/credential-share-previews.spec.ts +97 -0
- package/test/verification/campaigns.ts +49 -0
- package/vitest.campaigns.config.ts +21 -0
- package/vitest.config.ts +10 -0
- 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
|
},
|