@dsh-enhanced/plugin-control-plane 0.1.48 → 0.1.53

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 (185) hide show
  1. package/README.md +187 -15
  2. package/bin/dsh-live-qualification-authority.js +15 -0
  3. package/bin/dsh-local-release-adapter.js +331 -9
  4. package/bin/dsh-systemd-host-attestor.js +359 -9
  5. package/bin/dsh-systemd-host-authority.js +12 -0
  6. package/lib/adoption-coordinator.d.ts +3 -1
  7. package/lib/adoption-coordinator.d.ts.map +1 -1
  8. package/lib/adoption-coordinator.js +24 -9
  9. package/lib/adoption-coordinator.js.map +1 -1
  10. package/lib/cli.d.ts.map +1 -1
  11. package/lib/cli.js +33 -14
  12. package/lib/cli.js.map +1 -1
  13. package/lib/creation-capability-feedback.d.ts +23 -0
  14. package/lib/creation-capability-feedback.d.ts.map +1 -0
  15. package/lib/creation-capability-feedback.js +132 -0
  16. package/lib/creation-capability-feedback.js.map +1 -0
  17. package/lib/creation-capability-journal.d.ts +66 -0
  18. package/lib/creation-capability-journal.d.ts.map +1 -0
  19. package/lib/creation-capability-journal.js +775 -0
  20. package/lib/creation-capability-journal.js.map +1 -0
  21. package/lib/creation-capability-runtime.d.ts +63 -0
  22. package/lib/creation-capability-runtime.d.ts.map +1 -0
  23. package/lib/creation-capability-runtime.js +578 -0
  24. package/lib/creation-capability-runtime.js.map +1 -0
  25. package/lib/creation-capability-source.d.ts +37 -0
  26. package/lib/creation-capability-source.d.ts.map +1 -0
  27. package/lib/creation-capability-source.js +328 -0
  28. package/lib/creation-capability-source.js.map +1 -0
  29. package/lib/creation-capability-types.d.ts +265 -0
  30. package/lib/creation-capability-types.d.ts.map +1 -0
  31. package/lib/creation-capability-types.js +2 -0
  32. package/lib/creation-capability-types.js.map +1 -0
  33. package/lib/deployment-readiness.d.ts +16 -0
  34. package/lib/deployment-readiness.d.ts.map +1 -1
  35. package/lib/deployment-readiness.js +48 -1
  36. package/lib/deployment-readiness.js.map +1 -1
  37. package/lib/effect-blocked-replay.js +2 -2
  38. package/lib/effect-blocked-replay.js.map +1 -1
  39. package/lib/foreground-deployment-runtime.d.ts +3 -0
  40. package/lib/foreground-deployment-runtime.d.ts.map +1 -1
  41. package/lib/foreground-deployment-runtime.js +55 -13
  42. package/lib/foreground-deployment-runtime.js.map +1 -1
  43. package/lib/foreground-deployment.d.ts.map +1 -1
  44. package/lib/foreground-deployment.js +5 -0
  45. package/lib/foreground-deployment.js.map +1 -1
  46. package/lib/host-attestor.d.ts +2 -0
  47. package/lib/host-attestor.d.ts.map +1 -1
  48. package/lib/host-attestor.js +8 -1
  49. package/lib/host-attestor.js.map +1 -1
  50. package/lib/host-inputs.d.ts +13 -0
  51. package/lib/host-inputs.d.ts.map +1 -0
  52. package/lib/host-inputs.js +162 -0
  53. package/lib/host-inputs.js.map +1 -0
  54. package/lib/host-maintenance.d.ts +61 -0
  55. package/lib/host-maintenance.d.ts.map +1 -0
  56. package/lib/host-maintenance.js +158 -0
  57. package/lib/host-maintenance.js.map +1 -0
  58. package/lib/host-readiness.d.ts +5 -0
  59. package/lib/host-readiness.d.ts.map +1 -0
  60. package/lib/host-readiness.js +22 -0
  61. package/lib/host-readiness.js.map +1 -0
  62. package/lib/index.d.ts +16 -0
  63. package/lib/index.d.ts.map +1 -1
  64. package/lib/index.js +7 -0
  65. package/lib/index.js.map +1 -1
  66. package/lib/live-qualification-authority.d.ts +32 -0
  67. package/lib/live-qualification-authority.d.ts.map +1 -0
  68. package/lib/live-qualification-authority.js +280 -0
  69. package/lib/live-qualification-authority.js.map +1 -0
  70. package/lib/live-qualification-client.d.ts +4 -0
  71. package/lib/live-qualification-client.d.ts.map +1 -0
  72. package/lib/live-qualification-client.js +22 -0
  73. package/lib/live-qualification-client.js.map +1 -0
  74. package/lib/live-qualification-runtime.d.ts +78 -0
  75. package/lib/live-qualification-runtime.d.ts.map +1 -0
  76. package/lib/live-qualification-runtime.js +367 -0
  77. package/lib/live-qualification-runtime.js.map +1 -0
  78. package/lib/live-qualification.d.ts +73 -0
  79. package/lib/live-qualification.d.ts.map +1 -0
  80. package/lib/live-qualification.js +164 -0
  81. package/lib/live-qualification.js.map +1 -0
  82. package/lib/native-source-build-worker.d.ts +4 -0
  83. package/lib/native-source-build-worker.d.ts.map +1 -0
  84. package/lib/native-source-build-worker.js +87 -0
  85. package/lib/native-source-build-worker.js.map +1 -0
  86. package/lib/native-source-build.d.ts +36 -0
  87. package/lib/native-source-build.d.ts.map +1 -0
  88. package/lib/native-source-build.js +143 -0
  89. package/lib/native-source-build.js.map +1 -0
  90. package/lib/native-source-pack.d.ts +11 -0
  91. package/lib/native-source-pack.d.ts.map +1 -0
  92. package/lib/native-source-pack.js +83 -0
  93. package/lib/native-source-pack.js.map +1 -0
  94. package/lib/owner-task-gaps.d.ts +2 -0
  95. package/lib/owner-task-gaps.d.ts.map +1 -1
  96. package/lib/owner-task-gaps.js +9 -1
  97. package/lib/owner-task-gaps.js.map +1 -1
  98. package/lib/runtime-epoch.d.ts +69 -0
  99. package/lib/runtime-epoch.d.ts.map +1 -0
  100. package/lib/runtime-epoch.js +139 -0
  101. package/lib/runtime-epoch.js.map +1 -0
  102. package/lib/runtime-observer-protocol.d.ts +3 -3
  103. package/lib/runtime-observer-protocol.js +5 -5
  104. package/lib/runtime-observer-protocol.js.map +1 -1
  105. package/lib/runtime-observer.d.ts.map +1 -1
  106. package/lib/runtime-observer.js +42 -6
  107. package/lib/runtime-observer.js.map +1 -1
  108. package/lib/service.d.ts +248 -4
  109. package/lib/service.d.ts.map +1 -1
  110. package/lib/service.js +1431 -64
  111. package/lib/service.js.map +1 -1
  112. package/lib/source-adoption-authority.d.ts +3 -0
  113. package/lib/source-adoption-authority.d.ts.map +1 -1
  114. package/lib/source-adoption-authority.js +13 -3
  115. package/lib/source-adoption-authority.js.map +1 -1
  116. package/lib/source-adoption-runner.d.ts +3 -0
  117. package/lib/source-adoption-runner.d.ts.map +1 -1
  118. package/lib/source-adoption-runner.js +16 -4
  119. package/lib/source-adoption-runner.js.map +1 -1
  120. package/lib/source-approval-authority.d.ts.map +1 -1
  121. package/lib/source-approval-authority.js +1 -0
  122. package/lib/source-approval-authority.js.map +1 -1
  123. package/lib/source-baseline.d.ts +37 -0
  124. package/lib/source-baseline.d.ts.map +1 -0
  125. package/lib/source-baseline.js +250 -0
  126. package/lib/source-baseline.js.map +1 -0
  127. package/lib/source-build.d.ts +43 -9
  128. package/lib/source-build.d.ts.map +1 -1
  129. package/lib/source-build.js +670 -57
  130. package/lib/source-build.js.map +1 -1
  131. package/lib/source-context.d.ts +18 -0
  132. package/lib/source-context.d.ts.map +1 -1
  133. package/lib/source-context.js +85 -11
  134. package/lib/source-context.js.map +1 -1
  135. package/lib/source-creation.d.ts +106 -0
  136. package/lib/source-creation.d.ts.map +1 -0
  137. package/lib/source-creation.js +664 -0
  138. package/lib/source-creation.js.map +1 -0
  139. package/lib/source-job-types.d.ts +28 -1
  140. package/lib/source-job-types.d.ts.map +1 -1
  141. package/lib/source-jobs.d.ts +33 -3
  142. package/lib/source-jobs.d.ts.map +1 -1
  143. package/lib/source-jobs.js +420 -43
  144. package/lib/source-jobs.js.map +1 -1
  145. package/lib/source-maintenance.d.ts +66 -0
  146. package/lib/source-maintenance.d.ts.map +1 -0
  147. package/lib/source-maintenance.js +177 -0
  148. package/lib/source-maintenance.js.map +1 -0
  149. package/lib/source-revision.d.ts +57 -0
  150. package/lib/source-revision.d.ts.map +1 -0
  151. package/lib/source-revision.js +175 -0
  152. package/lib/source-revision.js.map +1 -0
  153. package/lib/source-workspace.d.ts +19 -14
  154. package/lib/source-workspace.d.ts.map +1 -1
  155. package/lib/source-workspace.js +90 -17
  156. package/lib/source-workspace.js.map +1 -1
  157. package/lib/sqlite.d.ts +13 -3
  158. package/lib/sqlite.d.ts.map +1 -1
  159. package/lib/sqlite.js +362 -1
  160. package/lib/sqlite.js.map +1 -1
  161. package/lib/store.d.ts +258 -3
  162. package/lib/store.d.ts.map +1 -1
  163. package/lib/store.js +2257 -96
  164. package/lib/store.js.map +1 -1
  165. package/lib/systemd-host-authority.d.ts +68 -0
  166. package/lib/systemd-host-authority.d.ts.map +1 -0
  167. package/lib/systemd-host-authority.js +424 -0
  168. package/lib/systemd-host-authority.js.map +1 -0
  169. package/lib/task-observation-evidence.d.ts +32 -0
  170. package/lib/task-observation-evidence.d.ts.map +1 -0
  171. package/lib/task-observation-evidence.js +62 -0
  172. package/lib/task-observation-evidence.js.map +1 -0
  173. package/lib/task-observation-runtime.d.ts +2 -3
  174. package/lib/task-observation-runtime.d.ts.map +1 -1
  175. package/lib/task-observation-runtime.js +7 -39
  176. package/lib/task-observation-runtime.js.map +1 -1
  177. package/lib/task-observation-store.js +1 -1
  178. package/lib/trust.d.ts.map +1 -1
  179. package/lib/trust.js +0 -0
  180. package/lib/trust.js.map +1 -1
  181. package/lib/types.d.ts +57 -7
  182. package/lib/types.d.ts.map +1 -1
  183. package/lib/version.d.ts +1 -1
  184. package/lib/version.js +1 -1
  185. package/package.json +22 -14
@@ -0,0 +1,775 @@
1
+ import { createHash, createPrivateKey, createPublicKey, sign, verify } from 'node:crypto';
2
+ import { closeSync, constants, fstatSync, fsyncSync, lstatSync, openSync, readFileSync, realpathSync } from 'node:fs';
3
+ import { dirname, isAbsolute, resolve } from 'node:path';
4
+ import { DatabaseSync } from 'node:sqlite';
5
+ import { canonicalGrowthJson, validatePluginCreationVerificationCertificate } from '@dsh-enhanced/assistant-growth-contract';
6
+ import { nativeMacRuntimeDigest, validateNativeMacRuntime } from '@dsh-enhanced/assistant-isolation';
7
+ import { validateCreationCapabilitySource } from './creation-capability-source.js';
8
+ const ID = /^[A-Za-z0-9][A-Za-z0-9._:/-]{0,199}$/u;
9
+ const SHA = /^[a-f0-9]{64}$/u;
10
+ const APP_ID = 0x44534341;
11
+ const SEMANTICS = 'dsh-created-capability-journal/v1:reserve-before-dispatch:unknown-no-retry:finite-owner-grant';
12
+ const SOURCE_ARCHIVE_LIMIT = 2 * 1024 * 1024;
13
+ const SOURCE_ARCHIVE_DOMAIN = 'dsh-created-capability-source-archive-v1\0';
14
+ function fail() { throw new Error('creation capability journal rejected input or state'); }
15
+ function obj(value, expected) {
16
+ if (!value || typeof value !== 'object' || Array.isArray(value) || Object.getPrototypeOf(value) !== Object.prototype
17
+ || Reflect.ownKeys(value).length !== expected.length)
18
+ fail();
19
+ const descriptors = Object.getOwnPropertyDescriptors(value);
20
+ if (Object.keys(descriptors).sort().join('\0') !== [...expected].sort().join('\0')
21
+ || !Object.values(descriptors).every(d => d.enumerable && 'value' in d))
22
+ fail();
23
+ return value;
24
+ }
25
+ function str(value, pattern = ID, max = 512) {
26
+ if (typeof value !== 'string' || !pattern.test(value) || value.normalize('NFC') !== value
27
+ || Buffer.byteLength(value) > max || /[\p{Cc}]/u.test(value))
28
+ fail();
29
+ return value;
30
+ }
31
+ function int(value, min, max) {
32
+ if (!Number.isSafeInteger(value) || Number(value) < min || Number(value) > max)
33
+ fail();
34
+ return Number(value);
35
+ }
36
+ function path(value) {
37
+ const result = str(value, /^[\s\S]+$/u, 4096);
38
+ if (!isAbsolute(result) || result === '/' || resolve(result) !== result)
39
+ fail();
40
+ return result;
41
+ }
42
+ function privateStat(file, kind) {
43
+ const s = lstatSync(file);
44
+ if (realpathSync(file) !== file || (kind === 'file' ? !s.isFile() || s.nlink !== 1 : !s.isDirectory())
45
+ || s.uid !== process.getuid?.() || (s.mode & 0o077) !== 0)
46
+ fail();
47
+ }
48
+ function keyBytes(file) {
49
+ privateStat(dirname(file), 'directory');
50
+ privateStat(file, 'file');
51
+ const fd = openSync(file, constants.O_RDONLY | constants.O_NOFOLLOW);
52
+ try {
53
+ const before = fstatSync(fd, { bigint: true }), named = lstatSync(file, { bigint: true });
54
+ if (before.size > 32768n || before.ino !== named.ino || before.dev !== named.dev)
55
+ fail();
56
+ const bytes = readFileSync(fd), after = fstatSync(fd, { bigint: true });
57
+ if (bytes.length > 32768 || before.ino !== after.ino || before.dev !== after.dev
58
+ || before.mtimeNs !== after.mtimeNs || before.ctimeNs !== after.ctimeNs)
59
+ fail();
60
+ return bytes;
61
+ }
62
+ finally {
63
+ closeSync(fd);
64
+ }
65
+ }
66
+ function json(value, max = 65536) {
67
+ const seen = new Set();
68
+ let nodes = 0;
69
+ const walk = (v, depth) => {
70
+ if (++nodes > 8192 || depth > 32)
71
+ fail();
72
+ if (v === null || typeof v === 'string' || typeof v === 'boolean')
73
+ return;
74
+ if (typeof v === 'number' && Number.isFinite(v))
75
+ return;
76
+ if (!v || typeof v !== 'object' || seen.has(v))
77
+ fail();
78
+ seen.add(v);
79
+ if (Array.isArray(v)) {
80
+ if (Object.getPrototypeOf(v) !== Array.prototype || Reflect.ownKeys(v).length !== v.length + 1)
81
+ fail();
82
+ for (let i = 0; i < v.length; i++) {
83
+ if (!Object.hasOwn(v, i))
84
+ fail();
85
+ walk(v[i], depth + 1);
86
+ }
87
+ }
88
+ else {
89
+ const d = Object.getOwnPropertyDescriptors(v);
90
+ if ((Object.getPrototypeOf(v) !== Object.prototype && Object.getPrototypeOf(v) !== null)
91
+ || Reflect.ownKeys(v).length !== Object.keys(d).length
92
+ || !Object.values(d).every(x => x.enumerable && 'value' in x))
93
+ fail();
94
+ for (const [k, x] of Object.entries(d)) {
95
+ if (k === '__proto__' || k === 'constructor')
96
+ fail();
97
+ walk(x.value, depth + 1);
98
+ }
99
+ }
100
+ seen.delete(v);
101
+ };
102
+ walk(value, 0);
103
+ const encoded = canonicalGrowthJson(value);
104
+ if (Buffer.byteLength(encoded) > max)
105
+ fail();
106
+ return encoded;
107
+ }
108
+ const sha = (bytes) => createHash('sha256').update(bytes).digest('hex');
109
+ function digest(value) { return sha(json(value)); }
110
+ function witnessJson(value, config, planId, alias, argumentsDigest, key) {
111
+ const root = obj(value, ['protocol', 'task', 'turn', 'call']);
112
+ if (root.protocol !== 'assistant-delivery/foreground-tool-call/v1')
113
+ fail();
114
+ const task = obj(root.task, ['protocol', 'inboxId', 'sessionId', 'scope', 'owner', 'binding', 'dispatchedAt']);
115
+ if (task.protocol !== 'assistant-delivery/foreground-task/v1')
116
+ fail();
117
+ str(task.inboxId);
118
+ str(task.sessionId);
119
+ const scope = obj(task.scope, ['workspace', 'preset']);
120
+ if (path(scope.workspace) !== config.owner.workspace || str(scope.preset) !== config.owner.agentPreset)
121
+ fail();
122
+ const owner = obj(task.owner, ['principalRecordId', 'principalVersion']);
123
+ if (str(owner.principalRecordId, /^[\s\S]+$/u, 512) !== config.owner.principalRecordId
124
+ || int(owner.principalVersion, 1, Number.MAX_SAFE_INTEGER) !== config.owner.principalVersion)
125
+ fail();
126
+ const binding = obj(task.binding, ['id', 'version', 'generation']);
127
+ str(binding.id, /^[\s\S]+$/u, 512);
128
+ int(binding.version, 1, Number.MAX_SAFE_INTEGER);
129
+ int(binding.generation, 1, Number.MAX_SAFE_INTEGER);
130
+ int(task.dispatchedAt, 1, 8_640_000_000_000_000);
131
+ int(root.turn, 0, Number.MAX_SAFE_INTEGER);
132
+ const call = obj(root.call, ['id', 'toolName', 'eventSeq', 'eventDigest', 'argumentsDigest']);
133
+ str(call.id);
134
+ str(call.toolName);
135
+ int(call.eventSeq, 0, Number.MAX_SAFE_INTEGER);
136
+ str(call.eventDigest, SHA, 64);
137
+ str(call.argumentsDigest, SHA, 64);
138
+ if (call.toolName !== alias || call.argumentsDigest !== argumentsDigest
139
+ || sha(JSON.stringify({ planId, sessionId: task.sessionId, callId: call.id, toolName: alias })) !== key)
140
+ fail();
141
+ return json(value, 4096);
142
+ }
143
+ function receiptBody(receipt) {
144
+ const { signature: _signature, ...body } = receipt;
145
+ return body;
146
+ }
147
+ function compatibleExecution(certificate, config) {
148
+ if (certificate.protocol === 'assistant-growth/creation-verification/v1')
149
+ return !('backend' in config.runner);
150
+ return 'backend' in config.runner && config.runner.backend === 'macos-seatbelt'
151
+ && certificate.execution?.boundary === 'macos-seatbelt-single-process-v1'
152
+ && certificate.execution.runtimeDigest === nativeMacRuntimeDigest(config.runner.runtime);
153
+ }
154
+ function receiptShape(value) {
155
+ const v = obj(value, ['protocol', 'authorityId', 'authorityDigest', 'keyId', 'planId', 'planDigest', 'verificationDigest',
156
+ 'artifactSha256', 'artifactBytes', 'source', 'schemaDigest', 'toolsDigest', 'expiresAt', 'adoptedAt', 'signature']);
157
+ if (v.protocol !== 'dsh-created-capability-adoption/v1' && v.protocol !== 'dsh-created-capability-adoption/v2')
158
+ fail();
159
+ for (const name of ['authorityId', 'keyId', 'planId'])
160
+ str(v[name]);
161
+ for (const name of ['authorityDigest', 'planDigest', 'verificationDigest', 'artifactSha256', 'schemaDigest', 'toolsDigest'])
162
+ str(v[name], SHA, 64);
163
+ int(v.artifactBytes, 1, 512 * 1024);
164
+ int(v.expiresAt, 1, 8_640_000_000_000_000);
165
+ int(v.adoptedAt, 1, 8_640_000_000_000_000);
166
+ obj(v.source, ['referenceDigest', 'ownerDigest', 'growthRunDigest']);
167
+ for (const x of Object.values(v.source))
168
+ str(x, SHA, 64);
169
+ const sig = str(v.signature, /^[A-Za-z0-9_-]{86}$/u, 86);
170
+ if (Buffer.from(sig, 'base64url').length !== 64 || Buffer.from(sig, 'base64url').toString('base64url') !== sig)
171
+ fail();
172
+ return value;
173
+ }
174
+ export function verifyCreationCapabilityReceipt(value, authorityDigest, publicKey, now = Date.now()) {
175
+ try {
176
+ const receipt = receiptShape(value);
177
+ if (!SHA.test(authorityDigest) || receipt.authorityDigest !== authorityDigest || !Number.isSafeInteger(now)
178
+ || receipt.adoptedAt > now || receipt.expiresAt <= now || receipt.expiresAt <= receipt.adoptedAt)
179
+ return false;
180
+ const key = createPublicKey(publicKey);
181
+ return key.asymmetricKeyType === 'ed25519'
182
+ && verify(null, Buffer.from(json(receiptBody(receipt))), key, Buffer.from(receipt.signature, 'base64url'));
183
+ }
184
+ catch {
185
+ return false;
186
+ }
187
+ }
188
+ /** Read-only preflight; it never creates a signing key or ledger. */
189
+ export function validateCreationCapabilityConfig(value) {
190
+ const v = obj(value, ['authorityId', 'keyId', 'keyPath', 'owner', 'namePrefix', 'expiresAt', 'maxAdoptions', 'maxTools',
191
+ 'maxCallsPerAdoption', 'maxCallRecords', 'maxInputBytes', 'runner', ...(Object.hasOwn(value, 'retention') ? ['retention'] : [])]);
192
+ str(v.authorityId);
193
+ str(v.keyId);
194
+ const keyPath = path(v.keyPath);
195
+ const owner = obj(v.owner, ['authorityId', 'authorityHash', 'principalId', 'principalRecordId', 'principalVersion', 'workspace', 'agentPreset']);
196
+ str(owner.authorityId);
197
+ str(owner.authorityHash, SHA, 64);
198
+ str(owner.principalId, /^[\s\S]+$/u, 512);
199
+ str(owner.principalRecordId, /^[\s\S]+$/u, 512);
200
+ int(owner.principalVersion, 1, Number.MAX_SAFE_INTEGER);
201
+ path(owner.workspace);
202
+ str(owner.agentPreset);
203
+ str(v.namePrefix, /^[a-z][a-z0-9]*(?:-[a-z0-9]+)*-$/u, 48);
204
+ int(v.expiresAt, 1, 8_640_000_000_000_000);
205
+ int(v.maxAdoptions, 1, 32);
206
+ int(v.maxTools, 1, 16);
207
+ int(v.maxCallsPerAdoption, 1, 1024);
208
+ int(v.maxCallRecords, 1, 1024);
209
+ int(v.maxInputBytes, 1, 65536);
210
+ if (Object.hasOwn(v, 'retention')) {
211
+ const retention = obj(v.retention, ['maximumLifetimeMs']);
212
+ int(retention.maximumLifetimeMs, 1, 30 * 86_400_000);
213
+ }
214
+ const native = !!v.runner && typeof v.runner === 'object' && v.runner.backend === 'macos-seatbelt';
215
+ const runner = obj(v.runner, ['stateRoot', ...(native ? ['backend', 'runtime'] : ['image', 'dockerPath']),
216
+ 'expiresAt', 'maxRuns', 'maxTotalDurationMs', 'maxDurationMs', 'maxOutputBytes',
217
+ ...(!native && v.runner && typeof v.runner === 'object' && Object.hasOwn(v.runner, 'socketPath') ? ['socketPath'] : [])]);
218
+ const root = path(runner.stateRoot);
219
+ privateStat(root, 'directory');
220
+ if (native)
221
+ validateNativeMacRuntime(runner.runtime);
222
+ else {
223
+ str(runner.image, /^(?:[a-z0-9][a-z0-9._:/-]*@)?sha256:[a-f0-9]{64}$/u, 512);
224
+ const dockerPath = path(runner.dockerPath);
225
+ if (dockerPath === keyPath)
226
+ fail();
227
+ if (Object.hasOwn(runner, 'socketPath')) {
228
+ const socketPath = path(runner.socketPath);
229
+ if (/[\p{Cc},]/u.test(socketPath) || socketPath.includes('://'))
230
+ fail();
231
+ }
232
+ }
233
+ int(runner.expiresAt, 1, 8_640_000_000_000_000);
234
+ int(runner.maxRuns, 1, 1024);
235
+ int(runner.maxTotalDurationMs, 1, 86_400_000);
236
+ int(runner.maxDurationMs, 1, 300_000);
237
+ if (runner.maxDurationMs > runner.maxTotalDurationMs)
238
+ fail();
239
+ int(runner.maxOutputBytes, 65536, 65536);
240
+ const privateKey = createPrivateKey(keyBytes(keyPath));
241
+ if (privateKey.asymmetricKeyType !== 'ed25519')
242
+ fail();
243
+ json(value);
244
+ }
245
+ /** Stable public identity for preflight comparison with the independent verifier key. */
246
+ export function creationCapabilityPublicKey(config) {
247
+ validateCreationCapabilityConfig(config);
248
+ return createPublicKey(createPrivateKey(keyBytes(config.keyPath))).export({ type: 'spki', format: 'pem' }).toString();
249
+ }
250
+ /** Reservations consume quota before any external invocation; recovery only marks uncertainty. */
251
+ export class CreationCapabilityJournal {
252
+ authorityDigest;
253
+ publicKey;
254
+ db;
255
+ key;
256
+ identity;
257
+ path;
258
+ config;
259
+ constructor(input) {
260
+ validateCreationCapabilityConfig(input.config);
261
+ this.config = structuredClone(input.config);
262
+ this.path = path(input.path);
263
+ if (!this.path.endsWith('/creation-adoptions.sqlite') || this.path === this.config.keyPath)
264
+ fail();
265
+ this.key = createPrivateKey(keyBytes(this.config.keyPath));
266
+ this.publicKey = createPublicKey(this.key).export({ type: 'spki', format: 'pem' }).toString();
267
+ this.authorityDigest = digest({ semantics: SEMANTICS, path: this.path, config: this.config,
268
+ keyFingerprint: sha(keyBytes(this.config.keyPath)) });
269
+ privateStat(dirname(this.path), 'directory');
270
+ try {
271
+ const fd = openSync(this.path, constants.O_CREAT | constants.O_EXCL | constants.O_RDWR | constants.O_NOFOLLOW, 0o600);
272
+ try {
273
+ fsyncSync(fd);
274
+ }
275
+ finally {
276
+ closeSync(fd);
277
+ }
278
+ }
279
+ catch (error) {
280
+ if (error.code !== 'EEXIST')
281
+ throw error;
282
+ }
283
+ privateStat(this.path, 'file');
284
+ const stat = lstatSync(this.path);
285
+ this.identity = { dev: stat.dev, ino: stat.ino };
286
+ this.db = new DatabaseSync(this.path);
287
+ try {
288
+ this.checkFiles();
289
+ this.db.exec('PRAGMA busy_timeout=1000; PRAGMA synchronous=FULL; PRAGMA foreign_keys=ON; BEGIN IMMEDIATE');
290
+ try {
291
+ const app = this.db.prepare('PRAGMA application_id').get().application_id;
292
+ const version = this.db.prepare('PRAGMA user_version').get().user_version;
293
+ if (app === 0 && version === 0 && this.db.prepare('SELECT name FROM sqlite_master').all().length === 0) {
294
+ this.db.exec(`CREATE TABLE authority (authority_id TEXT PRIMARY KEY, authority_digest TEXT NOT NULL CHECK(length(authority_digest)=64),
295
+ key_id TEXT NOT NULL, public_key TEXT NOT NULL, config_json TEXT NOT NULL CHECK(json_valid(config_json) AND length(config_json)<=65536)) STRICT, WITHOUT ROWID;
296
+ CREATE TABLE adoptions (plan_id TEXT PRIMARY KEY, status TEXT NOT NULL CHECK(status IN ('claimed','authorized','active','closed','unknown','rejected')),
297
+ certificate_json TEXT NOT NULL CHECK(json_valid(certificate_json) AND length(certificate_json)<=65536), certificate_digest TEXT NOT NULL CHECK(length(certificate_digest)=64),
298
+ artifact BLOB NOT NULL CHECK(length(artifact)>0 AND length(artifact)<=524288), artifact_sha TEXT NOT NULL CHECK(length(artifact_sha)=64),
299
+ tools_json TEXT CHECK(tools_json IS NULL OR (json_valid(tools_json) AND length(tools_json)<=65536)), tools_digest TEXT,
300
+ receipt_json TEXT CHECK(receipt_json IS NULL OR (json_valid(receipt_json) AND length(receipt_json)<=65536)), reason TEXT CHECK(reason IS NULL OR (length(reason)>0 AND length(reason)<=64)),
301
+ CHECK((status='claimed' AND tools_json IS NULL AND tools_digest IS NULL AND receipt_json IS NULL AND reason IS NULL)
302
+ OR (status IN ('authorized','active') AND tools_json IS NOT NULL AND tools_digest IS NOT NULL AND receipt_json IS NOT NULL AND reason IS NULL)
303
+ OR (status IN ('closed','unknown','rejected') AND reason IS NOT NULL))) STRICT, WITHOUT ROWID;
304
+ CREATE TABLE calls (plan_id TEXT NOT NULL REFERENCES adoptions(plan_id), call_key TEXT NOT NULL, arguments_digest TEXT NOT NULL CHECK(length(arguments_digest)=64),
305
+ status TEXT NOT NULL CHECK(status IN ('claimed','completed','unknown')), result_json TEXT CHECK(result_json IS NULL OR (json_valid(result_json) AND length(result_json)<=65536)),
306
+ job_id TEXT, tool_alias TEXT, foreground_json TEXT CHECK(foreground_json IS NULL OR (json_valid(foreground_json) AND length(foreground_json)<=4096)),
307
+ claimed_at INTEGER, settled_at INTEGER, PRIMARY KEY(plan_id,call_key),
308
+ CHECK((status='claimed' AND result_json IS NULL AND job_id IS NULL) OR status IN ('completed','unknown'))) STRICT, WITHOUT ROWID;
309
+ PRAGMA application_id=${APP_ID}; PRAGMA user_version=2;`);
310
+ }
311
+ else if (app === APP_ID && version === 1) {
312
+ this.db.exec(`ALTER TABLE calls ADD COLUMN tool_alias TEXT;
313
+ ALTER TABLE calls ADD COLUMN foreground_json TEXT CHECK(foreground_json IS NULL OR (json_valid(foreground_json) AND length(foreground_json)<=4096));
314
+ ALTER TABLE calls ADD COLUMN claimed_at INTEGER;
315
+ ALTER TABLE calls ADD COLUMN settled_at INTEGER;
316
+ PRAGMA user_version=2;`);
317
+ }
318
+ else if (app !== APP_ID || ![2, 3].includes(version))
319
+ fail();
320
+ if (version !== 3) {
321
+ this.db.exec(`CREATE TABLE source_archives (
322
+ plan_id TEXT PRIMARY KEY REFERENCES adoptions(plan_id),
323
+ archive_json TEXT NOT NULL CHECK(json_valid(archive_json) AND length(archive_json)<=${SOURCE_ARCHIVE_LIMIT})
324
+ ) STRICT, WITHOUT ROWID; PRAGMA user_version=3;`);
325
+ }
326
+ const row = this.db.prepare('SELECT * FROM authority WHERE authority_id=?').get(this.config.authorityId);
327
+ const count = this.db.prepare('SELECT COUNT(*) AS n FROM authority').get().n;
328
+ if (row) {
329
+ if (count !== 1 || row.authority_digest !== this.authorityDigest || row.key_id !== this.config.keyId
330
+ || row.public_key !== this.publicKey || row.config_json !== json(this.config))
331
+ fail();
332
+ }
333
+ else if (count !== 0)
334
+ fail();
335
+ else
336
+ this.db.prepare('INSERT INTO authority VALUES (?,?,?,?,?)').run(this.config.authorityId, this.authorityDigest, this.config.keyId, this.publicKey, json(this.config));
337
+ this.db.exec('COMMIT; PRAGMA journal_mode=WAL');
338
+ }
339
+ catch (e) {
340
+ try {
341
+ this.db.exec('ROLLBACK');
342
+ }
343
+ catch { }
344
+ throw e;
345
+ }
346
+ if (this.db.prepare('PRAGMA foreign_key_check').all().length)
347
+ fail();
348
+ this.checkFiles();
349
+ this.syncDirectory();
350
+ this.list();
351
+ }
352
+ catch (e) {
353
+ this.db.close();
354
+ throw e;
355
+ }
356
+ }
357
+ checkFiles() {
358
+ privateStat(dirname(this.path), 'directory');
359
+ privateStat(this.path, 'file');
360
+ const stat = lstatSync(this.path);
361
+ if (stat.dev !== this.identity.dev || stat.ino !== this.identity.ino)
362
+ fail();
363
+ for (const suffix of ['-wal', '-shm', '-journal']) {
364
+ try {
365
+ privateStat(this.path + suffix, 'file');
366
+ }
367
+ catch (e) {
368
+ if (e.code !== 'ENOENT')
369
+ throw e;
370
+ }
371
+ }
372
+ }
373
+ syncDirectory() {
374
+ this.checkFiles();
375
+ const fd = openSync(dirname(this.path), constants.O_RDONLY | constants.O_DIRECTORY | constants.O_NOFOLLOW);
376
+ try {
377
+ fsyncSync(fd);
378
+ }
379
+ finally {
380
+ closeSync(fd);
381
+ }
382
+ }
383
+ transaction(fn) {
384
+ this.checkFiles();
385
+ this.db.exec('BEGIN IMMEDIATE');
386
+ try {
387
+ const result = fn();
388
+ this.db.exec('COMMIT');
389
+ this.syncDirectory();
390
+ return result;
391
+ }
392
+ catch (e) {
393
+ try {
394
+ this.db.exec('ROLLBACK');
395
+ }
396
+ catch { }
397
+ throw e;
398
+ }
399
+ }
400
+ row(planId) {
401
+ str(planId);
402
+ return this.db.prepare('SELECT * FROM adoptions WHERE plan_id=?').get(planId);
403
+ }
404
+ decode(row) {
405
+ if (!(row.artifact instanceof Uint8Array) || row.artifact.length < 1 || row.artifact.length > 524288 || sha(Buffer.from(row.artifact)) !== row.artifact_sha
406
+ || Buffer.byteLength(row.certificate_json) > 65536 || sha(row.certificate_json) !== row.certificate_digest)
407
+ fail();
408
+ const certificate = JSON.parse(row.certificate_json);
409
+ validatePluginCreationVerificationCertificate(certificate);
410
+ if (!compatibleExecution(certificate, this.config))
411
+ fail();
412
+ if (json(certificate) !== row.certificate_json || certificate.plan.id !== row.plan_id
413
+ || certificate.plan.artifactSha256 !== row.artifact_sha || certificate.plan.artifactBytes !== row.artifact.length)
414
+ fail();
415
+ const record = { planId: row.plan_id, status: row.status, certificate, artifact: Buffer.from(row.artifact) };
416
+ if (row.tools_json !== null) {
417
+ if (Buffer.byteLength(row.tools_json) > 65536 || digest(JSON.parse(row.tools_json)) !== row.tools_digest)
418
+ fail();
419
+ const tools = JSON.parse(row.tools_json);
420
+ this.validateTools(tools);
421
+ if (json(tools) !== row.tools_json)
422
+ fail();
423
+ record.tools = tools;
424
+ }
425
+ else if (row.tools_digest !== null)
426
+ fail();
427
+ if (row.receipt_json !== null) {
428
+ if (Buffer.byteLength(row.receipt_json) > 65536)
429
+ fail();
430
+ const receipt = receiptShape(JSON.parse(row.receipt_json));
431
+ if (json(receipt) !== row.receipt_json || !verifyCreationCapabilityReceipt(receipt, this.authorityDigest, this.publicKey, receipt.adoptedAt)
432
+ || receipt.authorityId !== this.config.authorityId || receipt.keyId !== this.config.keyId
433
+ || receipt.planId !== row.plan_id || receipt.planDigest !== certificate.plan.digest
434
+ || receipt.verificationDigest !== digest(certificate) || receipt.artifactSha256 !== row.artifact_sha
435
+ || receipt.artifactBytes !== row.artifact.length || receipt.source.referenceDigest !== certificate.source.referenceDigest
436
+ || receipt.source.ownerDigest !== certificate.source.ownerDigest || receipt.source.growthRunDigest !== certificate.source.growthRunDigest
437
+ || receipt.toolsDigest !== row.tools_digest || receipt.schemaDigest !== certificate.schemaDigest)
438
+ fail();
439
+ const expectedExpiry = this.config.retention === undefined
440
+ ? Math.min(this.config.expiresAt, this.config.runner.expiresAt, certificate.expiresAt)
441
+ : Math.min(this.config.expiresAt, this.config.runner.expiresAt, receipt.adoptedAt + this.config.retention.maximumLifetimeMs);
442
+ if (receipt.protocol !== (this.config.retention === undefined
443
+ ? 'dsh-created-capability-adoption/v1' : 'dsh-created-capability-adoption/v2')
444
+ || receipt.expiresAt !== expectedExpiry || receipt.adoptedAt < certificate.verifiedAt
445
+ || receipt.adoptedAt >= certificate.expiresAt || receipt.adoptedAt >= this.config.expiresAt
446
+ || receipt.adoptedAt >= this.config.runner.expiresAt)
447
+ fail();
448
+ record.receipt = receipt;
449
+ }
450
+ if (row.reason !== null)
451
+ record.reason = row.reason;
452
+ if (row.status === 'claimed' && (row.tools_json !== null || row.receipt_json !== null || row.reason !== null))
453
+ fail();
454
+ if ((row.status === 'authorized' || row.status === 'active') && (!record.receipt || !record.tools || row.reason !== null))
455
+ fail();
456
+ return record;
457
+ }
458
+ inspect(planId) {
459
+ this.checkFiles();
460
+ const row = this.row(planId);
461
+ if (!row)
462
+ return undefined;
463
+ this.checkCalls(planId);
464
+ return this.decode(row);
465
+ }
466
+ list() {
467
+ this.checkFiles();
468
+ const rows = this.db.prepare('SELECT * FROM adoptions ORDER BY plan_id').all();
469
+ if (rows.length > this.config.maxAdoptions)
470
+ fail();
471
+ if (this.db.prepare('SELECT COUNT(*) AS n FROM calls').get().n > this.config.maxCallRecords)
472
+ fail();
473
+ for (const row of rows)
474
+ this.checkCalls(row.plan_id);
475
+ return rows.map(row => this.decode(row));
476
+ }
477
+ checkCalls(planId) {
478
+ const rows = this.db.prepare('SELECT * FROM calls WHERE plan_id=?').all(planId);
479
+ if (rows.length > this.config.maxCallsPerAdoption)
480
+ fail();
481
+ const adoption = this.row(planId);
482
+ if (!adoption)
483
+ fail();
484
+ const record = this.decode(adoption);
485
+ for (const row of rows)
486
+ this.decodeCall(row, record);
487
+ }
488
+ decodeCall(row, record) {
489
+ str(row.plan_id);
490
+ str(row.call_key);
491
+ str(row.arguments_digest, SHA, 64);
492
+ if (!['claimed', 'completed', 'unknown'].includes(row.status) || (row.status === 'claimed' && (row.result_json !== null || row.job_id !== null)))
493
+ fail();
494
+ const adoption = record ?? this.decode(this.row(row.plan_id));
495
+ if (row.tool_alias !== null) {
496
+ str(row.tool_alias);
497
+ if (!adoption.receipt || !adoption.tools?.some(tool => tool.name === row.tool_alias))
498
+ fail();
499
+ }
500
+ if (row.claimed_at === null) {
501
+ if (row.tool_alias !== null || row.foreground_json !== null || row.settled_at !== null)
502
+ fail();
503
+ }
504
+ else {
505
+ int(row.claimed_at, 1, 8_640_000_000_000_000);
506
+ if (row.settled_at !== null)
507
+ int(row.settled_at, row.claimed_at, 8_640_000_000_000_000);
508
+ }
509
+ if (row.status === 'claimed' && row.settled_at !== null)
510
+ fail();
511
+ if (row.foreground_json !== null) {
512
+ if (row.tool_alias === null || row.claimed_at === null || Buffer.byteLength(row.foreground_json) > 4096
513
+ || witnessJson(JSON.parse(row.foreground_json), this.config, row.plan_id, row.tool_alias, row.arguments_digest, row.call_key) !== row.foreground_json)
514
+ fail();
515
+ }
516
+ const call = { key: row.call_key, status: row.status };
517
+ if (row.result_json !== null) {
518
+ if (Buffer.byteLength(row.result_json) > 65536)
519
+ fail();
520
+ call.result = JSON.parse(row.result_json);
521
+ if (json(call.result) !== row.result_json)
522
+ fail();
523
+ }
524
+ if (row.job_id !== null)
525
+ call.jobId = str(row.job_id);
526
+ return call;
527
+ }
528
+ claim(input) {
529
+ validatePluginCreationVerificationCertificate(input.certificate);
530
+ if (!Buffer.isBuffer(input.artifact) || input.artifact.length < 1 || input.artifact.length > 524288)
531
+ fail();
532
+ const certificate = input.certificate, now = Date.now(), bytes = Buffer.from(input.artifact);
533
+ if (!compatibleExecution(certificate, this.config))
534
+ fail();
535
+ if (certificate.plan.artifactSha256 !== sha(bytes) || certificate.plan.artifactBytes !== bytes.length
536
+ || !certificate.plan.name.startsWith(this.config.namePrefix) || certificate.authority.keyId === this.config.keyId
537
+ || certificate.authority.authorityId === this.config.authorityId
538
+ || now >= this.config.expiresAt || now >= this.config.runner.expiresAt || now >= certificate.expiresAt)
539
+ fail();
540
+ const certificateJson = json(certificate);
541
+ let archiveJson;
542
+ if (input.source !== undefined) {
543
+ validateCreationCapabilitySource(input.source, certificate);
544
+ const body = {
545
+ protocol: 'dsh-created-capability-source-archive/v1', authorityId: this.config.authorityId,
546
+ authorityDigest: this.authorityDigest, keyId: this.config.keyId, planId: certificate.plan.id,
547
+ certificateDigest: sha(certificateJson), artifactSha256: sha(bytes), source: input.source,
548
+ };
549
+ archiveJson = json({ ...body, signature: sign(null, Buffer.from(SOURCE_ARCHIVE_DOMAIN + json(body, SOURCE_ARCHIVE_LIMIT)), this.key).toString('base64url') }, SOURCE_ARCHIVE_LIMIT);
550
+ }
551
+ return this.transaction(() => {
552
+ const prior = this.row(certificate.plan.id);
553
+ if (prior) {
554
+ const record = this.decode(prior);
555
+ if (prior.certificate_digest !== sha(certificateJson) || prior.artifact_sha !== sha(bytes)
556
+ || !record.artifact.equals(bytes))
557
+ fail();
558
+ if (archiveJson !== undefined) {
559
+ const existing = this.db.prepare('SELECT archive_json FROM source_archives WHERE plan_id=?')
560
+ .get(certificate.plan.id);
561
+ if (existing?.archive_json !== archiveJson)
562
+ fail();
563
+ }
564
+ return { created: false, record };
565
+ }
566
+ if (this.db.prepare('SELECT COUNT(*) AS n FROM adoptions').get().n >= this.config.maxAdoptions)
567
+ fail();
568
+ this.db.prepare("INSERT INTO adoptions(plan_id,status,certificate_json,certificate_digest,artifact,artifact_sha) VALUES (?,'claimed',?,?,?,?)")
569
+ .run(certificate.plan.id, certificateJson, sha(certificateJson), bytes, sha(bytes));
570
+ if (archiveJson !== undefined)
571
+ this.db.prepare('INSERT INTO source_archives VALUES (?,?)').run(certificate.plan.id, archiveJson);
572
+ return { created: true, record: this.decode(this.row(certificate.plan.id)) };
573
+ });
574
+ }
575
+ /** Archive validation is lazy; adoption reconciliation never loads source text. */
576
+ inspectSourceArchive(planId) {
577
+ const record = this.inspect(planId);
578
+ if (!record?.receipt)
579
+ return undefined;
580
+ const row = this.db.prepare('SELECT archive_json FROM source_archives WHERE plan_id=?')
581
+ .get(planId);
582
+ if (!row)
583
+ return undefined;
584
+ if (Buffer.byteLength(row.archive_json) > SOURCE_ARCHIVE_LIMIT)
585
+ fail();
586
+ const value = JSON.parse(row.archive_json);
587
+ const item = obj(value, ['protocol', 'authorityId', 'authorityDigest', 'keyId', 'planId', 'certificateDigest', 'artifactSha256', 'source', 'signature']);
588
+ if (item.protocol !== 'dsh-created-capability-source-archive/v1'
589
+ || item.authorityId !== this.config.authorityId || item.authorityDigest !== this.authorityDigest
590
+ || item.keyId !== this.config.keyId || item.planId !== record.planId
591
+ || item.certificateDigest !== digest(record.certificate)
592
+ || item.artifactSha256 !== record.certificate.plan.artifactSha256)
593
+ fail();
594
+ validateCreationCapabilitySource(item.source, record.certificate);
595
+ const signature = str(item.signature, /^[A-Za-z0-9_-]{86}$/u, 86);
596
+ const { signature: _signature, ...body } = item;
597
+ if (json(value, SOURCE_ARCHIVE_LIMIT) !== row.archive_json
598
+ || Buffer.from(signature, 'base64url').toString('base64url') !== signature
599
+ || !verify(null, Buffer.from(SOURCE_ARCHIVE_DOMAIN + json(body, SOURCE_ARCHIVE_LIMIT)), createPublicKey(this.key), Buffer.from(signature, 'base64url')))
600
+ fail();
601
+ return value;
602
+ }
603
+ validateTools(value) {
604
+ if (!Array.isArray(value) || Object.getPrototypeOf(value) !== Array.prototype || value.length < 1
605
+ || value.length > this.config.maxTools || Reflect.ownKeys(value).length !== value.length + 1)
606
+ fail();
607
+ const names = new Set();
608
+ for (const tool of value) {
609
+ const item = obj(tool, ['originalName', 'name', 'description', 'parameters']);
610
+ str(item.originalName);
611
+ str(item.name);
612
+ str(item.description, /^[\s\S]+$/u, 2048);
613
+ if (names.has(item.name))
614
+ fail();
615
+ names.add(item.name);
616
+ if (!item.parameters || typeof item.parameters !== 'object' || Array.isArray(item.parameters))
617
+ fail();
618
+ json(item.parameters, 65536);
619
+ }
620
+ json(value);
621
+ }
622
+ authorize(planId, tools) {
623
+ this.validateTools(tools);
624
+ const toolsJson = json(tools), toolsDigest = sha(toolsJson);
625
+ return this.transaction(() => {
626
+ const prior = this.row(planId);
627
+ if (!prior)
628
+ fail();
629
+ const record = this.decode(prior);
630
+ if (record.status === 'authorized' || record.status === 'active') {
631
+ if (prior.tools_digest !== toolsDigest)
632
+ fail();
633
+ return record;
634
+ }
635
+ if (record.status !== 'claimed')
636
+ fail();
637
+ const now = Date.now(), freshExpiry = Math.min(this.config.expiresAt, this.config.runner.expiresAt, record.certificate.expiresAt);
638
+ if (now >= freshExpiry || record.certificate.verifiedAt > now)
639
+ fail();
640
+ const expiresAt = this.config.retention === undefined ? freshExpiry
641
+ : Math.min(this.config.expiresAt, this.config.runner.expiresAt, now + this.config.retention.maximumLifetimeMs);
642
+ const unsigned = { protocol: this.config.retention === undefined
643
+ ? 'dsh-created-capability-adoption/v1' : 'dsh-created-capability-adoption/v2',
644
+ authorityId: this.config.authorityId, authorityDigest: this.authorityDigest, keyId: this.config.keyId,
645
+ planId, planDigest: record.certificate.plan.digest, verificationDigest: digest(record.certificate),
646
+ artifactSha256: prior.artifact_sha, artifactBytes: prior.artifact.length, source: record.certificate.source,
647
+ schemaDigest: record.certificate.schemaDigest, toolsDigest, expiresAt, adoptedAt: now };
648
+ const receipt = receiptShape({ ...unsigned, signature: sign(null, Buffer.from(json(unsigned)), this.key).toString('base64url') });
649
+ this.db.prepare("UPDATE adoptions SET status='authorized',tools_json=?,tools_digest=?,receipt_json=? WHERE plan_id=? AND status='claimed'")
650
+ .run(toolsJson, toolsDigest, json(receipt), planId);
651
+ return this.decode(this.row(planId));
652
+ });
653
+ }
654
+ activate(planId) {
655
+ return this.transaction(() => {
656
+ const prior = this.row(planId);
657
+ if (!prior)
658
+ fail();
659
+ const record = this.decode(prior);
660
+ if (record.status === 'active')
661
+ return record;
662
+ if (record.status !== 'authorized' || !record.receipt || Date.now() >= record.receipt.expiresAt)
663
+ fail();
664
+ this.db.prepare("UPDATE adoptions SET status='active' WHERE plan_id=? AND status='authorized'").run(planId);
665
+ return this.decode(this.row(planId));
666
+ });
667
+ }
668
+ settle(planId, status, reason) {
669
+ if (!['closed', 'unknown', 'rejected'].includes(status))
670
+ fail();
671
+ str(reason, /^[a-z][a-z0-9-]{0,63}$/u, 64);
672
+ this.transaction(() => {
673
+ const prior = this.row(planId);
674
+ if (!prior)
675
+ fail();
676
+ const current = this.decode(prior);
677
+ if (current.status === status && prior.reason === reason)
678
+ return;
679
+ if (['closed', 'unknown', 'rejected'].includes(current.status))
680
+ fail();
681
+ this.db.prepare('UPDATE adoptions SET status=?,reason=? WHERE plan_id=?').run(status, reason, planId);
682
+ });
683
+ }
684
+ claimCall(input) {
685
+ str(input.planId);
686
+ str(input.key);
687
+ str(input.argumentsDigest, SHA, 64);
688
+ return this.transaction(() => {
689
+ const row = this.row(input.planId);
690
+ if (!row)
691
+ fail();
692
+ const record = this.decode(row);
693
+ if (input.toolAlias !== undefined && (!record.tools?.some(tool => tool.name === input.toolAlias)
694
+ || str(input.toolAlias) !== input.toolAlias))
695
+ fail();
696
+ if (input.foreground !== undefined && input.toolAlias === undefined)
697
+ fail();
698
+ const foreground = input.foreground === undefined ? null : witnessJson(input.foreground, this.config, input.planId, input.toolAlias, input.argumentsDigest, input.key);
699
+ const prior = this.db.prepare('SELECT * FROM calls WHERE plan_id=? AND call_key=?').get(input.planId, input.key);
700
+ if (prior) {
701
+ if (prior.arguments_digest !== input.argumentsDigest
702
+ || (input.toolAlias !== undefined && prior.tool_alias !== null && prior.tool_alias !== input.toolAlias)
703
+ || (prior.foreground_json !== null && foreground === null)
704
+ || (foreground !== null && prior.foreground_json !== null && prior.foreground_json !== foreground)
705
+ || (foreground !== null && prior.foreground_json === null && prior.claimed_at !== null))
706
+ fail();
707
+ return { created: false, call: this.decodeCall(prior, record) };
708
+ }
709
+ const now = Date.now();
710
+ if (record.status !== 'active' || !record.receipt || now >= record.receipt.expiresAt)
711
+ fail();
712
+ const global = this.db.prepare('SELECT COUNT(*) AS n FROM calls').get().n;
713
+ const local = this.db.prepare('SELECT COUNT(*) AS n FROM calls WHERE plan_id=?').get(input.planId).n;
714
+ if (global >= this.config.maxCallRecords || local >= this.config.maxCallsPerAdoption)
715
+ fail();
716
+ this.db.prepare("INSERT INTO calls(plan_id,call_key,arguments_digest,status,tool_alias,foreground_json,claimed_at) VALUES (?,?,?,'claimed',?,?,?)")
717
+ .run(input.planId, input.key, input.argumentsDigest, input.toolAlias ?? null, foreground, now);
718
+ return { created: true, call: { key: input.key, status: 'claimed' } };
719
+ });
720
+ }
721
+ settleCall(input) {
722
+ str(input.planId);
723
+ str(input.key);
724
+ if (input.status !== 'completed' && input.status !== 'unknown')
725
+ fail();
726
+ const resultJson = Object.hasOwn(input, 'result') ? json(input.result) : null;
727
+ const jobId = input.jobId === undefined ? null : str(input.jobId);
728
+ this.transaction(() => {
729
+ const row = this.db.prepare('SELECT * FROM calls WHERE plan_id=? AND call_key=?').get(input.planId, input.key);
730
+ if (!row || row.status !== 'claimed')
731
+ fail();
732
+ this.decodeCall(row);
733
+ this.db.prepare('UPDATE calls SET status=?,result_json=?,job_id=?,settled_at=? WHERE plan_id=? AND call_key=? AND status=\'claimed\'')
734
+ .run(input.status, resultJson, jobId, row.claimed_at === null ? null : Math.max(Date.now(), row.claimed_at), input.planId, input.key);
735
+ });
736
+ }
737
+ /** Host-only bounded metadata; never expose candidate arguments, results or private artifact bytes. */
738
+ listCallEvidence(planId) {
739
+ this.checkFiles();
740
+ const adoption = this.row(planId);
741
+ if (!adoption)
742
+ return [];
743
+ const record = this.decode(adoption);
744
+ const rows = this.db.prepare('SELECT * FROM calls WHERE plan_id=? ORDER BY call_key').all(planId);
745
+ if (rows.length > this.config.maxCallsPerAdoption || (rows.length > 0 && !record.receipt))
746
+ fail();
747
+ if (!record.receipt)
748
+ return [];
749
+ const receiptDigest = digest(record.receipt);
750
+ return rows.map(row => {
751
+ this.decodeCall(row, record);
752
+ const tool = record.tools?.find(item => item.name === row.tool_alias);
753
+ return { protocol: 'dsh-created-capability-call-evidence/v1',
754
+ planId, key: row.call_key, status: row.status,
755
+ attribution: row.claimed_at === null ? 'legacy-unattributed'
756
+ : row.foreground_json === null ? 'unattributed' : 'foreground',
757
+ ...(row.tool_alias === null ? {} : { toolAlias: row.tool_alias }),
758
+ ...(tool === undefined ? {} : { originalName: tool.originalName }),
759
+ receiptDigest, artifactSha256: record.receipt.artifactSha256, schemaDigest: record.receipt.schemaDigest,
760
+ ...(row.claimed_at === null ? {} : { claimedAt: row.claimed_at }),
761
+ ...(row.settled_at === null ? {} : { settledAt: row.settled_at }),
762
+ ...(row.foreground_json === null ? {} : { foreground: JSON.parse(row.foreground_json) }),
763
+ };
764
+ });
765
+ }
766
+ recoverClaims() {
767
+ this.transaction(() => {
768
+ this.db.prepare("UPDATE adoptions SET status='unknown',reason='recovered-claim' WHERE status='claimed'").run();
769
+ this.db.prepare("UPDATE calls SET status='unknown',settled_at=CASE WHEN claimed_at IS NULL THEN NULL ELSE max(claimed_at,?) END WHERE status='claimed'")
770
+ .run(Date.now());
771
+ });
772
+ }
773
+ close() { this.db.close(); }
774
+ }
775
+ //# sourceMappingURL=creation-capability-journal.js.map