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
|
@@ -148,16 +148,10 @@ describe('Trust Store', () => {
|
|
|
148
148
|
expect(userRules[1].decision).toBe('allow');
|
|
149
149
|
});
|
|
150
150
|
|
|
151
|
-
test('accepts
|
|
151
|
+
test('accepts executionTarget option and persists it', () => {
|
|
152
152
|
const rule = addRule('skill_tool', 'skill_tool:*', '/tmp', 'allow', 100, {
|
|
153
|
-
principalKind: 'skill',
|
|
154
|
-
principalId: 'my-skill-42',
|
|
155
|
-
principalVersion: 'sha256-abc123',
|
|
156
153
|
executionTarget: 'sandbox',
|
|
157
154
|
});
|
|
158
|
-
expect(rule.principalKind).toBe('skill');
|
|
159
|
-
expect(rule.principalId).toBe('my-skill-42');
|
|
160
|
-
expect(rule.principalVersion).toBe('sha256-abc123');
|
|
161
155
|
expect(rule.executionTarget).toBe('sandbox');
|
|
162
156
|
|
|
163
157
|
// Verify persistence to disk
|
|
@@ -165,24 +159,15 @@ describe('Trust Store', () => {
|
|
|
165
159
|
const rules = getAllRules();
|
|
166
160
|
const found = rules.find((r) => r.id === rule.id);
|
|
167
161
|
expect(found).toBeDefined();
|
|
168
|
-
expect(found!.principalKind).toBe('skill');
|
|
169
|
-
expect(found!.principalId).toBe('my-skill-42');
|
|
170
|
-
expect(found!.principalVersion).toBe('sha256-abc123');
|
|
171
162
|
expect(found!.executionTarget).toBe('sandbox');
|
|
172
163
|
});
|
|
173
164
|
|
|
174
|
-
test('accepts all contextual options together (
|
|
165
|
+
test('accepts all contextual options together (target, allowHighRisk)', () => {
|
|
175
166
|
const rule = addRule('risky_tool', 'risky_tool:*', 'everywhere', 'allow', 100, {
|
|
176
167
|
allowHighRisk: true,
|
|
177
|
-
principalKind: 'skill',
|
|
178
|
-
principalId: 'dangerous-skill',
|
|
179
|
-
principalVersion: 'sha256-deadbeef',
|
|
180
168
|
executionTarget: 'host',
|
|
181
169
|
});
|
|
182
170
|
expect(rule.allowHighRisk).toBe(true);
|
|
183
|
-
expect(rule.principalKind).toBe('skill');
|
|
184
|
-
expect(rule.principalId).toBe('dangerous-skill');
|
|
185
|
-
expect(rule.principalVersion).toBe('sha256-deadbeef');
|
|
186
171
|
expect(rule.executionTarget).toBe('host');
|
|
187
172
|
|
|
188
173
|
// Verify on disk
|
|
@@ -190,26 +175,17 @@ describe('Trust Store', () => {
|
|
|
190
175
|
const diskRule = raw.rules.find((r: { id: string }) => r.id === rule.id);
|
|
191
176
|
expect(diskRule).toBeDefined();
|
|
192
177
|
expect(diskRule.allowHighRisk).toBe(true);
|
|
193
|
-
expect(diskRule.principalKind).toBe('skill');
|
|
194
|
-
expect(diskRule.principalId).toBe('dangerous-skill');
|
|
195
|
-
expect(diskRule.principalVersion).toBe('sha256-deadbeef');
|
|
196
178
|
expect(diskRule.executionTarget).toBe('host');
|
|
197
179
|
});
|
|
198
180
|
|
|
199
|
-
test('addRule without
|
|
181
|
+
test('addRule without options does not set optional fields', () => {
|
|
200
182
|
const rule = addRule('bash', 'echo *', '/tmp');
|
|
201
|
-
expect(rule.principalKind).toBeUndefined();
|
|
202
|
-
expect(rule.principalId).toBeUndefined();
|
|
203
|
-
expect(rule.principalVersion).toBeUndefined();
|
|
204
183
|
expect(rule.executionTarget).toBeUndefined();
|
|
205
184
|
|
|
206
185
|
// Verify on disk
|
|
207
186
|
const raw = JSON.parse(readFileSync(trustPath, 'utf-8'));
|
|
208
187
|
const diskRule = raw.rules.find((r: { id: string }) => r.id === rule.id);
|
|
209
188
|
expect(diskRule).toBeDefined();
|
|
210
|
-
expect(diskRule).not.toHaveProperty('principalKind');
|
|
211
|
-
expect(diskRule).not.toHaveProperty('principalId');
|
|
212
|
-
expect(diskRule).not.toHaveProperty('principalVersion');
|
|
213
189
|
expect(diskRule).not.toHaveProperty('executionTarget');
|
|
214
190
|
});
|
|
215
191
|
});
|
|
@@ -741,6 +717,7 @@ describe('Trust Store', () => {
|
|
|
741
717
|
'host_file_edit',
|
|
742
718
|
'host_file_read',
|
|
743
719
|
'host_file_write',
|
|
720
|
+
'memory_search',
|
|
744
721
|
'scaffold_managed_skill',
|
|
745
722
|
'skill_load',
|
|
746
723
|
'ui_dismiss',
|
|
@@ -1025,12 +1002,9 @@ describe('Trust Store', () => {
|
|
|
1025
1002
|
// ── trust rule schema v3 (PR 14) ──────────────────────────────
|
|
1026
1003
|
|
|
1027
1004
|
describe('trust rule schema v3 (PR 14)', () => {
|
|
1028
|
-
test('new rules can include
|
|
1005
|
+
test('new rules can include v3 optional fields', () => {
|
|
1029
1006
|
const rule = addRule('bash', 'git *', '/tmp');
|
|
1030
|
-
// Manually set v3
|
|
1031
|
-
rule.principalKind = 'skill';
|
|
1032
|
-
rule.principalId = 'my-skill';
|
|
1033
|
-
rule.principalVersion = 'abc123';
|
|
1007
|
+
// Manually set v3 optional fields on the rule and persist
|
|
1034
1008
|
rule.executionTarget = '/usr/local/bin/node';
|
|
1035
1009
|
rule.allowHighRisk = true;
|
|
1036
1010
|
// Re-persist the updated rules
|
|
@@ -1044,9 +1018,6 @@ describe('Trust Store', () => {
|
|
|
1044
1018
|
const reloaded = getAllRules();
|
|
1045
1019
|
const found = reloaded.find((r) => r.id === rule.id);
|
|
1046
1020
|
expect(found).toBeDefined();
|
|
1047
|
-
expect(found!.principalKind).toBe('skill');
|
|
1048
|
-
expect(found!.principalId).toBe('my-skill');
|
|
1049
|
-
expect(found!.principalVersion).toBe('abc123');
|
|
1050
1021
|
expect(found!.executionTarget).toBe('/usr/local/bin/node');
|
|
1051
1022
|
expect(found!.allowHighRisk).toBe(true);
|
|
1052
1023
|
});
|
|
@@ -1071,7 +1042,7 @@ describe('Trust Store', () => {
|
|
|
1071
1042
|
expect(data.version).toBe(3);
|
|
1072
1043
|
});
|
|
1073
1044
|
|
|
1074
|
-
test('v2 rules survive v3 migration with no
|
|
1045
|
+
test('v2 rules survive v3 migration with no v3-only fields', () => {
|
|
1075
1046
|
mkdirSync(dirname(trustPath), { recursive: true });
|
|
1076
1047
|
writeFileSync(trustPath, JSON.stringify({
|
|
1077
1048
|
version: 2,
|
|
@@ -1104,10 +1075,7 @@ describe('Trust Store', () => {
|
|
|
1104
1075
|
expect(ruleB).toBeDefined();
|
|
1105
1076
|
expect(ruleA!.pattern).toBe('git *');
|
|
1106
1077
|
expect(ruleB!.decision).toBe('deny');
|
|
1107
|
-
// No
|
|
1108
|
-
expect(ruleA).not.toHaveProperty('principalKind');
|
|
1109
|
-
expect(ruleA).not.toHaveProperty('principalId');
|
|
1110
|
-
expect(ruleA).not.toHaveProperty('principalVersion');
|
|
1078
|
+
// No v3-only fields should be present
|
|
1111
1079
|
expect(ruleA).not.toHaveProperty('executionTarget');
|
|
1112
1080
|
expect(ruleA).not.toHaveProperty('allowHighRisk');
|
|
1113
1081
|
});
|
|
@@ -1253,25 +1221,22 @@ describe('Trust Store', () => {
|
|
|
1253
1221
|
expect(userRules).toHaveLength(1);
|
|
1254
1222
|
});
|
|
1255
1223
|
|
|
1256
|
-
test('v3 file with
|
|
1224
|
+
test('v3 file with optional fields is loaded correctly without re-migration', () => {
|
|
1257
1225
|
mkdirSync(dirname(trustPath), { recursive: true });
|
|
1258
1226
|
const v3Rules = [
|
|
1259
1227
|
{
|
|
1260
|
-
id: 'v3-with-
|
|
1228
|
+
id: 'v3-with-options',
|
|
1261
1229
|
tool: 'bash',
|
|
1262
1230
|
pattern: 'skill-cmd *',
|
|
1263
1231
|
scope: '/tmp',
|
|
1264
1232
|
decision: 'allow',
|
|
1265
1233
|
priority: 100,
|
|
1266
1234
|
createdAt: 7000,
|
|
1267
|
-
principalKind: 'skill',
|
|
1268
|
-
principalId: 'my-skill',
|
|
1269
|
-
principalVersion: 'sha256-abc',
|
|
1270
1235
|
executionTarget: '/usr/bin/node',
|
|
1271
1236
|
allowHighRisk: false,
|
|
1272
1237
|
},
|
|
1273
1238
|
{
|
|
1274
|
-
id: 'v3-without-
|
|
1239
|
+
id: 'v3-without-options',
|
|
1275
1240
|
tool: 'bash',
|
|
1276
1241
|
pattern: 'git *',
|
|
1277
1242
|
scope: '/tmp',
|
|
@@ -1284,23 +1249,19 @@ describe('Trust Store', () => {
|
|
|
1284
1249
|
clearCache();
|
|
1285
1250
|
const rules = getAllRules();
|
|
1286
1251
|
|
|
1287
|
-
// Rule with
|
|
1288
|
-
const
|
|
1289
|
-
expect(
|
|
1290
|
-
expect(
|
|
1291
|
-
expect(
|
|
1292
|
-
expect(withPrincipal!.principalVersion).toBe('sha256-abc');
|
|
1293
|
-
expect(withPrincipal!.executionTarget).toBe('/usr/bin/node');
|
|
1294
|
-
expect(withPrincipal!.allowHighRisk).toBe(false);
|
|
1252
|
+
// Rule with optional fields should have them preserved
|
|
1253
|
+
const withOptions = rules.find((r) => r.id === 'v3-with-options');
|
|
1254
|
+
expect(withOptions).toBeDefined();
|
|
1255
|
+
expect(withOptions!.executionTarget).toBe('/usr/bin/node');
|
|
1256
|
+
expect(withOptions!.allowHighRisk).toBe(false);
|
|
1295
1257
|
|
|
1296
|
-
// Rule without
|
|
1297
|
-
const
|
|
1298
|
-
expect(
|
|
1299
|
-
expect(
|
|
1300
|
-
expect(withoutPrincipal).not.toHaveProperty('principalId');
|
|
1258
|
+
// Rule without optional fields should remain without them
|
|
1259
|
+
const withoutOptions = rules.find((r) => r.id === 'v3-without-options');
|
|
1260
|
+
expect(withoutOptions).toBeDefined();
|
|
1261
|
+
expect(withoutOptions).not.toHaveProperty('executionTarget');
|
|
1301
1262
|
});
|
|
1302
1263
|
|
|
1303
|
-
test('v2 migration preserves rule meaning exactly — no
|
|
1264
|
+
test('v2 migration preserves rule meaning exactly — no extra fields added', () => {
|
|
1304
1265
|
mkdirSync(dirname(trustPath), { recursive: true });
|
|
1305
1266
|
const originalRules = [
|
|
1306
1267
|
{
|
|
@@ -1336,22 +1297,10 @@ describe('Trust Store', () => {
|
|
|
1336
1297
|
expect(migrated!.decision).toBe(original.decision);
|
|
1337
1298
|
expect(migrated!.priority).toBe(original.priority);
|
|
1338
1299
|
expect(migrated!.createdAt).toBe(original.createdAt);
|
|
1339
|
-
// No
|
|
1340
|
-
expect(migrated).not.toHaveProperty('principalKind');
|
|
1341
|
-
expect(migrated).not.toHaveProperty('principalId');
|
|
1342
|
-
expect(migrated).not.toHaveProperty('principalVersion');
|
|
1300
|
+
// No extra fields were injected by migration
|
|
1343
1301
|
expect(migrated).not.toHaveProperty('executionTarget');
|
|
1344
1302
|
expect(migrated).not.toHaveProperty('allowHighRisk');
|
|
1345
1303
|
}
|
|
1346
|
-
|
|
1347
|
-
// Verify disk representation also has no principal fields on user rules
|
|
1348
|
-
const data = JSON.parse(readFileSync(trustPath, 'utf-8'));
|
|
1349
|
-
for (const original of originalRules) {
|
|
1350
|
-
const diskRule = data.rules.find((r: { id: string }) => r.id === original.id);
|
|
1351
|
-
expect(diskRule).toBeDefined();
|
|
1352
|
-
expect(diskRule).not.toHaveProperty('principalKind');
|
|
1353
|
-
expect(diskRule).not.toHaveProperty('principalId');
|
|
1354
|
-
}
|
|
1355
1304
|
});
|
|
1356
1305
|
|
|
1357
1306
|
test('v1 → v3 full migration preserves rules and adds priority', () => {
|
|
@@ -1373,54 +1322,15 @@ describe('Trust Store', () => {
|
|
|
1373
1322
|
expect(rule).toBeDefined();
|
|
1374
1323
|
// v1 → v2 adds priority 100
|
|
1375
1324
|
expect(rule!.priority).toBe(100);
|
|
1376
|
-
// v2 → v3 adds no principal fields
|
|
1377
|
-
expect(rule).not.toHaveProperty('principalKind');
|
|
1378
|
-
expect(rule).not.toHaveProperty('principalId');
|
|
1379
1325
|
// File should be v3 on disk
|
|
1380
1326
|
const data = JSON.parse(readFileSync(trustPath, 'utf-8'));
|
|
1381
1327
|
expect(data.version).toBe(3);
|
|
1382
1328
|
});
|
|
1383
1329
|
});
|
|
1384
1330
|
|
|
1385
|
-
// ──
|
|
1386
|
-
// These tests verify that addRule() without explicit principal options
|
|
1387
|
-
// creates wildcard rules. The TrustRule schema *does* support principal
|
|
1388
|
-
// and version fields (since PR 14), but they are only set when explicitly
|
|
1389
|
-
// provided via the options parameter.
|
|
1390
|
-
|
|
1391
|
-
describe('backward compat: addRule without principal options (PR 2/40)', () => {
|
|
1392
|
-
test('addRule without principal options creates rules without principal fields', () => {
|
|
1393
|
-
const rule = addRule('skill_test_tool', 'skill_test_tool:*', '/tmp');
|
|
1394
|
-
expect(rule).not.toHaveProperty('principalKind');
|
|
1395
|
-
expect(rule).not.toHaveProperty('principalId');
|
|
1396
|
-
expect(rule).not.toHaveProperty('principalVersion');
|
|
1397
|
-
expect(rule).not.toHaveProperty('executionTarget');
|
|
1398
|
-
expect(rule).not.toHaveProperty('allowHighRisk');
|
|
1399
|
-
});
|
|
1400
|
-
|
|
1401
|
-
test('findHighestPriorityRule matches without policy context (backward compat)', () => {
|
|
1402
|
-
addRule('skill_test_tool', 'skill_test_tool:*', '/tmp', 'allow', 200);
|
|
1403
|
-
// Calling without the optional 4th ctx parameter still matches wildcard rules
|
|
1404
|
-
const match = findHighestPriorityRule('skill_test_tool', ['skill_test_tool:do-thing'], '/tmp');
|
|
1405
|
-
expect(match).not.toBeNull();
|
|
1406
|
-
expect(match!.decision).toBe('allow');
|
|
1407
|
-
});
|
|
1408
|
-
|
|
1409
|
-
test('trust file schema is v3 (rules created without principal fields)', () => {
|
|
1410
|
-
addRule('skill_test_tool', 'skill_test_tool:*', '/tmp');
|
|
1411
|
-
const raw = JSON.parse(readFileSync(trustPath, 'utf-8'));
|
|
1412
|
-
expect(raw.version).toBe(3);
|
|
1413
|
-
const userRule = raw.rules.find((r: { pattern: string }) => r.pattern === 'skill_test_tool:*');
|
|
1414
|
-
expect(userRule).toBeDefined();
|
|
1415
|
-
// addRule without principal options doesn't set principal fields
|
|
1416
|
-
expect(userRule).not.toHaveProperty('principalVersion');
|
|
1417
|
-
expect(userRule).not.toHaveProperty('principalKind');
|
|
1418
|
-
});
|
|
1419
|
-
});
|
|
1420
|
-
|
|
1421
|
-
// ── principal-aware rule matching (PR 16) ──────────────────────
|
|
1331
|
+
// ── executionTarget-aware rule matching ──────────────────────
|
|
1422
1332
|
|
|
1423
|
-
describe('
|
|
1333
|
+
describe('executionTarget-aware rule matching', () => {
|
|
1424
1334
|
/**
|
|
1425
1335
|
* Helper: write a v3 trust file with the given rules directly to disk,
|
|
1426
1336
|
* then clear the cache so the next getRules() call picks them up.
|
|
@@ -1431,26 +1341,17 @@ describe('Trust Store', () => {
|
|
|
1431
1341
|
clearCache();
|
|
1432
1342
|
}
|
|
1433
1343
|
|
|
1434
|
-
// ── wildcard semantics (no
|
|
1344
|
+
// ── wildcard semantics (no executionTarget on rule) ──────────
|
|
1435
1345
|
|
|
1436
|
-
describe('wildcard semantics — rules without
|
|
1437
|
-
test('rule with no
|
|
1346
|
+
describe('wildcard semantics — rules without executionTarget', () => {
|
|
1347
|
+
test('rule with no executionTarget matches when no context is provided', () => {
|
|
1438
1348
|
addRule('bash', 'git *', '/tmp', 'allow', 200);
|
|
1439
1349
|
const match = findHighestPriorityRule('bash', ['git status'], '/tmp');
|
|
1440
1350
|
expect(match).not.toBeNull();
|
|
1441
1351
|
expect(match!.decision).toBe('allow');
|
|
1442
1352
|
});
|
|
1443
1353
|
|
|
1444
|
-
test('rule with no
|
|
1445
|
-
addRule('bash', 'git *', '/tmp', 'allow', 200);
|
|
1446
|
-
const match = findHighestPriorityRule('bash', ['git status'], '/tmp', {
|
|
1447
|
-
principal: { kind: 'skill', id: 'my-skill', version: 'v1' },
|
|
1448
|
-
});
|
|
1449
|
-
expect(match).not.toBeNull();
|
|
1450
|
-
expect(match!.decision).toBe('allow');
|
|
1451
|
-
});
|
|
1452
|
-
|
|
1453
|
-
test('rule with no principal fields matches any execution target', () => {
|
|
1354
|
+
test('rule with no executionTarget matches any execution target', () => {
|
|
1454
1355
|
addRule('bash', 'git *', '/tmp', 'allow', 200);
|
|
1455
1356
|
const match = findHighestPriorityRule('bash', ['git status'], '/tmp', {
|
|
1456
1357
|
executionTarget: '/usr/bin/node',
|
|
@@ -1458,186 +1359,6 @@ describe('Trust Store', () => {
|
|
|
1458
1359
|
expect(match).not.toBeNull();
|
|
1459
1360
|
expect(match!.decision).toBe('allow');
|
|
1460
1361
|
});
|
|
1461
|
-
|
|
1462
|
-
test('rule with no principal fields matches context with both principal and target', () => {
|
|
1463
|
-
addRule('bash', 'npm *', '/tmp', 'allow', 200);
|
|
1464
|
-
const match = findHighestPriorityRule('bash', ['npm install'], '/tmp', {
|
|
1465
|
-
principal: { kind: 'skill', id: 'builder', version: 'sha256-xyz' },
|
|
1466
|
-
executionTarget: '/usr/local/bin/bun',
|
|
1467
|
-
});
|
|
1468
|
-
expect(match).not.toBeNull();
|
|
1469
|
-
});
|
|
1470
|
-
});
|
|
1471
|
-
|
|
1472
|
-
// ── principalKind matching ────────────────────────────────────
|
|
1473
|
-
|
|
1474
|
-
describe('principalKind matching', () => {
|
|
1475
|
-
test('rule with principalKind matches when context kind matches', () => {
|
|
1476
|
-
seedRules([{
|
|
1477
|
-
id: 'pk-match',
|
|
1478
|
-
tool: 'bash',
|
|
1479
|
-
pattern: 'echo *',
|
|
1480
|
-
scope: 'everywhere',
|
|
1481
|
-
decision: 'allow',
|
|
1482
|
-
priority: 200,
|
|
1483
|
-
createdAt: Date.now(),
|
|
1484
|
-
principalKind: 'skill',
|
|
1485
|
-
}]);
|
|
1486
|
-
const match = findHighestPriorityRule('bash', ['echo hello'], '/tmp', {
|
|
1487
|
-
principal: { kind: 'skill' },
|
|
1488
|
-
});
|
|
1489
|
-
expect(match).not.toBeNull();
|
|
1490
|
-
expect(match!.id).toBe('pk-match');
|
|
1491
|
-
});
|
|
1492
|
-
|
|
1493
|
-
test('rule with principalKind does NOT match when context kind differs', () => {
|
|
1494
|
-
seedRules([{
|
|
1495
|
-
id: 'pk-mismatch',
|
|
1496
|
-
tool: 'bash',
|
|
1497
|
-
pattern: 'echo *',
|
|
1498
|
-
scope: 'everywhere',
|
|
1499
|
-
decision: 'allow',
|
|
1500
|
-
priority: 200,
|
|
1501
|
-
createdAt: Date.now(),
|
|
1502
|
-
principalKind: 'skill',
|
|
1503
|
-
}]);
|
|
1504
|
-
const match = findHighestPriorityRule('bash', ['echo hello'], '/tmp', {
|
|
1505
|
-
principal: { kind: 'core' },
|
|
1506
|
-
});
|
|
1507
|
-
// Should not match the pk-mismatch rule; may still match a default rule
|
|
1508
|
-
expect(match === null || match.id !== 'pk-mismatch').toBe(true);
|
|
1509
|
-
});
|
|
1510
|
-
|
|
1511
|
-
test('rule with principalKind does NOT match when no context is provided', () => {
|
|
1512
|
-
seedRules([{
|
|
1513
|
-
id: 'pk-no-ctx',
|
|
1514
|
-
tool: 'bash',
|
|
1515
|
-
pattern: 'echo *',
|
|
1516
|
-
scope: 'everywhere',
|
|
1517
|
-
decision: 'allow',
|
|
1518
|
-
priority: 200,
|
|
1519
|
-
createdAt: Date.now(),
|
|
1520
|
-
principalKind: 'skill',
|
|
1521
|
-
}]);
|
|
1522
|
-
const match = findHighestPriorityRule('bash', ['echo hello'], '/tmp');
|
|
1523
|
-
expect(match === null || match.id !== 'pk-no-ctx').toBe(true);
|
|
1524
|
-
});
|
|
1525
|
-
});
|
|
1526
|
-
|
|
1527
|
-
// ── principalId matching ──────────────────────────────────────
|
|
1528
|
-
|
|
1529
|
-
describe('principalId matching', () => {
|
|
1530
|
-
test('rule with principalKind + principalId matches exact principal', () => {
|
|
1531
|
-
seedRules([{
|
|
1532
|
-
id: 'pid-exact',
|
|
1533
|
-
tool: 'bash',
|
|
1534
|
-
pattern: 'deploy *',
|
|
1535
|
-
scope: 'everywhere',
|
|
1536
|
-
decision: 'allow',
|
|
1537
|
-
priority: 200,
|
|
1538
|
-
createdAt: Date.now(),
|
|
1539
|
-
principalKind: 'skill',
|
|
1540
|
-
principalId: 'deployer',
|
|
1541
|
-
}]);
|
|
1542
|
-
const match = findHighestPriorityRule('bash', ['deploy prod'], '/tmp', {
|
|
1543
|
-
principal: { kind: 'skill', id: 'deployer' },
|
|
1544
|
-
});
|
|
1545
|
-
expect(match).not.toBeNull();
|
|
1546
|
-
expect(match!.id).toBe('pid-exact');
|
|
1547
|
-
});
|
|
1548
|
-
|
|
1549
|
-
test('rule with principalId does NOT match different id', () => {
|
|
1550
|
-
seedRules([{
|
|
1551
|
-
id: 'pid-diff',
|
|
1552
|
-
tool: 'bash',
|
|
1553
|
-
pattern: 'deploy *',
|
|
1554
|
-
scope: 'everywhere',
|
|
1555
|
-
decision: 'allow',
|
|
1556
|
-
priority: 200,
|
|
1557
|
-
createdAt: Date.now(),
|
|
1558
|
-
principalKind: 'skill',
|
|
1559
|
-
principalId: 'deployer',
|
|
1560
|
-
}]);
|
|
1561
|
-
const match = findHighestPriorityRule('bash', ['deploy prod'], '/tmp', {
|
|
1562
|
-
principal: { kind: 'skill', id: 'other-skill' },
|
|
1563
|
-
});
|
|
1564
|
-
expect(match === null || match.id !== 'pid-diff').toBe(true);
|
|
1565
|
-
});
|
|
1566
|
-
});
|
|
1567
|
-
|
|
1568
|
-
// ── principalVersion matching ─────────────────────────────────
|
|
1569
|
-
|
|
1570
|
-
describe('principalVersion matching', () => {
|
|
1571
|
-
test('rule with principalVersion matches exact version', () => {
|
|
1572
|
-
seedRules([{
|
|
1573
|
-
id: 'pv-exact',
|
|
1574
|
-
tool: 'bash',
|
|
1575
|
-
pattern: 'build *',
|
|
1576
|
-
scope: 'everywhere',
|
|
1577
|
-
decision: 'allow',
|
|
1578
|
-
priority: 200,
|
|
1579
|
-
createdAt: Date.now(),
|
|
1580
|
-
principalKind: 'skill',
|
|
1581
|
-
principalId: 'builder',
|
|
1582
|
-
principalVersion: 'sha256-abc123',
|
|
1583
|
-
}]);
|
|
1584
|
-
const match = findHighestPriorityRule('bash', ['build all'], '/tmp', {
|
|
1585
|
-
principal: { kind: 'skill', id: 'builder', version: 'sha256-abc123' },
|
|
1586
|
-
});
|
|
1587
|
-
expect(match).not.toBeNull();
|
|
1588
|
-
expect(match!.id).toBe('pv-exact');
|
|
1589
|
-
});
|
|
1590
|
-
|
|
1591
|
-
test('rule with principalVersion does NOT match different version', () => {
|
|
1592
|
-
seedRules([{
|
|
1593
|
-
id: 'pv-diff',
|
|
1594
|
-
tool: 'bash',
|
|
1595
|
-
pattern: 'build *',
|
|
1596
|
-
scope: 'everywhere',
|
|
1597
|
-
decision: 'allow',
|
|
1598
|
-
priority: 200,
|
|
1599
|
-
createdAt: Date.now(),
|
|
1600
|
-
principalKind: 'skill',
|
|
1601
|
-
principalId: 'builder',
|
|
1602
|
-
principalVersion: 'sha256-abc123',
|
|
1603
|
-
}]);
|
|
1604
|
-
const match = findHighestPriorityRule('bash', ['build all'], '/tmp', {
|
|
1605
|
-
principal: { kind: 'skill', id: 'builder', version: 'sha256-DIFFERENT' },
|
|
1606
|
-
});
|
|
1607
|
-
expect(match === null || match.id !== 'pv-diff').toBe(true);
|
|
1608
|
-
});
|
|
1609
|
-
|
|
1610
|
-
test('rule WITHOUT principalVersion matches any version (wildcard)', () => {
|
|
1611
|
-
seedRules([{
|
|
1612
|
-
id: 'pv-wildcard',
|
|
1613
|
-
tool: 'bash',
|
|
1614
|
-
pattern: 'build *',
|
|
1615
|
-
scope: 'everywhere',
|
|
1616
|
-
decision: 'allow',
|
|
1617
|
-
priority: 200,
|
|
1618
|
-
createdAt: Date.now(),
|
|
1619
|
-
principalKind: 'skill',
|
|
1620
|
-
principalId: 'builder',
|
|
1621
|
-
// no principalVersion — should match any version
|
|
1622
|
-
}]);
|
|
1623
|
-
const matchV1 = findHighestPriorityRule('bash', ['build all'], '/tmp', {
|
|
1624
|
-
principal: { kind: 'skill', id: 'builder', version: 'v1' },
|
|
1625
|
-
});
|
|
1626
|
-
expect(matchV1).not.toBeNull();
|
|
1627
|
-
expect(matchV1!.id).toBe('pv-wildcard');
|
|
1628
|
-
|
|
1629
|
-
const matchV2 = findHighestPriorityRule('bash', ['build all'], '/tmp', {
|
|
1630
|
-
principal: { kind: 'skill', id: 'builder', version: 'v2' },
|
|
1631
|
-
});
|
|
1632
|
-
expect(matchV2).not.toBeNull();
|
|
1633
|
-
expect(matchV2!.id).toBe('pv-wildcard');
|
|
1634
|
-
|
|
1635
|
-
const matchNoVersion = findHighestPriorityRule('bash', ['build all'], '/tmp', {
|
|
1636
|
-
principal: { kind: 'skill', id: 'builder' },
|
|
1637
|
-
});
|
|
1638
|
-
expect(matchNoVersion).not.toBeNull();
|
|
1639
|
-
expect(matchNoVersion!.id).toBe('pv-wildcard');
|
|
1640
|
-
});
|
|
1641
1362
|
});
|
|
1642
1363
|
|
|
1643
1364
|
// ── executionTarget matching ──────────────────────────────────
|
|
@@ -1703,139 +1424,6 @@ describe('Trust Store', () => {
|
|
|
1703
1424
|
});
|
|
1704
1425
|
});
|
|
1705
1426
|
|
|
1706
|
-
// ── combined principal + executionTarget ───────────────────────
|
|
1707
|
-
|
|
1708
|
-
describe('combined principal + executionTarget matching', () => {
|
|
1709
|
-
test('rule with both principal and executionTarget matches when all fields match', () => {
|
|
1710
|
-
seedRules([{
|
|
1711
|
-
id: 'combo-match',
|
|
1712
|
-
tool: 'bash',
|
|
1713
|
-
pattern: 'deploy *',
|
|
1714
|
-
scope: 'everywhere',
|
|
1715
|
-
decision: 'allow',
|
|
1716
|
-
priority: 200,
|
|
1717
|
-
createdAt: Date.now(),
|
|
1718
|
-
principalKind: 'skill',
|
|
1719
|
-
principalId: 'deployer',
|
|
1720
|
-
principalVersion: 'sha256-abc',
|
|
1721
|
-
executionTarget: '/usr/bin/node',
|
|
1722
|
-
}]);
|
|
1723
|
-
const match = findHighestPriorityRule('bash', ['deploy prod'], '/tmp', {
|
|
1724
|
-
principal: { kind: 'skill', id: 'deployer', version: 'sha256-abc' },
|
|
1725
|
-
executionTarget: '/usr/bin/node',
|
|
1726
|
-
});
|
|
1727
|
-
expect(match).not.toBeNull();
|
|
1728
|
-
expect(match!.id).toBe('combo-match');
|
|
1729
|
-
});
|
|
1730
|
-
|
|
1731
|
-
test('rule with both principal and executionTarget fails if principal mismatches', () => {
|
|
1732
|
-
seedRules([{
|
|
1733
|
-
id: 'combo-bad-principal',
|
|
1734
|
-
tool: 'bash',
|
|
1735
|
-
pattern: 'deploy *',
|
|
1736
|
-
scope: 'everywhere',
|
|
1737
|
-
decision: 'allow',
|
|
1738
|
-
priority: 200,
|
|
1739
|
-
createdAt: Date.now(),
|
|
1740
|
-
principalKind: 'skill',
|
|
1741
|
-
principalId: 'deployer',
|
|
1742
|
-
executionTarget: '/usr/bin/node',
|
|
1743
|
-
}]);
|
|
1744
|
-
const match = findHighestPriorityRule('bash', ['deploy prod'], '/tmp', {
|
|
1745
|
-
principal: { kind: 'skill', id: 'other-skill' },
|
|
1746
|
-
executionTarget: '/usr/bin/node',
|
|
1747
|
-
});
|
|
1748
|
-
expect(match === null || match.id !== 'combo-bad-principal').toBe(true);
|
|
1749
|
-
});
|
|
1750
|
-
|
|
1751
|
-
test('rule with both principal and executionTarget fails if target mismatches', () => {
|
|
1752
|
-
seedRules([{
|
|
1753
|
-
id: 'combo-bad-target',
|
|
1754
|
-
tool: 'bash',
|
|
1755
|
-
pattern: 'deploy *',
|
|
1756
|
-
scope: 'everywhere',
|
|
1757
|
-
decision: 'allow',
|
|
1758
|
-
priority: 200,
|
|
1759
|
-
createdAt: Date.now(),
|
|
1760
|
-
principalKind: 'skill',
|
|
1761
|
-
principalId: 'deployer',
|
|
1762
|
-
executionTarget: '/usr/bin/node',
|
|
1763
|
-
}]);
|
|
1764
|
-
const match = findHighestPriorityRule('bash', ['deploy prod'], '/tmp', {
|
|
1765
|
-
principal: { kind: 'skill', id: 'deployer' },
|
|
1766
|
-
executionTarget: '/usr/bin/bun',
|
|
1767
|
-
});
|
|
1768
|
-
expect(match === null || match.id !== 'combo-bad-target').toBe(true);
|
|
1769
|
-
});
|
|
1770
|
-
});
|
|
1771
|
-
|
|
1772
|
-
// ── priority interaction with principal filtering ──────────────
|
|
1773
|
-
|
|
1774
|
-
describe('priority interaction with principal filtering', () => {
|
|
1775
|
-
test('higher-priority principal-specific rule wins over lower-priority wildcard', () => {
|
|
1776
|
-
seedRules([
|
|
1777
|
-
{
|
|
1778
|
-
id: 'wildcard-low',
|
|
1779
|
-
tool: 'bash',
|
|
1780
|
-
pattern: 'test *',
|
|
1781
|
-
scope: 'everywhere',
|
|
1782
|
-
decision: 'deny',
|
|
1783
|
-
priority: 50,
|
|
1784
|
-
createdAt: Date.now(),
|
|
1785
|
-
},
|
|
1786
|
-
{
|
|
1787
|
-
id: 'specific-high',
|
|
1788
|
-
tool: 'bash',
|
|
1789
|
-
pattern: 'test *',
|
|
1790
|
-
scope: 'everywhere',
|
|
1791
|
-
decision: 'allow',
|
|
1792
|
-
priority: 200,
|
|
1793
|
-
createdAt: Date.now(),
|
|
1794
|
-
principalKind: 'skill',
|
|
1795
|
-
principalId: 'tester',
|
|
1796
|
-
},
|
|
1797
|
-
]);
|
|
1798
|
-
const match = findHighestPriorityRule('bash', ['test unit'], '/tmp', {
|
|
1799
|
-
principal: { kind: 'skill', id: 'tester' },
|
|
1800
|
-
});
|
|
1801
|
-
expect(match).not.toBeNull();
|
|
1802
|
-
expect(match!.id).toBe('specific-high');
|
|
1803
|
-
expect(match!.decision).toBe('allow');
|
|
1804
|
-
});
|
|
1805
|
-
|
|
1806
|
-
test('non-matching principal rule is skipped, falling through to wildcard rule', () => {
|
|
1807
|
-
seedRules([
|
|
1808
|
-
{
|
|
1809
|
-
id: 'specific-high',
|
|
1810
|
-
tool: 'bash',
|
|
1811
|
-
pattern: 'test *',
|
|
1812
|
-
scope: 'everywhere',
|
|
1813
|
-
decision: 'allow',
|
|
1814
|
-
priority: 200,
|
|
1815
|
-
createdAt: Date.now(),
|
|
1816
|
-
principalKind: 'skill',
|
|
1817
|
-
principalId: 'deployer',
|
|
1818
|
-
},
|
|
1819
|
-
{
|
|
1820
|
-
id: 'wildcard-low',
|
|
1821
|
-
tool: 'bash',
|
|
1822
|
-
pattern: 'test *',
|
|
1823
|
-
scope: 'everywhere',
|
|
1824
|
-
decision: 'deny',
|
|
1825
|
-
priority: 50,
|
|
1826
|
-
createdAt: Date.now(),
|
|
1827
|
-
},
|
|
1828
|
-
]);
|
|
1829
|
-
// Context has kind=skill, id=tester — doesn't match 'deployer'
|
|
1830
|
-
const match = findHighestPriorityRule('bash', ['test unit'], '/tmp', {
|
|
1831
|
-
principal: { kind: 'skill', id: 'tester' },
|
|
1832
|
-
});
|
|
1833
|
-
expect(match).not.toBeNull();
|
|
1834
|
-
expect(match!.id).toBe('wildcard-low');
|
|
1835
|
-
expect(match!.decision).toBe('deny');
|
|
1836
|
-
});
|
|
1837
|
-
});
|
|
1838
|
-
|
|
1839
1427
|
// ── backward compatibility ────────────────────────────────────
|
|
1840
1428
|
|
|
1841
1429
|
describe('backward compatibility', () => {
|
|
@@ -1847,19 +1435,6 @@ describe('Trust Store', () => {
|
|
|
1847
1435
|
expect(match!.pattern).toBe('git *');
|
|
1848
1436
|
});
|
|
1849
1437
|
|
|
1850
|
-
test('existing default rules (no principal fields) match with any context', () => {
|
|
1851
|
-
// Default rules have no principal fields and should match regardless of context.
|
|
1852
|
-
// Use host_file_read which has a default ask rule (default:ask-host_file_read-global).
|
|
1853
|
-
const match = findHighestPriorityRule(
|
|
1854
|
-
'host_file_read',
|
|
1855
|
-
['host_file_read:/etc/hosts'],
|
|
1856
|
-
'/tmp',
|
|
1857
|
-
{ principal: { kind: 'skill', id: 'random-skill', version: 'v99' } },
|
|
1858
|
-
);
|
|
1859
|
-
expect(match).not.toBeNull();
|
|
1860
|
-
expect(match!.decision).toBe('ask');
|
|
1861
|
-
});
|
|
1862
|
-
|
|
1863
1438
|
test('empty PolicyContext object behaves the same as no context', () => {
|
|
1864
1439
|
addRule('bash', 'ls *', '/tmp', 'allow', 200);
|
|
1865
1440
|
const matchNoCtx = findHighestPriorityRule('bash', ['ls -la'], '/tmp');
|