@kafca/agentdock 0.1.61 → 0.1.63

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 (127) hide show
  1. package/README.md +18 -0
  2. package/dist/renderer/assets/AutomationList-DR-micIT.js +16 -0
  3. package/dist/renderer/assets/{Badge-BLI4LHse.js → Badge-COE1Zm2i.js} +1 -1
  4. package/dist/renderer/assets/{Card-DJeLDdL3.js → Card-F6UNFxnQ.js} +1 -1
  5. package/dist/renderer/assets/{Config-D1awc81A.js → Config-BKqxfhZi.js} +2 -2
  6. package/dist/renderer/assets/Dashboard-Bvp7-_2z.js +36 -0
  7. package/dist/renderer/assets/{EmptyState-CkI1RwPX.js → EmptyState-BwV_J0Og.js} +1 -1
  8. package/dist/renderer/assets/{HighlightedMarkdown-BUhDlhgW.js → HighlightedMarkdown-CBIBaWyM.js} +1 -1
  9. package/dist/renderer/assets/{Input-DHYrn-Ne.js → Input-C8LZd4Bi.js} +1 -1
  10. package/dist/renderer/assets/{KnowledgeDetail-Bij-Xo90.js → KnowledgeDetail-DjXYNMIs.js} +2 -2
  11. package/dist/renderer/assets/{KnowledgeHome-DmPZ2IlI.js → KnowledgeHome-Cqk5_nJh.js} +2 -2
  12. package/dist/renderer/assets/{Logs-ISY99heA.js → Logs-B6cXFvW4.js} +2 -2
  13. package/dist/renderer/assets/{Modal-DAQfz0ot.js → Modal-CAj7soll.js} +3 -3
  14. package/dist/renderer/assets/{Page-Dw43UYDT.js → Page-DlnMnu4F.js} +1 -1
  15. package/dist/renderer/assets/{Select-DzmN5OGQ.js → Select-CVay_LUX.js} +1 -1
  16. package/dist/renderer/assets/{ThreadChat-B5CWqqMK.js → ThreadChat-BxXRpYa1.js} +3 -3
  17. package/dist/renderer/assets/{Workspace-C5g9LBj9.js → Workspace-YC0pC7cM.js} +1 -1
  18. package/dist/renderer/assets/{arrow-left-BLXh56Ip.js → arrow-left-Cwm09dxx.js} +1 -1
  19. package/dist/renderer/assets/{book-open-DhPGbc-7.js → book-open-BwcGC_im.js} +1 -1
  20. package/dist/renderer/assets/{channels-DfQrmuYO.js → channels-BPytmVCd.js} +2 -2
  21. package/dist/renderer/assets/{chevron-down-CEFtpPDc.js → chevron-down-2LP9oGHG.js} +1 -1
  22. package/dist/renderer/assets/en-Bnr4nMzg.js +1 -0
  23. package/dist/renderer/assets/es-B1GIFh3i.js +1 -0
  24. package/dist/renderer/assets/{index-BIKL3fTP.css → index-B6WcpkJF.css} +1 -1
  25. package/dist/renderer/assets/index-eoVERL5K.js +162 -0
  26. package/dist/renderer/assets/{index-CYhDUAR_.js → index-llCPCiYk.js} +1 -1
  27. package/dist/renderer/assets/ja-CDWPWqEW.js +1 -0
  28. package/dist/renderer/assets/{knowledge-uHRQ80sR.js → knowledge-QujAv-Zh.js} +1 -1
  29. package/dist/renderer/assets/{pencil-BD2Y31YC.js → pencil-0P2INETc.js} +1 -1
  30. package/dist/renderer/assets/{plus-DDcldp_c.js → plus-tiGRgE2v.js} +1 -1
  31. package/dist/renderer/assets/{save-CMW_cZI9.js → save-D8U93DNa.js} +1 -1
  32. package/dist/renderer/assets/{search-BM-0CvJr.js → search-C-9J9J3f.js} +1 -1
  33. package/dist/renderer/assets/{shield-check-DaMB0FKG.js → shield-check-BCKkPPTJ.js} +1 -1
  34. package/dist/renderer/assets/{threads-DZgt9tWj.js → threads-Bsrwwnpm.js} +1 -1
  35. package/dist/renderer/assets/{trash-2-DTXVbC5y.js → trash-2-CP4sn15n.js} +1 -1
  36. package/dist/renderer/assets/zh-MSddxMDX.js +1 -0
  37. package/dist/renderer/assets/zh-TW-C_LNWX0o.js +1 -0
  38. package/dist/renderer/index.html +2 -2
  39. package/dist-electron/electron/managed-skills/agent-browser/SKILL.md +41 -0
  40. package/dist-electron/electron/managed-skills/agent-browser/references/authentication.md +83 -0
  41. package/dist-electron/electron/managed-skills/agent-browser/references/commands.md +50 -0
  42. package/dist-electron/electron/managed-skills/agent-browser/references/profiling.md +20 -0
  43. package/dist-electron/electron/managed-skills/agent-browser/references/proxy-support.md +18 -0
  44. package/dist-electron/electron/managed-skills/agent-browser/references/session-management.md +17 -0
  45. package/dist-electron/electron/managed-skills/agent-browser/references/snapshot-refs.md +17 -0
  46. package/dist-electron/electron/managed-skills/agent-browser/references/video-recording.md +21 -0
  47. package/dist-electron/electron/managed-skills/agent-browser/templates/authenticated-session.sh +11 -0
  48. package/dist-electron/electron/managed-skills/agent-browser/templates/capture-workflow.sh +12 -0
  49. package/dist-electron/electron/managed-skills/agent-browser/templates/form-automation.sh +10 -0
  50. package/dist-electron/electron/managed-skills/condition-trigger/SKILL.md +21 -0
  51. package/dist-electron/electron/managed-skills/condition-trigger/scripts/register-condition-trigger.sh +106 -0
  52. package/dist-electron/electron/managed-skills/knowledge-base/SKILL.md +13 -0
  53. package/dist-electron/electron/managed-skills/knowledge-base/scripts/search-knowledge.sh +80 -0
  54. package/dist-electron/packages/contracts/src/automations.js +211 -0
  55. package/dist-electron/packages/contracts/src/index.js +1 -0
  56. package/dist-electron/packages/core-sdk/src/automations.js +87 -0
  57. package/dist-electron/packages/core-sdk/src/client.js +101 -0
  58. package/dist-electron/packages/core-sdk/src/index.js +2 -1
  59. package/dist-electron/services/local-ai-core/src/acp/store/automation-script-store.js +415 -0
  60. package/dist-electron/services/local-ai-core/src/acp/store/automation-store.js +829 -0
  61. package/dist-electron/services/local-ai-core/src/acp/store/local-core-acp-store.js +158 -0
  62. package/dist-electron/services/local-ai-core/src/acp/store/schema.js +81 -0
  63. package/dist-electron/services/local-ai-core/src/automation/automation-action-executor.js +144 -0
  64. package/dist-electron/services/local-ai-core/src/automation/automation-condition-engine.js +79 -0
  65. package/dist-electron/services/local-ai-core/src/automation/automation-conversation-executor.js +39 -73
  66. package/dist-electron/services/local-ai-core/src/automation/automation-monitor-service.js +695 -260
  67. package/dist-electron/services/local-ai-core/src/automation/automation-script-service.js +507 -0
  68. package/dist-electron/services/local-ai-core/src/automation/automation-service.js +1019 -0
  69. package/dist-electron/services/local-ai-core/src/automation/automation-trigger-engine.js +141 -0
  70. package/dist-electron/services/local-ai-core/src/automation/condition-evaluator.js +60 -17
  71. package/dist-electron/services/local-ai-core/src/automation/legacy-automation-mappers.js +286 -0
  72. package/dist-electron/services/local-ai-core/src/automation/scripts/anthropic-sandbox-runner.js +792 -0
  73. package/dist-electron/services/local-ai-core/src/automation/scripts/sandbox-runner.js +2 -0
  74. package/dist-electron/services/local-ai-core/src/automation/scripts/script-package.js +475 -0
  75. package/dist-electron/services/local-ai-core/src/automation/scripts/script-protocol-runner.js +266 -0
  76. package/dist-electron/services/local-ai-core/src/automation/scripts/secret-resolver.js +37 -0
  77. package/dist-electron/services/local-ai-core/src/channel/weixin/inbound-poller.js +1 -2
  78. package/dist-electron/services/local-ai-core/src/channel/weixin/transport.js +9 -8
  79. package/dist-electron/services/local-ai-core/src/cli/lac.js +229 -0
  80. package/dist-electron/services/local-ai-core/src/kernel/bootstrap.js +21 -0
  81. package/dist-electron/services/local-ai-core/src/runtime/deployment-diagnostics.js +76 -0
  82. package/dist-electron/services/local-ai-core/src/runtime/handlers/automations-handler.js +322 -0
  83. package/dist-electron/services/local-ai-core/src/runtime/local-core-controller.js +20 -0
  84. package/dist-electron/services/local-ai-core/src/runtime/managed-skill-catalog.js +37 -0
  85. package/dist-electron/services/local-ai-core/src/runtime/server-helpers.js +14 -4
  86. package/dist-electron/services/local-ai-core/src/runtime/server-routes.js +75 -0
  87. package/dist-electron/services/local-ai-core/src/runtime/server.js +21 -0
  88. package/dist-electron/services/local-ai-core/src/runtime/standalone.js +1 -0
  89. package/dist-electron/services/local-ai-core/src/scheduler/cron.js +151 -39
  90. package/dist-electron/services/local-ai-core/src/scheduler/scheduled-conversation-executor.js +2 -4
  91. package/dist-electron/services/local-ai-core/src/scheduler/scheduled-job-application-service.js +64 -12
  92. package/dist-electron/services/local-ai-core/src/scheduler/scheduler-service.js +71 -4
  93. package/dist-electron/services/local-ai-core/src/scheduler/thread-resolution.js +10 -0
  94. package/dist-electron/services/local-ai-core/src/thread/agent-message-policy.js +16 -1
  95. package/dist-electron/src/pages/Automation/automation-page-model.js +76 -0
  96. package/dist-electron/tests/contracts/agent-message-policy.test.js +32 -0
  97. package/dist-electron/tests/contracts/architecture-docs.test.js +30 -0
  98. package/dist-electron/tests/contracts/automation-contracts.test.js +154 -0
  99. package/dist-electron/tests/contracts/automation-renderer-model.test.js +47 -0
  100. package/dist-electron/tests/electron/automation-action-executor.test.js +75 -0
  101. package/dist-electron/tests/electron/automation-compatibility.test.js +463 -0
  102. package/dist-electron/tests/electron/automation-deployment.test.js +66 -0
  103. package/dist-electron/tests/electron/automation-engine.test.js +264 -0
  104. package/dist-electron/tests/electron/automation-monitor.test.js +1333 -0
  105. package/dist-electron/tests/electron/automation-sandbox-runner.test.js +344 -0
  106. package/dist-electron/tests/electron/automation-script-approval.test.js +553 -0
  107. package/dist-electron/tests/electron/automation-script-package.test.js +499 -0
  108. package/dist-electron/tests/electron/automation-script-runner.test.js +120 -0
  109. package/dist-electron/tests/electron/automation-service.test.js +1078 -0
  110. package/dist-electron/tests/electron/automation-store.test.js +593 -0
  111. package/dist-electron/tests/electron/condition-trigger-skill.test.js +93 -0
  112. package/dist-electron/tests/electron/core-client.test.js +99 -0
  113. package/dist-electron/tests/electron/lac-cli.test.js +110 -0
  114. package/dist-electron/tests/electron/workspace-task-store.test.js +25 -0
  115. package/dist-electron/tests/integration/automation-routes.test.js +186 -0
  116. package/dist-electron/tests/integration/automation-sandbox-runtime.test.js +92 -0
  117. package/package.json +4 -3
  118. package/dist/renderer/assets/CronList-CupZoNwW.js +0 -1
  119. package/dist/renderer/assets/Dashboard-nLZnOtcs.js +0 -41
  120. package/dist/renderer/assets/MonitorList-BlX82rXF.js +0 -1
  121. package/dist/renderer/assets/en-CmAr_0e6.js +0 -1
  122. package/dist/renderer/assets/es-Cs9x52ca.js +0 -1
  123. package/dist/renderer/assets/index-Dd00oqgY.js +0 -167
  124. package/dist/renderer/assets/ja-DtvtBRTW.js +0 -1
  125. package/dist/renderer/assets/play-DBJ8wsSy.js +0 -6
  126. package/dist/renderer/assets/zh-CcJ9vBZj.js +0 -1
  127. package/dist/renderer/assets/zh-TW-elI72AC1.js +0 -1
@@ -0,0 +1,553 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ const strict_1 = __importDefault(require("node:assert/strict"));
7
+ const node_fs_1 = require("node:fs");
8
+ const node_os_1 = require("node:os");
9
+ const node_path_1 = require("node:path");
10
+ const node_test_1 = __importDefault(require("node:test"));
11
+ const local_core_acp_store_js_1 = require("../../services/local-ai-core/src/acp/local-core-acp-store.js");
12
+ const automation_script_service_js_1 = require("../../services/local-ai-core/src/automation/automation-script-service.js");
13
+ const NOW = '2026-07-08T12:00:00.000Z';
14
+ function tempDir(prefix) {
15
+ return (0, node_fs_1.mkdtempSync)((0, node_path_1.join)((0, node_os_1.tmpdir)(), prefix));
16
+ }
17
+ function removeTempTree(path) {
18
+ makeWritable(path);
19
+ (0, node_fs_1.rmSync)(path, { recursive: true, force: true });
20
+ }
21
+ function makeWritable(path) {
22
+ let stat;
23
+ try {
24
+ stat = (0, node_fs_1.lstatSync)(path);
25
+ }
26
+ catch {
27
+ return;
28
+ }
29
+ if (stat.isSymbolicLink() || !stat.isDirectory()) {
30
+ try {
31
+ (0, node_fs_1.chmodSync)(path, 0o644);
32
+ }
33
+ catch {
34
+ // Best effort for portable test cleanup.
35
+ }
36
+ return;
37
+ }
38
+ try {
39
+ (0, node_fs_1.chmodSync)(path, 0o755);
40
+ }
41
+ catch {
42
+ // Best effort for portable test cleanup.
43
+ }
44
+ for (const entry of (0, node_fs_1.readdirSync)(path)) {
45
+ makeWritable((0, node_path_1.join)(path, entry));
46
+ }
47
+ }
48
+ function writeBundle(root, overrides = {}) {
49
+ const manifest = {
50
+ protocolVersion: 1,
51
+ entrypoint: 'run.sh',
52
+ config: {},
53
+ configSchema: { type: 'object' },
54
+ capabilities: {
55
+ network: 'none',
56
+ internalAccess: false,
57
+ allowedReadDirs: [],
58
+ ...(overrides.capabilities || {}),
59
+ },
60
+ secretRefs: overrides.secretRefs || [],
61
+ env: overrides.env || [],
62
+ testPlan: overrides.testPlan || { command: 'manual', cases: ['happy-path'] },
63
+ limits: { timeoutMs: 30_000, stdoutBytes: 8192, stderrBytes: 8192, payloadBytes: 8192, stateBytes: 8192 },
64
+ };
65
+ (0, node_fs_1.writeFileSync)((0, node_path_1.join)(root, 'manifest.json'), `${JSON.stringify(manifest, null, 2)}\n`);
66
+ (0, node_fs_1.writeFileSync)((0, node_path_1.join)(root, 'run.sh'), '#!/bin/sh\necho ok\n');
67
+ }
68
+ function createHarness(options = {}) {
69
+ const userDataPath = tempDir('automation-script-approval-store-');
70
+ const sourceDir = tempDir('automation-script-approval-bundle-');
71
+ writeBundle(sourceDir, options);
72
+ const store = new local_core_acp_store_js_1.LocalCoreAcpStore(userDataPath);
73
+ const internals = store;
74
+ const service = new automation_script_service_js_1.AutomationScriptService({
75
+ db: internals.db,
76
+ security: internals.security,
77
+ clock: () => new Date(options.now || NOW),
78
+ });
79
+ const script = store.createAutomationScript({
80
+ workspaceId: 'workspace-approval',
81
+ title: 'Check inbox',
82
+ description: 'approved script',
83
+ });
84
+ const version = store.createAutomationScriptVersionFromPackage({
85
+ scriptId: script.id,
86
+ sourceDir,
87
+ interpreterPath: '/bin/sh',
88
+ interpreterVersion: 'sh 1.0',
89
+ });
90
+ return {
91
+ userDataPath,
92
+ sourceDir,
93
+ store,
94
+ db: internals.db,
95
+ service,
96
+ script,
97
+ version,
98
+ cleanup() {
99
+ store.close();
100
+ removeTempTree(userDataPath);
101
+ removeTempTree(sourceDir);
102
+ },
103
+ };
104
+ }
105
+ const passedReport = {
106
+ status: 'passed',
107
+ finishedAt: NOW,
108
+ summary: 'Manual package approval smoke test passed.',
109
+ };
110
+ function approveThroughTwoStages(harness) {
111
+ const testApproval = harness.service.requestTestApproval(harness.version.id, 'author');
112
+ harness.store.resolveApprovalRequest(testApproval.approvalId, {
113
+ status: 'approved',
114
+ resolvedBy: 'security',
115
+ resolution: 'allow isolated package test',
116
+ });
117
+ harness.service.authorizeTest(harness.version.id, testApproval.approvalId, 'security');
118
+ harness.service.recordTestResult(harness.version.id, passedReport);
119
+ const enableApproval = harness.service.requestEnableApproval(harness.version.id, 'author');
120
+ harness.store.resolveApprovalRequest(enableApproval.approvalId, {
121
+ status: 'approved',
122
+ resolvedBy: 'owner',
123
+ resolution: 'enable after test evidence',
124
+ });
125
+ return harness.service.approveVersion(harness.version.id, enableApproval.approvalId, 'owner');
126
+ }
127
+ function advanceToTested(harness) {
128
+ const testApproval = harness.service.requestTestApproval(harness.version.id, 'author');
129
+ harness.store.resolveApprovalRequest(testApproval.approvalId, {
130
+ status: 'approved',
131
+ resolvedBy: 'security',
132
+ resolution: 'allow isolated package test',
133
+ });
134
+ harness.service.authorizeTest(harness.version.id, testApproval.approvalId, 'security');
135
+ return harness.service.recordTestResult(harness.version.id, passedReport);
136
+ }
137
+ function mutateVersion(harness, mutate) {
138
+ const current = harness.store.listAutomationScriptVersions(harness.script.id)
139
+ .find((candidate) => candidate.id === harness.version.id);
140
+ strict_1.default.ok(current, 'version must exist before mutation');
141
+ const next = mutate(current);
142
+ harness.db.prepare(`
143
+ UPDATE automation_script_versions
144
+ SET status = ?, package_sha256 = ?, updated_at = ?, version_json = ?
145
+ WHERE id = ?
146
+ `).run(next.status, next.packageSha256, next.updatedAt, JSON.stringify(next), next.id);
147
+ }
148
+ function mutateApprovalMetadata(harness, approvalId, mutate) {
149
+ const approval = harness.store.getApprovalRequest(approvalId);
150
+ strict_1.default.ok(approval, 'approval must exist before mutation');
151
+ harness.db.prepare('UPDATE approval_requests SET metadata_json = ? WHERE id = ?')
152
+ .run(JSON.stringify(mutate({ ...(approval.metadata || {}) })), approvalId);
153
+ }
154
+ (0, node_test_1.default)('requires test approval before recording results and enable approval before approval', () => {
155
+ const h = createHarness({
156
+ capabilities: { network: 'public', allowedReadDirs: ['/tmp/inbox'] },
157
+ secretRefs: ['env://SLACK_TOKEN'],
158
+ });
159
+ try {
160
+ const testApproval = h.service.requestTestApproval(h.version.id, 'author');
161
+ strict_1.default.equal(testApproval.kind, 'automation_script_test');
162
+ strict_1.default.equal(testApproval.status, 'pending');
163
+ strict_1.default.equal(testApproval.metadata?.versionId, h.version.id);
164
+ strict_1.default.equal(testApproval.metadata?.packageSha256, h.version.packageSha256);
165
+ strict_1.default.match(String(testApproval.metadata?.manifestDigest), /^[a-f0-9]{64}$/);
166
+ strict_1.default.match(String(testApproval.metadata?.testPlanDigest), /^[a-f0-9]{64}$/);
167
+ strict_1.default.deepEqual((testApproval.metadata?.permissionSnapshot).secretEnvRefs, ['env://SLACK_TOKEN']);
168
+ strict_1.default.ok(testApproval.scopes.includes('network.access'));
169
+ strict_1.default.ok(testApproval.scopes.includes('secrets.access'));
170
+ strict_1.default.equal(h.store.listAutomationScriptVersions(h.script.id)[0]?.status, 'pending_test_approval');
171
+ h.store.resolveApprovalRequest(testApproval.approvalId, {
172
+ status: 'approved',
173
+ resolvedBy: 'security',
174
+ resolution: 'allow isolated package test',
175
+ });
176
+ const authorized = h.service.authorizeTest(h.version.id, testApproval.approvalId, 'security');
177
+ strict_1.default.equal(authorized.status, 'test_authorized');
178
+ strict_1.default.equal(authorized.testAuthorization?.approvalId, testApproval.approvalId);
179
+ const tested = h.service.recordTestResult(h.version.id, passedReport);
180
+ strict_1.default.equal(tested.status, 'tested');
181
+ strict_1.default.equal(tested.testReport?.status, 'passed');
182
+ const enableApproval = h.service.requestEnableApproval(h.version.id, 'author');
183
+ strict_1.default.equal(enableApproval.kind, 'automation_script_enable');
184
+ strict_1.default.equal(enableApproval.metadata?.versionId, h.version.id);
185
+ strict_1.default.equal(h.store.listAutomationScriptVersions(h.script.id)[0]?.status, 'pending_approval');
186
+ h.store.resolveApprovalRequest(enableApproval.approvalId, {
187
+ status: 'approved',
188
+ resolvedBy: 'owner',
189
+ resolution: 'enable after test evidence',
190
+ });
191
+ const approved = h.service.approveVersion(h.version.id, enableApproval.approvalId, 'owner');
192
+ strict_1.default.equal(approved.status, 'approved');
193
+ strict_1.default.equal(approved.approval?.approvalId, enableApproval.approvalId);
194
+ const auditTypes = h.store.listAuditEvents({ workspaceId: h.script.workspaceId, limit: 20 })
195
+ .events.map((event) => event.type);
196
+ strict_1.default.ok(auditTypes.includes('automation.script.test_authorized'));
197
+ strict_1.default.ok(auditTypes.includes('automation.script.approved'));
198
+ }
199
+ finally {
200
+ h.cleanup();
201
+ }
202
+ });
203
+ (0, node_test_1.default)('rejected approvals reject the script version and approved versions can be revoked', () => {
204
+ const rejectedHarness = createHarness();
205
+ try {
206
+ const testApproval = rejectedHarness.service.requestTestApproval(rejectedHarness.version.id, 'author');
207
+ rejectedHarness.store.resolveApprovalRequest(testApproval.approvalId, {
208
+ status: 'rejected',
209
+ resolvedBy: 'security',
210
+ resolution: 'test plan is incomplete',
211
+ });
212
+ const rejected = rejectedHarness.service.authorizeTest(rejectedHarness.version.id, testApproval.approvalId, 'security');
213
+ strict_1.default.equal(rejected.status, 'rejected');
214
+ strict_1.default.equal(rejected.rejection?.approvalId, testApproval.approvalId);
215
+ }
216
+ finally {
217
+ rejectedHarness.cleanup();
218
+ }
219
+ const revokedHarness = createHarness();
220
+ try {
221
+ approveThroughTwoStages(revokedHarness);
222
+ const revoked = revokedHarness.service.revokeVersion(revokedHarness.version.id, 'owner');
223
+ strict_1.default.equal(revoked.status, 'revoked');
224
+ strict_1.default.equal(revoked.revocation?.actor, 'owner');
225
+ const auditTypes = revokedHarness.store.listAuditEvents({ workspaceId: revokedHarness.script.workspaceId, limit: 20 })
226
+ .events.map((event) => event.type);
227
+ strict_1.default.ok(auditTypes.includes('automation.script.revoked'));
228
+ }
229
+ finally {
230
+ revokedHarness.cleanup();
231
+ }
232
+ });
233
+ (0, node_test_1.default)('test authorization is one-shot', () => {
234
+ const h = createHarness();
235
+ try {
236
+ const approval = h.service.requestTestApproval(h.version.id, 'author');
237
+ h.store.resolveApprovalRequest(approval.approvalId, {
238
+ status: 'approved',
239
+ resolvedBy: 'security',
240
+ resolution: 'allow test once',
241
+ });
242
+ h.service.authorizeTest(h.version.id, approval.approvalId, 'security');
243
+ strict_1.default.throws(() => h.service.authorizeTest(h.version.id, approval.approvalId, 'security'), /one-shot|already authorized|test authorization/i);
244
+ h.service.recordTestResult(h.version.id, passedReport);
245
+ strict_1.default.throws(() => h.service.recordTestResult(h.version.id, passedReport), /one-shot|already recorded|test_authorized/i);
246
+ }
247
+ finally {
248
+ h.cleanup();
249
+ }
250
+ });
251
+ (0, node_test_1.default)('claiming a test execution atomically consumes the test authorization before sandbox work', () => {
252
+ const h = createHarness();
253
+ try {
254
+ const approval = h.service.requestTestApproval(h.version.id, 'author');
255
+ h.store.resolveApprovalRequest(approval.approvalId, {
256
+ status: 'approved', resolvedBy: 'security', resolution: 'allow one test',
257
+ });
258
+ h.service.authorizeTest(h.version.id, approval.approvalId, 'security');
259
+ strict_1.default.equal(h.service.claimTestExecution(h.version.id).status, 'testing');
260
+ strict_1.default.throws(() => h.service.claimTestExecution(h.version.id), /test_authorized/);
261
+ strict_1.default.equal(h.service.recordTestResult(h.version.id, passedReport).status, 'tested');
262
+ }
263
+ finally {
264
+ h.cleanup();
265
+ }
266
+ });
267
+ (0, node_test_1.default)('a stranded testing claim is recovered to a terminal failed report after its lease', () => {
268
+ const h = createHarness();
269
+ try {
270
+ const approval = h.service.requestTestApproval(h.version.id, 'author');
271
+ h.store.resolveApprovalRequest(approval.approvalId, { status: 'approved', resolvedBy: 'security', resolution: 'allow one test' });
272
+ h.service.authorizeTest(h.version.id, approval.approvalId, 'security');
273
+ h.service.claimTestExecution(h.version.id);
274
+ const resumed = new automation_script_service_js_1.AutomationScriptService({
275
+ db: h.db,
276
+ security: h.store.security,
277
+ clock: () => new Date(Date.parse(NOW) + 11 * 60 * 1000),
278
+ });
279
+ strict_1.default.throws(() => resumed.claimTestExecution(h.version.id), /lease expired/);
280
+ const recovered = h.store.getAutomationScriptVersion(h.version.id);
281
+ strict_1.default.equal(recovered.status, 'tested');
282
+ strict_1.default.equal(recovered.testReport?.status, 'failed');
283
+ strict_1.default.throws(() => resumed.requestEnableApproval(h.version.id, 'author'), /passing server-recorded test/i);
284
+ }
285
+ finally {
286
+ h.cleanup();
287
+ }
288
+ });
289
+ (0, node_test_1.default)('a failed server-recorded test cannot request enable approval', () => {
290
+ const h = createHarness();
291
+ try {
292
+ const approval = h.service.requestTestApproval(h.version.id, 'author');
293
+ h.store.resolveApprovalRequest(approval.approvalId, { status: 'approved', resolvedBy: 'security', resolution: 'allow one test' });
294
+ h.service.authorizeTest(h.version.id, approval.approvalId, 'security');
295
+ h.service.recordTestResult(h.version.id, { status: 'failed', finishedAt: NOW, summary: 'fixture failed' });
296
+ strict_1.default.throws(() => h.service.requestEnableApproval(h.version.id, 'author'), /passing server-recorded test/i);
297
+ }
298
+ finally {
299
+ h.cleanup();
300
+ }
301
+ });
302
+ (0, node_test_1.default)('expired test approvals cannot authorize a test run', () => {
303
+ const h = createHarness();
304
+ try {
305
+ const approval = h.service.requestTestApproval(h.version.id, 'author');
306
+ strict_1.default.ok(approval.expiresAt, 'test approval must expire');
307
+ h.db.prepare('UPDATE approval_requests SET expires_at = ? WHERE id = ?').run('2026-07-08T11:59:00.000Z', approval.approvalId);
308
+ h.store.resolveApprovalRequest(approval.approvalId, {
309
+ status: 'approved',
310
+ resolvedBy: 'security',
311
+ resolution: 'approved too late',
312
+ });
313
+ strict_1.default.throws(() => h.service.authorizeTest(h.version.id, approval.approvalId, 'security'), /expired/i);
314
+ strict_1.default.equal(h.store.getApprovalRequest(approval.approvalId)?.status, 'expired');
315
+ strict_1.default.equal(h.store.listAutomationScriptVersions(h.script.id)[0]?.status, 'pending_test_approval');
316
+ }
317
+ finally {
318
+ h.cleanup();
319
+ }
320
+ });
321
+ (0, node_test_1.default)('approval snapshots guard package hash, permissions, and env secret references', () => {
322
+ const hashHarness = createHarness();
323
+ try {
324
+ advanceToTested(hashHarness);
325
+ const approval = hashHarness.service.requestEnableApproval(hashHarness.version.id, 'author');
326
+ mutateVersion(hashHarness, (version) => ({
327
+ ...version,
328
+ packageSha256: 'f'.repeat(64),
329
+ staticCheck: { ...version.staticCheck, packageSha256: 'f'.repeat(64) },
330
+ }));
331
+ hashHarness.store.resolveApprovalRequest(approval.approvalId, {
332
+ status: 'approved',
333
+ resolvedBy: 'owner',
334
+ resolution: 'enable stale hash',
335
+ });
336
+ strict_1.default.throws(() => hashHarness.service.approveVersion(hashHarness.version.id, approval.approvalId, 'owner'), /package hash/i);
337
+ }
338
+ finally {
339
+ hashHarness.cleanup();
340
+ }
341
+ const permissionHarness = createHarness();
342
+ try {
343
+ advanceToTested(permissionHarness);
344
+ const approval = permissionHarness.service.requestEnableApproval(permissionHarness.version.id, 'author');
345
+ mutateVersion(permissionHarness, (version) => ({
346
+ ...version,
347
+ internalAccess: true,
348
+ capabilities: { ...version.capabilities, internalAccess: true },
349
+ }));
350
+ permissionHarness.store.resolveApprovalRequest(approval.approvalId, {
351
+ status: 'approved',
352
+ resolvedBy: 'owner',
353
+ resolution: 'enable stale permissions',
354
+ });
355
+ strict_1.default.throws(() => permissionHarness.service.approveVersion(permissionHarness.version.id, approval.approvalId, 'owner'), /permission/i);
356
+ }
357
+ finally {
358
+ permissionHarness.cleanup();
359
+ }
360
+ const secretHarness = createHarness({ secretRefs: ['env://OLD_TOKEN'] });
361
+ try {
362
+ advanceToTested(secretHarness);
363
+ const approval = secretHarness.service.requestEnableApproval(secretHarness.version.id, 'author');
364
+ mutateVersion(secretHarness, (version) => ({
365
+ ...version,
366
+ secretRefs: ['env://NEW_TOKEN'],
367
+ }));
368
+ secretHarness.store.resolveApprovalRequest(approval.approvalId, {
369
+ status: 'approved',
370
+ resolvedBy: 'owner',
371
+ resolution: 'enable stale secret env refs',
372
+ });
373
+ strict_1.default.throws(() => secretHarness.service.approveVersion(secretHarness.version.id, approval.approvalId, 'owner'), /secret env/i);
374
+ }
375
+ finally {
376
+ secretHarness.cleanup();
377
+ }
378
+ });
379
+ (0, node_test_1.default)('test authorization rejects stale permissions, secret env refs, and digests', () => {
380
+ const permissionHarness = createHarness();
381
+ try {
382
+ const approval = permissionHarness.service.requestTestApproval(permissionHarness.version.id, 'author');
383
+ mutateVersion(permissionHarness, (version) => ({
384
+ ...version,
385
+ internalAccess: true,
386
+ capabilities: { ...version.capabilities, internalAccess: true },
387
+ }));
388
+ permissionHarness.store.resolveApprovalRequest(approval.approvalId, {
389
+ status: 'approved',
390
+ resolvedBy: 'security',
391
+ resolution: 'authorize stale permissions',
392
+ });
393
+ strict_1.default.throws(() => permissionHarness.service.authorizeTest(permissionHarness.version.id, approval.approvalId, 'security'), /permission/i);
394
+ }
395
+ finally {
396
+ permissionHarness.cleanup();
397
+ }
398
+ const secretHarness = createHarness({ secretRefs: ['env://OLD_TOKEN'] });
399
+ try {
400
+ const approval = secretHarness.service.requestTestApproval(secretHarness.version.id, 'author');
401
+ mutateVersion(secretHarness, (version) => ({
402
+ ...version,
403
+ secretRefs: ['env://NEW_TOKEN'],
404
+ }));
405
+ secretHarness.store.resolveApprovalRequest(approval.approvalId, {
406
+ status: 'approved',
407
+ resolvedBy: 'security',
408
+ resolution: 'authorize stale secret env refs',
409
+ });
410
+ strict_1.default.throws(() => secretHarness.service.authorizeTest(secretHarness.version.id, approval.approvalId, 'security'), /secret env/i);
411
+ }
412
+ finally {
413
+ secretHarness.cleanup();
414
+ }
415
+ const testPlanHarness = createHarness();
416
+ try {
417
+ const approval = testPlanHarness.service.requestTestApproval(testPlanHarness.version.id, 'author');
418
+ mutateVersion(testPlanHarness, (version) => ({
419
+ ...version,
420
+ testPlan: { command: 'manual', cases: ['changed-case'] },
421
+ }));
422
+ testPlanHarness.store.resolveApprovalRequest(approval.approvalId, {
423
+ status: 'approved',
424
+ resolvedBy: 'security',
425
+ resolution: 'authorize stale test plan',
426
+ });
427
+ strict_1.default.throws(() => testPlanHarness.service.authorizeTest(testPlanHarness.version.id, approval.approvalId, 'security'), /test plan digest/i);
428
+ }
429
+ finally {
430
+ testPlanHarness.cleanup();
431
+ }
432
+ const manifestHarness = createHarness();
433
+ try {
434
+ const approval = manifestHarness.service.requestTestApproval(manifestHarness.version.id, 'author');
435
+ mutateVersion(manifestHarness, (version) => ({
436
+ ...version,
437
+ config: { changed: true },
438
+ }));
439
+ manifestHarness.store.resolveApprovalRequest(approval.approvalId, {
440
+ status: 'approved',
441
+ resolvedBy: 'security',
442
+ resolution: 'authorize stale manifest',
443
+ });
444
+ strict_1.default.throws(() => manifestHarness.service.authorizeTest(manifestHarness.version.id, approval.approvalId, 'security'), /manifest digest/i);
445
+ }
446
+ finally {
447
+ manifestHarness.cleanup();
448
+ }
449
+ });
450
+ (0, node_test_1.default)('approval metadata must contain required digest snapshots', () => {
451
+ const testHarness = createHarness();
452
+ try {
453
+ const approval = testHarness.service.requestTestApproval(testHarness.version.id, 'author');
454
+ mutateApprovalMetadata(testHarness, approval.approvalId, (metadata) => {
455
+ delete metadata.manifestDigest;
456
+ return metadata;
457
+ });
458
+ testHarness.store.resolveApprovalRequest(approval.approvalId, {
459
+ status: 'approved',
460
+ resolvedBy: 'security',
461
+ resolution: 'authorize malformed metadata',
462
+ });
463
+ strict_1.default.throws(() => testHarness.service.authorizeTest(testHarness.version.id, approval.approvalId, 'security'), /manifest digest/i);
464
+ }
465
+ finally {
466
+ testHarness.cleanup();
467
+ }
468
+ const enableHarness = createHarness();
469
+ try {
470
+ advanceToTested(enableHarness);
471
+ const approval = enableHarness.service.requestEnableApproval(enableHarness.version.id, 'author');
472
+ mutateApprovalMetadata(enableHarness, approval.approvalId, (metadata) => ({
473
+ ...metadata,
474
+ permissionSnapshotDigest: '0'.repeat(64),
475
+ }));
476
+ enableHarness.store.resolveApprovalRequest(approval.approvalId, {
477
+ status: 'approved',
478
+ resolvedBy: 'owner',
479
+ resolution: 'approve tampered metadata',
480
+ });
481
+ strict_1.default.throws(() => enableHarness.service.approveVersion(enableHarness.version.id, approval.approvalId, 'owner'), /permission snapshot digest/i);
482
+ }
483
+ finally {
484
+ enableHarness.cleanup();
485
+ }
486
+ });
487
+ (0, node_test_1.default)('approval transitions reject wrong approval ids and cross-workspace approvals', () => {
488
+ const wrongIdHarness = createHarness();
489
+ try {
490
+ const expected = wrongIdHarness.service.requestTestApproval(wrongIdHarness.version.id, 'author');
491
+ const wrong = wrongIdHarness.store.createApprovalRequest({
492
+ workspaceId: wrongIdHarness.script.workspaceId,
493
+ kind: 'automation_script_test',
494
+ riskLevel: 'low',
495
+ title: 'Wrong test approval',
496
+ description: 'Wrong approval for same version',
497
+ requestedAction: 'Run wrong test approval',
498
+ requestedBy: 'author',
499
+ metadata: expected.metadata,
500
+ });
501
+ wrongIdHarness.store.resolveApprovalRequest(wrong.approvalId, {
502
+ status: 'approved',
503
+ resolvedBy: 'security',
504
+ resolution: 'approve wrong id',
505
+ });
506
+ strict_1.default.throws(() => wrongIdHarness.service.authorizeTest(wrongIdHarness.version.id, wrong.approvalId, 'security'), /approval id/i);
507
+ }
508
+ finally {
509
+ wrongIdHarness.cleanup();
510
+ }
511
+ const workspaceHarness = createHarness();
512
+ try {
513
+ const expected = workspaceHarness.service.requestTestApproval(workspaceHarness.version.id, 'author');
514
+ const crossWorkspace = workspaceHarness.store.createApprovalRequest({
515
+ workspaceId: 'workspace-other',
516
+ kind: 'automation_script_test',
517
+ riskLevel: 'low',
518
+ title: 'Cross workspace test approval',
519
+ description: 'Cross workspace approval for same version',
520
+ requestedAction: 'Run cross workspace test approval',
521
+ requestedBy: 'author',
522
+ metadata: expected.metadata,
523
+ });
524
+ mutateVersion(workspaceHarness, (version) => ({
525
+ ...version,
526
+ pendingTestApprovalId: crossWorkspace.approvalId,
527
+ }));
528
+ workspaceHarness.store.resolveApprovalRequest(crossWorkspace.approvalId, {
529
+ status: 'approved',
530
+ resolvedBy: 'security',
531
+ resolution: 'approve cross workspace id',
532
+ });
533
+ strict_1.default.throws(() => workspaceHarness.service.authorizeTest(workspaceHarness.version.id, crossWorkspace.approvalId, 'security'), /workspace/i);
534
+ }
535
+ finally {
536
+ workspaceHarness.cleanup();
537
+ }
538
+ });
539
+ (0, node_test_1.default)('approval service rejects mismatched duplicated version row data', () => {
540
+ const h = createHarness();
541
+ try {
542
+ const corrupted = {
543
+ ...h.version,
544
+ packageSha256: 'e'.repeat(64),
545
+ };
546
+ h.db.prepare('UPDATE automation_script_versions SET version_json = ? WHERE id = ?')
547
+ .run(JSON.stringify(corrupted), h.version.id);
548
+ strict_1.default.throws(() => h.service.requestTestApproval(h.version.id, 'author'), /mismatch between duplicated columns|invalid persisted data/i);
549
+ }
550
+ finally {
551
+ h.cleanup();
552
+ }
553
+ });