vellum 0.2.12 → 0.2.14
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/README.md +32 -0
- package/bun.lock +2 -2
- package/docs/skills.md +4 -4
- package/package.json +2 -2
- package/src/__tests__/__snapshots__/ipc-snapshot.test.ts.snap +213 -3
- package/src/__tests__/app-git-history.test.ts +176 -0
- package/src/__tests__/app-git-service.test.ts +169 -0
- package/src/__tests__/assistant-events-sse-hardening.test.ts +315 -0
- package/src/__tests__/browser-skill-baseline-tool-payload.test.ts +8 -8
- package/src/__tests__/browser-skill-endstate.test.ts +6 -6
- package/src/__tests__/call-bridge.test.ts +105 -13
- package/src/__tests__/call-domain.test.ts +163 -0
- package/src/__tests__/call-orchestrator.test.ts +171 -0
- package/src/__tests__/call-routes-http.test.ts +246 -6
- package/src/__tests__/channel-approval-routes.test.ts +438 -0
- package/src/__tests__/channel-approval.test.ts +266 -0
- package/src/__tests__/channel-approvals.test.ts +393 -0
- package/src/__tests__/channel-delivery-store.test.ts +447 -0
- package/src/__tests__/checker.test.ts +607 -1048
- package/src/__tests__/cli.test.ts +1 -56
- package/src/__tests__/config-schema.test.ts +402 -5
- package/src/__tests__/conflict-intent-tokenization.test.ts +141 -0
- package/src/__tests__/conflict-policy.test.ts +121 -0
- package/src/__tests__/conflict-store.test.ts +2 -0
- package/src/__tests__/contacts-tools.test.ts +3 -3
- package/src/__tests__/contradiction-checker.test.ts +99 -1
- package/src/__tests__/credential-security-invariants.test.ts +22 -6
- package/src/__tests__/credential-vault-unit.test.ts +780 -0
- package/src/__tests__/elevenlabs-client.test.ts +271 -0
- package/src/__tests__/ephemeral-permissions.test.ts +73 -23
- package/src/__tests__/filesystem-tools.test.ts +579 -0
- package/src/__tests__/gateway-only-enforcement.test.ts +114 -4
- package/src/__tests__/handlers-add-trust-rule-metadata.test.ts +202 -0
- package/src/__tests__/handlers-cu-observation-blob.test.ts +2 -1
- package/src/__tests__/handlers-ipc-blob-probe.test.ts +2 -1
- package/src/__tests__/handlers-slack-config.test.ts +2 -1
- package/src/__tests__/handlers-telegram-config.test.ts +855 -0
- package/src/__tests__/handlers-twitter-config.test.ts +141 -1
- package/src/__tests__/hooks-runner.test.ts +6 -2
- package/src/__tests__/host-file-edit-tool.test.ts +124 -0
- package/src/__tests__/host-file-read-tool.test.ts +62 -0
- package/src/__tests__/host-file-write-tool.test.ts +59 -0
- package/src/__tests__/host-shell-tool.test.ts +251 -0
- package/src/__tests__/ingress-reconcile.test.ts +581 -0
- package/src/__tests__/ipc-snapshot.test.ts +100 -41
- package/src/__tests__/ipc-validate.test.ts +50 -0
- package/src/__tests__/key-migration.test.ts +23 -0
- package/src/__tests__/memory-regressions.test.ts +99 -0
- package/src/__tests__/memory-retrieval.benchmark.test.ts +1 -1
- package/src/__tests__/oauth-callback-registry.test.ts +11 -4
- package/src/__tests__/playbook-execution.test.ts +502 -0
- package/src/__tests__/playbook-tools.test.ts +4 -6
- package/src/__tests__/public-ingress-urls.test.ts +34 -0
- package/src/__tests__/qdrant-manager.test.ts +267 -0
- package/src/__tests__/recurrence-engine-rruleset.test.ts +97 -0
- package/src/__tests__/recurrence-engine.test.ts +9 -0
- package/src/__tests__/recurrence-types.test.ts +8 -0
- package/src/__tests__/registry.test.ts +1 -1
- package/src/__tests__/runtime-runs.test.ts +1 -25
- package/src/__tests__/schedule-store.test.ts +16 -14
- package/src/__tests__/schedule-tools.test.ts +83 -0
- package/src/__tests__/scheduler-recurrence.test.ts +111 -10
- package/src/__tests__/secret-allowlist.test.ts +18 -17
- package/src/__tests__/secret-ingress-handler.test.ts +11 -0
- package/src/__tests__/secret-scanner.test.ts +43 -0
- package/src/__tests__/session-conflict-gate.test.ts +442 -6
- package/src/__tests__/session-init.benchmark.test.ts +3 -0
- package/src/__tests__/session-process-bridge.test.ts +242 -0
- package/src/__tests__/session-skill-tools.test.ts +1 -1
- package/src/__tests__/shell-identity.test.ts +256 -0
- package/src/__tests__/skill-projection.benchmark.test.ts +11 -1
- package/src/__tests__/subagent-tools.test.ts +637 -54
- package/src/__tests__/task-management-tools.test.ts +936 -0
- package/src/__tests__/task-runner.test.ts +2 -2
- package/src/__tests__/terminal-tools.test.ts +840 -0
- package/src/__tests__/tool-executor-shell-integration.test.ts +301 -0
- package/src/__tests__/tool-executor.test.ts +85 -151
- package/src/__tests__/tool-permission-simulate-handler.test.ts +336 -0
- package/src/__tests__/trust-store.test.ts +28 -453
- package/src/__tests__/twilio-provider.test.ts +153 -3
- package/src/__tests__/twilio-routes-elevenlabs.test.ts +375 -0
- package/src/__tests__/twilio-routes-twiml.test.ts +127 -0
- package/src/__tests__/twilio-routes.test.ts +17 -262
- package/src/__tests__/twitter-auth-handler.test.ts +2 -1
- package/src/__tests__/twitter-cli-error-shaping.test.ts +208 -0
- package/src/__tests__/twitter-cli-routing.test.ts +252 -0
- package/src/__tests__/twitter-oauth-client.test.ts +209 -0
- package/src/__tests__/workspace-policy.test.ts +213 -0
- package/src/calls/call-bridge.ts +92 -19
- package/src/calls/call-domain.ts +157 -5
- package/src/calls/call-orchestrator.ts +96 -8
- package/src/calls/call-store.ts +6 -0
- package/src/calls/elevenlabs-client.ts +97 -0
- package/src/calls/elevenlabs-config.ts +31 -0
- package/src/calls/twilio-provider.ts +91 -0
- package/src/calls/twilio-routes.ts +50 -6
- package/src/calls/types.ts +3 -1
- package/src/calls/voice-quality.ts +114 -0
- package/src/cli/twitter.ts +200 -21
- package/src/cli.ts +1 -20
- package/src/config/bundled-skills/contacts/tools/contact-merge.ts +52 -4
- package/src/config/bundled-skills/contacts/tools/contact-search.ts +55 -4
- package/src/config/bundled-skills/contacts/tools/contact-upsert.ts +61 -4
- package/src/config/bundled-skills/messaging/SKILL.md +17 -2
- package/src/config/bundled-skills/messaging/tools/messaging-reply.ts +4 -1
- package/src/config/bundled-skills/messaging/tools/messaging-send.ts +5 -1
- package/src/config/bundled-skills/messaging/tools/shared.ts +5 -0
- package/src/config/bundled-skills/phone-calls/SKILL.md +207 -19
- package/src/config/bundled-skills/playbooks/tools/playbook-create.ts +95 -6
- package/src/config/bundled-skills/playbooks/tools/playbook-delete.ts +51 -6
- package/src/config/bundled-skills/playbooks/tools/playbook-list.ts +73 -6
- package/src/config/bundled-skills/playbooks/tools/playbook-update.ts +110 -6
- package/src/config/bundled-skills/public-ingress/SKILL.md +22 -5
- package/src/config/bundled-skills/twitter/SKILL.md +103 -17
- package/src/config/defaults.ts +26 -2
- package/src/config/schema.ts +178 -9
- package/src/config/types.ts +3 -0
- package/src/config/vellum-skills/telegram-setup/SKILL.md +56 -61
- package/src/daemon/assistant-attachments.ts +4 -2
- package/src/daemon/handlers/apps.ts +69 -0
- package/src/daemon/handlers/config.ts +543 -24
- package/src/daemon/handlers/index.ts +1 -0
- package/src/daemon/handlers/sessions.ts +22 -6
- package/src/daemon/handlers/shared.ts +2 -1
- package/src/daemon/handlers/skills.ts +5 -20
- package/src/daemon/ipc-contract-inventory.json +28 -0
- package/src/daemon/ipc-contract.ts +168 -10
- package/src/daemon/ipc-validate.ts +17 -0
- package/src/daemon/lifecycle.ts +2 -0
- package/src/daemon/server.ts +78 -72
- package/src/daemon/session-attachments.ts +1 -1
- package/src/daemon/session-conflict-gate.ts +62 -6
- package/src/daemon/session-notifiers.ts +1 -1
- package/src/daemon/session-process.ts +62 -3
- package/src/daemon/session-tool-setup.ts +1 -2
- package/src/daemon/tls-certs.ts +189 -0
- package/src/daemon/video-thumbnail.ts +5 -3
- package/src/hooks/manager.ts +5 -9
- package/src/memory/app-git-service.ts +295 -0
- package/src/memory/app-store.ts +21 -0
- package/src/memory/conflict-intent.ts +47 -4
- package/src/memory/conflict-policy.ts +73 -0
- package/src/memory/conflict-store.ts +9 -1
- package/src/memory/contradiction-checker.ts +28 -0
- package/src/memory/conversation-key-store.ts +15 -0
- package/src/memory/db.ts +81 -0
- package/src/memory/embedding-local.ts +3 -13
- package/src/memory/external-conversation-store.ts +234 -0
- package/src/memory/job-handlers/conflict.ts +22 -2
- package/src/memory/jobs-worker.ts +67 -28
- package/src/memory/runs-store.ts +54 -7
- package/src/memory/schema.ts +20 -0
- package/src/messaging/provider.ts +9 -0
- package/src/messaging/providers/telegram-bot/adapter.ts +162 -0
- package/src/messaging/providers/telegram-bot/client.ts +104 -0
- package/src/messaging/providers/telegram-bot/types.ts +15 -0
- package/src/messaging/registry.ts +1 -0
- package/src/permissions/checker.ts +48 -44
- package/src/permissions/defaults.ts +11 -0
- package/src/permissions/prompter.ts +0 -4
- package/src/permissions/shell-identity.ts +227 -0
- package/src/permissions/trust-store.ts +76 -53
- package/src/permissions/types.ts +0 -19
- package/src/permissions/workspace-policy.ts +114 -0
- package/src/providers/retry.ts +12 -37
- package/src/runtime/assistant-event-hub.ts +41 -4
- package/src/runtime/channel-approval-parser.ts +60 -0
- package/src/runtime/channel-approval-types.ts +71 -0
- package/src/runtime/channel-approvals.ts +145 -0
- package/src/runtime/gateway-client.ts +16 -0
- package/src/runtime/http-server.ts +29 -9
- package/src/runtime/routes/call-routes.ts +52 -2
- package/src/runtime/routes/channel-routes.ts +296 -16
- package/src/runtime/routes/conversation-routes.ts +12 -5
- package/src/runtime/routes/events-routes.ts +97 -28
- package/src/runtime/routes/run-routes.ts +2 -7
- package/src/runtime/run-orchestrator.ts +0 -3
- package/src/schedule/recurrence-engine.ts +26 -2
- package/src/schedule/recurrence-types.ts +1 -1
- package/src/schedule/schedule-store.ts +12 -3
- package/src/security/secret-scanner.ts +7 -0
- package/src/tasks/ephemeral-permissions.ts +0 -2
- package/src/tasks/task-scheduler.ts +2 -1
- package/src/tools/calls/call-start.ts +8 -0
- package/src/tools/execution-target.ts +21 -0
- package/src/tools/execution-timeout.ts +49 -0
- package/src/tools/executor.ts +6 -135
- package/src/tools/network/web-search.ts +9 -32
- package/src/tools/policy-context.ts +29 -0
- package/src/tools/schedule/update.ts +8 -1
- package/src/tools/terminal/parser.ts +16 -18
- package/src/tools/types.ts +4 -11
- package/src/twitter/oauth-client.ts +102 -0
- package/src/twitter/router.ts +101 -0
- package/src/util/debounce.ts +88 -0
- package/src/util/network-info.ts +47 -0
- package/src/util/platform.ts +29 -4
- package/src/util/promise-guard.ts +37 -0
- package/src/util/retry.ts +98 -0
- package/src/util/truncate.ts +1 -1
- package/src/workspace/git-service.ts +129 -112
- package/src/tools/contacts/contact-merge.ts +0 -55
- package/src/tools/contacts/contact-search.ts +0 -58
- package/src/tools/contacts/contact-upsert.ts +0 -64
- package/src/tools/playbooks/index.ts +0 -4
- package/src/tools/playbooks/playbook-create.ts +0 -96
- package/src/tools/playbooks/playbook-delete.ts +0 -52
- package/src/tools/playbooks/playbook-list.ts +0 -74
- package/src/tools/playbooks/playbook-update.ts +0 -111
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import { describe, expect, test } from 'bun:test';
|
|
2
|
+
import {
|
|
3
|
+
isConflictKindEligible,
|
|
4
|
+
isConflictKindPairEligible,
|
|
5
|
+
isTransientTrackingStatement,
|
|
6
|
+
isDurableInstructionStatement,
|
|
7
|
+
isStatementConflictEligible,
|
|
8
|
+
} from '../memory/conflict-policy.js';
|
|
9
|
+
|
|
10
|
+
describe('conflict-policy', () => {
|
|
11
|
+
const config = { conflictableKinds: ['preference', 'profile', 'constraint'] };
|
|
12
|
+
|
|
13
|
+
describe('isConflictKindEligible', () => {
|
|
14
|
+
test('returns true for eligible kind', () => {
|
|
15
|
+
expect(isConflictKindEligible('preference', config)).toBe(true);
|
|
16
|
+
expect(isConflictKindEligible('profile', config)).toBe(true);
|
|
17
|
+
expect(isConflictKindEligible('constraint', config)).toBe(true);
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
test('returns false for ineligible kind', () => {
|
|
21
|
+
expect(isConflictKindEligible('project', config)).toBe(false);
|
|
22
|
+
expect(isConflictKindEligible('todo', config)).toBe(false);
|
|
23
|
+
expect(isConflictKindEligible('fact', config)).toBe(false);
|
|
24
|
+
});
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
describe('isConflictKindPairEligible', () => {
|
|
28
|
+
test('returns true when both kinds are eligible', () => {
|
|
29
|
+
expect(isConflictKindPairEligible('preference', 'profile', config)).toBe(true);
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
test('returns false when existing kind is ineligible', () => {
|
|
33
|
+
expect(isConflictKindPairEligible('project', 'preference', config)).toBe(false);
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
test('returns false when candidate kind is ineligible', () => {
|
|
37
|
+
expect(isConflictKindPairEligible('preference', 'todo', config)).toBe(false);
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
test('returns false when both kinds are ineligible', () => {
|
|
41
|
+
expect(isConflictKindPairEligible('project', 'todo', config)).toBe(false);
|
|
42
|
+
});
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
describe('isTransientTrackingStatement', () => {
|
|
46
|
+
test('detects PR URLs', () => {
|
|
47
|
+
expect(isTransientTrackingStatement('Track https://github.com/org/repo/pull/5526')).toBe(true);
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
test('detects issue/ticket references', () => {
|
|
51
|
+
expect(isTransientTrackingStatement('Track PR #5526 and #5525')).toBe(true);
|
|
52
|
+
expect(isTransientTrackingStatement('See issue #42 for details')).toBe(true);
|
|
53
|
+
expect(isTransientTrackingStatement('Filed ticket 1234')).toBe(true);
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
test('detects tracking language', () => {
|
|
57
|
+
expect(isTransientTrackingStatement('While we wait for CI to pass')).toBe(true);
|
|
58
|
+
expect(isTransientTrackingStatement('This PR needs review')).toBe(true);
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
test('does not flag generic time words as transient', () => {
|
|
62
|
+
expect(isTransientTrackingStatement('The deadline is today')).toBe(false);
|
|
63
|
+
expect(isTransientTrackingStatement('I need this right now')).toBe(false);
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
test('does not flag durable statements', () => {
|
|
67
|
+
expect(isTransientTrackingStatement('Always answer with concise bullet points')).toBe(false);
|
|
68
|
+
expect(isTransientTrackingStatement('User prefers dark mode')).toBe(false);
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
test('does not false-positive on non-PR URLs', () => {
|
|
72
|
+
expect(isTransientTrackingStatement('Visit https://example.com for docs')).toBe(false);
|
|
73
|
+
});
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
describe('isDurableInstructionStatement', () => {
|
|
77
|
+
test('detects durable instruction cues', () => {
|
|
78
|
+
expect(isDurableInstructionStatement('Always answer with concise bullet points')).toBe(true);
|
|
79
|
+
expect(isDurableInstructionStatement('Never use semicolons in JavaScript')).toBe(true);
|
|
80
|
+
expect(isDurableInstructionStatement('Use concise format for status updates')).toBe(true);
|
|
81
|
+
expect(isDurableInstructionStatement('The default database is Postgres')).toBe(true);
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
test('rejects statements without durable cues', () => {
|
|
85
|
+
expect(isDurableInstructionStatement('Check the build output')).toBe(false);
|
|
86
|
+
expect(isDurableInstructionStatement('Run the migration script')).toBe(false);
|
|
87
|
+
});
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
describe('isStatementConflictEligible', () => {
|
|
91
|
+
test('rejects transient statements for any kind', () => {
|
|
92
|
+
expect(isStatementConflictEligible('preference', 'Track PR #5526')).toBe(false);
|
|
93
|
+
expect(isStatementConflictEligible('instruction', 'This PR needs review')).toBe(false);
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
test('accepts durable instruction statements', () => {
|
|
97
|
+
expect(isStatementConflictEligible('instruction', 'Always use TypeScript strict mode')).toBe(true);
|
|
98
|
+
expect(isStatementConflictEligible('style', 'Default to concise format')).toBe(true);
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
test('rejects non-durable instruction statements', () => {
|
|
102
|
+
expect(isStatementConflictEligible('instruction', 'Run the build first')).toBe(false);
|
|
103
|
+
expect(isStatementConflictEligible('style', 'Check the output')).toBe(false);
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
test('accepts non-transient statements for non-instruction kinds', () => {
|
|
107
|
+
expect(isStatementConflictEligible('preference', 'User prefers dark mode')).toBe(true);
|
|
108
|
+
expect(isStatementConflictEligible('fact', 'User works at Acme Corp')).toBe(true);
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
test('rejects kinds not in conflictableKinds when config is provided', () => {
|
|
112
|
+
const policyConfig = { conflictableKinds: ['preference', 'profile'] };
|
|
113
|
+
expect(isStatementConflictEligible('fact', 'User works at Acme Corp', policyConfig)).toBe(false);
|
|
114
|
+
expect(isStatementConflictEligible('preference', 'User prefers dark mode', policyConfig)).toBe(true);
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
test('skips kind check when config is omitted', () => {
|
|
118
|
+
expect(isStatementConflictEligible('fact', 'User works at Acme Corp')).toBe(true);
|
|
119
|
+
});
|
|
120
|
+
});
|
|
121
|
+
});
|
|
@@ -233,6 +233,8 @@ describe('conflict-store', () => {
|
|
|
233
233
|
expect(details).toHaveLength(1);
|
|
234
234
|
expect(details[0].existingStatement).toBe('Existing statement details');
|
|
235
235
|
expect(details[0].candidateStatement).toBe('Candidate statement details');
|
|
236
|
+
expect(details[0].existingKind).toBe('fact');
|
|
237
|
+
expect(details[0].candidateKind).toBe('fact');
|
|
236
238
|
});
|
|
237
239
|
|
|
238
240
|
test('applyConflictResolution keeps candidate and resolves conflict row', () => {
|
|
@@ -32,9 +32,9 @@ mock.module('../config/loader.js', () => ({
|
|
|
32
32
|
import type { Database } from 'bun:sqlite';
|
|
33
33
|
import { initializeDb, getDb, resetDb } from '../memory/db.js';
|
|
34
34
|
import type { ToolContext } from '../tools/types.js';
|
|
35
|
-
import { executeContactUpsert } from '../
|
|
36
|
-
import { executeContactSearch } from '../
|
|
37
|
-
import { executeContactMerge } from '../
|
|
35
|
+
import { executeContactUpsert } from '../config/bundled-skills/contacts/tools/contact-upsert.js';
|
|
36
|
+
import { executeContactSearch } from '../config/bundled-skills/contacts/tools/contact-search.js';
|
|
37
|
+
import { executeContactMerge } from '../config/bundled-skills/contacts/tools/contact-merge.js';
|
|
38
38
|
|
|
39
39
|
initializeDb();
|
|
40
40
|
|
|
@@ -51,9 +51,18 @@ mock.module('../util/logger.js', () => ({
|
|
|
51
51
|
}),
|
|
52
52
|
}));
|
|
53
53
|
|
|
54
|
+
let mockConflictableKinds: string[] = [
|
|
55
|
+
'preference', 'profile', 'constraint', 'instruction', 'style',
|
|
56
|
+
];
|
|
57
|
+
|
|
54
58
|
mock.module('../config/loader.js', () => ({
|
|
55
59
|
getConfig: () => ({
|
|
56
60
|
apiKeys: { anthropic: 'test-key' },
|
|
61
|
+
memory: {
|
|
62
|
+
conflicts: {
|
|
63
|
+
conflictableKinds: mockConflictableKinds,
|
|
64
|
+
},
|
|
65
|
+
},
|
|
57
66
|
}),
|
|
58
67
|
}));
|
|
59
68
|
|
|
@@ -67,6 +76,9 @@ beforeAll(() => {
|
|
|
67
76
|
|
|
68
77
|
beforeEach(() => {
|
|
69
78
|
classifyCallCount = 0;
|
|
79
|
+
mockConflictableKinds = [
|
|
80
|
+
'preference', 'profile', 'constraint', 'instruction', 'style',
|
|
81
|
+
];
|
|
70
82
|
const db = getDb();
|
|
71
83
|
db.run('DELETE FROM memory_item_conflicts');
|
|
72
84
|
db.run('DELETE FROM memory_item_sources');
|
|
@@ -88,12 +100,13 @@ function insertMemoryItem(params: {
|
|
|
88
100
|
statement: string;
|
|
89
101
|
scopeId?: string;
|
|
90
102
|
status?: 'active' | 'pending_clarification';
|
|
103
|
+
kind?: string;
|
|
91
104
|
}): void {
|
|
92
105
|
const now = Date.now();
|
|
93
106
|
const db = getDb();
|
|
94
107
|
db.insert(memoryItems).values({
|
|
95
108
|
id: params.id,
|
|
96
|
-
kind: 'preference',
|
|
109
|
+
kind: params.kind ?? 'preference',
|
|
97
110
|
subject: 'framework preference',
|
|
98
111
|
statement: params.statement,
|
|
99
112
|
status: params.status ?? 'active',
|
|
@@ -213,4 +226,89 @@ describe('checkContradictions', () => {
|
|
|
213
226
|
expect(candidate?.status).toBe('active');
|
|
214
227
|
expect(conflicts).toHaveLength(0);
|
|
215
228
|
});
|
|
229
|
+
|
|
230
|
+
test('project kind ambiguous contradiction does not generate pending conflict with default config', async () => {
|
|
231
|
+
nextRelationship = 'ambiguous_contradiction';
|
|
232
|
+
nextExplanation = 'Project items may conflict but are not durable.';
|
|
233
|
+
|
|
234
|
+
insertMemoryItem({
|
|
235
|
+
id: 'item-existing-project',
|
|
236
|
+
statement: 'The backend uses Node.js.',
|
|
237
|
+
kind: 'project',
|
|
238
|
+
});
|
|
239
|
+
insertMemoryItem({
|
|
240
|
+
id: 'item-candidate-project',
|
|
241
|
+
statement: 'The backend uses Deno.',
|
|
242
|
+
kind: 'project',
|
|
243
|
+
});
|
|
244
|
+
|
|
245
|
+
await checkContradictions('item-candidate-project');
|
|
246
|
+
|
|
247
|
+
expect(classifyCallCount).toBe(0);
|
|
248
|
+
const db = getDb();
|
|
249
|
+
const conflicts = db.select().from(memoryItemConflicts).all();
|
|
250
|
+
expect(conflicts).toHaveLength(0);
|
|
251
|
+
});
|
|
252
|
+
|
|
253
|
+
test('skips classification when item kind is not in conflictableKinds', async () => {
|
|
254
|
+
mockConflictableKinds = ['instruction', 'style'];
|
|
255
|
+
nextRelationship = 'ambiguous_contradiction';
|
|
256
|
+
|
|
257
|
+
insertMemoryItem({
|
|
258
|
+
id: 'item-existing-ineligible',
|
|
259
|
+
statement: 'User prefers React for frontend work.',
|
|
260
|
+
});
|
|
261
|
+
insertMemoryItem({
|
|
262
|
+
id: 'item-candidate-ineligible',
|
|
263
|
+
statement: 'User prefers Vue for frontend work.',
|
|
264
|
+
});
|
|
265
|
+
|
|
266
|
+
await checkContradictions('item-candidate-ineligible');
|
|
267
|
+
|
|
268
|
+
expect(classifyCallCount).toBe(0);
|
|
269
|
+
const db = getDb();
|
|
270
|
+
const conflicts = db.select().from(memoryItemConflicts).all();
|
|
271
|
+
expect(conflicts).toHaveLength(0);
|
|
272
|
+
});
|
|
273
|
+
|
|
274
|
+
test('skips classification when candidate statement contains PR-tracking content', async () => {
|
|
275
|
+
nextRelationship = 'ambiguous_contradiction';
|
|
276
|
+
|
|
277
|
+
insertMemoryItem({
|
|
278
|
+
id: 'item-existing-pr-tracking',
|
|
279
|
+
statement: 'Track PR #5526 for review.',
|
|
280
|
+
});
|
|
281
|
+
insertMemoryItem({
|
|
282
|
+
id: 'item-candidate-pr-tracking',
|
|
283
|
+
statement: 'Track PR #5525 for review.',
|
|
284
|
+
});
|
|
285
|
+
|
|
286
|
+
await checkContradictions('item-candidate-pr-tracking');
|
|
287
|
+
|
|
288
|
+
expect(classifyCallCount).toBe(0);
|
|
289
|
+
const db = getDb();
|
|
290
|
+
const conflicts = db.select().from(memoryItemConflicts).all();
|
|
291
|
+
expect(conflicts).toHaveLength(0);
|
|
292
|
+
});
|
|
293
|
+
|
|
294
|
+
test('durable preference contradiction still runs normal flow', async () => {
|
|
295
|
+
nextRelationship = 'ambiguous_contradiction';
|
|
296
|
+
nextExplanation = 'Both are valid preferences that conflict.';
|
|
297
|
+
|
|
298
|
+
insertMemoryItem({
|
|
299
|
+
id: 'item-existing-durable',
|
|
300
|
+
statement: 'User prefers React for frontend work.',
|
|
301
|
+
});
|
|
302
|
+
insertMemoryItem({
|
|
303
|
+
id: 'item-candidate-durable',
|
|
304
|
+
statement: 'User prefers Vue for frontend work.',
|
|
305
|
+
});
|
|
306
|
+
|
|
307
|
+
await checkContradictions('item-candidate-durable');
|
|
308
|
+
|
|
309
|
+
expect(classifyCallCount).toBe(1);
|
|
310
|
+
const db = getDb();
|
|
311
|
+
const conflicts = db.select().from(memoryItemConflicts).all();
|
|
312
|
+
expect(conflicts).toHaveLength(1);
|
|
313
|
+
});
|
|
216
314
|
});
|
|
@@ -185,10 +185,17 @@ describe('Invariant 2: no generic plaintext secret read API', () => {
|
|
|
185
185
|
'email/providers/index.ts', // email provider API key lookup
|
|
186
186
|
'tools/network/script-proxy/session-manager.ts', // proxy credential injection at runtime
|
|
187
187
|
'messaging/registry.ts', // checks stored credentials for connected providers
|
|
188
|
+
'calls/call-domain.ts', // caller identity resolution (user phone number lookup)
|
|
189
|
+
'calls/elevenlabs-config.ts', // ElevenLabs voice quality API key lookup
|
|
188
190
|
'calls/twilio-config.ts', // call infrastructure credential lookup
|
|
189
191
|
'calls/twilio-provider.ts', // call infrastructure credential lookup
|
|
192
|
+
'cli/config-commands.ts', // CLI credential management commands
|
|
190
193
|
'runtime/http-server.ts', // HTTP server credential lookup
|
|
191
194
|
'daemon/handlers/twitter-auth.ts', // Twitter OAuth token storage
|
|
195
|
+
'twitter/oauth-client.ts', // Twitter OAuth API client (reads access token for API calls)
|
|
196
|
+
'calls/elevenlabs-config.ts', // ElevenLabs credential lookup
|
|
197
|
+
'cli/config-commands.ts', // CLI config management
|
|
198
|
+
'messaging/providers/telegram-bot/adapter.ts', // Telegram bot token lookup for connectivity check
|
|
192
199
|
]);
|
|
193
200
|
|
|
194
201
|
const thisDir = dirname(fileURLToPath(import.meta.url));
|
|
@@ -267,18 +274,27 @@ describe('Invariant 3: secrets never logged in plaintext', () => {
|
|
|
267
274
|
});
|
|
268
275
|
} else if (tc.component === 'ipc_decode') {
|
|
269
276
|
// PR 24 — IPC decode log hygiene: the TS daemon's IPC parser must
|
|
270
|
-
// not
|
|
277
|
+
// not log raw message content that could contain secrets.
|
|
278
|
+
// Logging metadata (line length, error type) is acceptable; logging
|
|
279
|
+
// the raw line, trimmed content, or error.message is not.
|
|
271
280
|
test(`${tc.label}`, () => {
|
|
272
281
|
const thisDir = dirname(fileURLToPath(import.meta.url));
|
|
273
282
|
const ipcSrc = readFileSync(
|
|
274
283
|
resolve(thisDir, '../daemon/ipc-protocol.ts'),
|
|
275
284
|
'utf-8',
|
|
276
285
|
);
|
|
277
|
-
//
|
|
278
|
-
//
|
|
279
|
-
//
|
|
280
|
-
|
|
281
|
-
|
|
286
|
+
// Verify log calls never include raw content fields — only safe
|
|
287
|
+
// metadata like lineLength and errorType are permitted.
|
|
288
|
+
// `trimmed.length` is safe (numeric); `trimmed` alone would leak raw content.
|
|
289
|
+
// Use [^\n]* instead of [^)]* so that inner parentheses (e.g.
|
|
290
|
+
// helper calls like formatErr(err)) don't terminate the match
|
|
291
|
+
// early — avoiding false negatives — while still scoping each
|
|
292
|
+
// pattern to a single line (no cross-statement matching).
|
|
293
|
+
expect(ipcSrc).not.toMatch(/\blog\.\w+\([^\n]*[{,]\s*trimmed[^.]/);
|
|
294
|
+
expect(ipcSrc).not.toMatch(/\blog\.\w+\([^\n]*[{,]\s*line[^L]/);
|
|
295
|
+
expect(ipcSrc).not.toMatch(/\blog\.\w+\([^\n]*[{,]\s*data\b/);
|
|
296
|
+
expect(ipcSrc).not.toMatch(/\blog\.\w+\([^\n]*[{,]\s*buffer\b/);
|
|
297
|
+
expect(ipcSrc).not.toMatch(/\blog\.\w+\([^\n]*err\.message\b/);
|
|
282
298
|
});
|
|
283
299
|
} else {
|
|
284
300
|
// PR 25 — secret prompter log hygiene: verify the prompter source
|