@dsh-enhanced/plugin-control-plane 0.1.7 → 0.1.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.
Files changed (67) hide show
  1. package/README.md +153 -32
  2. package/bin/dsh-local-release-adapter.js +1381 -0
  3. package/cordis.patch.yml +1 -0
  4. package/lib/approval.d.ts +13 -0
  5. package/lib/approval.d.ts.map +1 -0
  6. package/lib/approval.js +79 -0
  7. package/lib/approval.js.map +1 -0
  8. package/lib/attestation.d.ts +14 -0
  9. package/lib/attestation.d.ts.map +1 -0
  10. package/lib/attestation.js +222 -0
  11. package/lib/attestation.js.map +1 -0
  12. package/lib/catalog-interpreter.d.ts +12 -0
  13. package/lib/catalog-interpreter.d.ts.map +1 -0
  14. package/lib/catalog-interpreter.js +100 -0
  15. package/lib/catalog-interpreter.js.map +1 -0
  16. package/lib/catalog.d.ts +95 -10
  17. package/lib/catalog.d.ts.map +1 -1
  18. package/lib/catalog.js +1031 -18
  19. package/lib/catalog.js.map +1 -1
  20. package/lib/cli.d.ts +9 -0
  21. package/lib/cli.d.ts.map +1 -1
  22. package/lib/cli.js +1160 -162
  23. package/lib/cli.js.map +1 -1
  24. package/lib/host-attestor.d.ts +13 -0
  25. package/lib/host-attestor.d.ts.map +1 -0
  26. package/lib/host-attestor.js +139 -0
  27. package/lib/host-attestor.js.map +1 -0
  28. package/lib/index.d.ts +9 -0
  29. package/lib/index.d.ts.map +1 -1
  30. package/lib/index.js +9 -0
  31. package/lib/index.js.map +1 -1
  32. package/lib/lockfile.d.ts +7 -0
  33. package/lib/lockfile.d.ts.map +1 -0
  34. package/lib/lockfile.js +271 -0
  35. package/lib/lockfile.js.map +1 -0
  36. package/lib/release.d.ts +51 -0
  37. package/lib/release.d.ts.map +1 -0
  38. package/lib/release.js +1188 -0
  39. package/lib/release.js.map +1 -0
  40. package/lib/service.d.ts +9 -11
  41. package/lib/service.d.ts.map +1 -1
  42. package/lib/service.js +57 -29
  43. package/lib/service.js.map +1 -1
  44. package/lib/sqlite.d.ts +9 -0
  45. package/lib/sqlite.d.ts.map +1 -0
  46. package/lib/sqlite.js +709 -0
  47. package/lib/sqlite.js.map +1 -0
  48. package/lib/store.d.ts +258 -0
  49. package/lib/store.d.ts.map +1 -0
  50. package/lib/store.js +1848 -0
  51. package/lib/store.js.map +1 -0
  52. package/lib/tools.d.ts.map +1 -1
  53. package/lib/tools.js +23 -3
  54. package/lib/tools.js.map +1 -1
  55. package/lib/trust.d.ts +79 -0
  56. package/lib/trust.d.ts.map +1 -0
  57. package/lib/trust.js +477 -0
  58. package/lib/trust.js.map +1 -0
  59. package/lib/types.d.ts +740 -0
  60. package/lib/types.d.ts.map +1 -0
  61. package/lib/types.js +2 -0
  62. package/lib/types.js.map +1 -0
  63. package/lib/version.d.ts +1 -1
  64. package/lib/version.d.ts.map +1 -1
  65. package/lib/version.js +1 -1
  66. package/lib/version.js.map +1 -1
  67. package/package.json +2 -2
package/lib/store.js ADDED
@@ -0,0 +1,1848 @@
1
+ import { createHash, randomUUID } from 'node:crypto';
2
+ import { isAbsolute } from 'node:path';
3
+ import { discover, parseCatalog } from './catalog.js';
4
+ import { parseApprovalReceipt } from './approval.js';
5
+ import { parseSourcePublishReconciliationReceipt, parseSourcePublishReconciliationRequest, parseSourceReleaseAuthorization, parseSourceReleaseReceipt, parseSourceReleaseRequest, parseVerifiedSourceReleaseAuthorization } from './release.js';
6
+ import { controlPlaneOperationReceiptDigest, openControlPlaneDatabase } from './sqlite.js';
7
+ const KEY = /^[A-Za-z0-9][A-Za-z0-9._:-]{0,159}$/u;
8
+ const PROFILE = /^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$/u;
9
+ const PLUGIN_NAME = /^(?=.{1,64}$)[a-z][a-z0-9]*(?:-[a-z0-9]+)*$/u;
10
+ const DIGEST = /^[a-f0-9]{64}$/u;
11
+ const COMMIT = /^[a-f0-9]{40}$/u;
12
+ const UUID = /^[a-f0-9]{8}-[a-f0-9]{4}-[1-8][a-f0-9]{3}-[89ab][a-f0-9]{3}-[a-f0-9]{12}$/u;
13
+ const SOURCE_STATUSES = new Set(['pending-approval', 'approved', 'running-local-checks', 'ready-for-human-review',
14
+ 'local-checks-failed', 'awaiting-pr', 'awaiting-review', 'awaiting-merge', 'awaiting-build', 'awaiting-sign', 'awaiting-publish',
15
+ 'awaiting-registry-verify', 'awaiting-catalog-admission', 'release-complete', 'release-failed', 'publish-ambiguous']);
16
+ const SOURCE_PLAN_KEYS = ['schemaVersion', 'kind', 'id', 'gapId', 'gapSnapshot', 'status', 'revision', 'createdAt',
17
+ 'expiresAt', 'digest', 'repository', 'worktree', 'baseCommit', 'name', 'generatorDigest', 'scope'];
18
+ const ACTIVATION_PLAN_KEYS = ['schemaVersion', 'kind', 'id', 'gapId', 'gapSnapshot', 'status', 'revision', 'createdAt',
19
+ 'expiresAt', 'profile', 'candidate', 'dossier', 'installationId', 'ledger', 'target', 'executor', 'digest'];
20
+ const ACTIVATION_STATUSES = new Set(['pending-approval', 'approved', 'staging', 'awaiting-reload', 'awaiting-readiness',
21
+ 'awaiting-effect-blocked-replay', 'awaiting-shadow', 'awaiting-canary', 'awaiting-soak', 'awaiting-health', 'commit-pending',
22
+ 'rollback-pending', 'activated', 'rolled-back']);
23
+ export class ControlPlaneStoreError extends Error {
24
+ code;
25
+ constructor(code, message) {
26
+ super(message);
27
+ this.code = code;
28
+ this.name = 'ControlPlaneStoreError';
29
+ }
30
+ }
31
+ function canonical(value) {
32
+ if (Array.isArray(value))
33
+ return `[${value.map(canonical).join(',')}]`;
34
+ if (typeof value === 'object' && value !== null) {
35
+ return `{${Object.entries(value).filter(([, item]) => item !== undefined)
36
+ .sort(([left], [right]) => left.localeCompare(right)).map(([key, item]) => `${JSON.stringify(key)}:${canonical(item)}`).join(',')}}`;
37
+ }
38
+ return JSON.stringify(value);
39
+ }
40
+ export function controlPlaneDigest(value) {
41
+ return createHash('sha256').update(canonical(value)).digest('hex');
42
+ }
43
+ function bounded(value, field, maximum = 1_000) {
44
+ if (typeof value !== 'string')
45
+ throw new ControlPlaneStoreError('invalid-input', `${field} must be text`);
46
+ const result = value.normalize('NFC').trim();
47
+ const control = [...result].some(character => {
48
+ const point = character.codePointAt(0);
49
+ return point <= 0x1f || point === 0x7f;
50
+ });
51
+ if (result === '' || Buffer.byteLength(result) > maximum || control)
52
+ throw new ControlPlaneStoreError('invalid-input', `${field} must be bounded printable text`);
53
+ return result;
54
+ }
55
+ function finite(value, field, minimum, maximum) {
56
+ if (!Number.isFinite(value) || value < minimum || value > maximum)
57
+ throw new ControlPlaneStoreError('invalid-input', `${field} is outside its accepted range`);
58
+ return value;
59
+ }
60
+ function positiveInteger(value, field) {
61
+ if (!Number.isSafeInteger(value) || value < 1)
62
+ throw new ControlPlaneStoreError('invalid-input', `${field} must be a positive safe integer`);
63
+ return value;
64
+ }
65
+ function expectedSourceScope(name) {
66
+ return Object.freeze(['plugins/README.md', `plugins/${name}`].sort());
67
+ }
68
+ function objectRecord(value, label) {
69
+ if (typeof value !== 'object' || value === null || Array.isArray(value)) {
70
+ throw new ControlPlaneStoreError('invalid-state', `${label} is corrupt`);
71
+ }
72
+ return value;
73
+ }
74
+ function exactKeys(value, fields, label) {
75
+ if (Object.keys(value).sort().join('\0') !== [...fields].sort().join('\0')) {
76
+ throw new ControlPlaneStoreError('invalid-state', `${label} has unknown or missing fields`);
77
+ }
78
+ }
79
+ function verifiedApprovalFromStored(value, planId, planDigest, createdAt, expiresAt) {
80
+ const item = objectRecord(value, 'stored source approval');
81
+ exactKeys(item, ['schemaVersion', 'approvalId', 'authority', 'keyId', 'planId', 'planDigest', 'decision', 'principal',
82
+ 'decidedAt', 'expiresAt', 'signatureDigest'], 'stored source approval');
83
+ if (item['schemaVersion'] !== 1 || item['decision'] !== 'approved' || item['planId'] !== planId
84
+ || item['planDigest'] !== planDigest || typeof item['approvalId'] !== 'string' || !KEY.test(item['approvalId'])
85
+ || typeof item['authority'] !== 'string' || !KEY.test(item['authority']) || typeof item['keyId'] !== 'string'
86
+ || !KEY.test(item['keyId']) || typeof item['principal'] !== 'string' || item['principal'].normalize('NFC').trim() !== item['principal']
87
+ || item['principal'] === '' || item['principal'].length > 256 || !Number.isSafeInteger(item['decidedAt'])
88
+ || !Number.isSafeInteger(item['expiresAt']) || Number(item['expiresAt']) <= Number(item['decidedAt'])
89
+ || Number(item['decidedAt']) < createdAt || Number(item['decidedAt']) > expiresAt
90
+ || !DIGEST.test(String(item['signatureDigest']))) {
91
+ throw new ControlPlaneStoreError('invalid-state', 'stored source approval is corrupt or plan-mismatched');
92
+ }
93
+ return item;
94
+ }
95
+ function projectedApproval(receipt) {
96
+ const { signature, ...fields } = receipt;
97
+ return { ...fields, principal: fields.principal.normalize('NFC').trim(),
98
+ signatureDigest: createHash('sha256').update(Buffer.from(signature, 'base64')).digest('hex') };
99
+ }
100
+ async function reverifySourceReleaseAuthorization(authorization, plan, resolveAuthority) {
101
+ const { signatureDigest: _signatureDigest, ...signedAuthorization } = authorization;
102
+ const verified = await resolveAuthority(signedAuthorization).verify(signedAuthorization, plan);
103
+ if (controlPlaneDigest(verified) !== controlPlaneDigest(authorization)) {
104
+ throw new ControlPlaneStoreError('conflict', 'source release authorization changed during verification');
105
+ }
106
+ }
107
+ function activationSnapshotFromStored(value) {
108
+ const item = objectRecord(value, 'stored activation plan snapshot');
109
+ const optional = ['approval', 'activation'].filter(key => Object.hasOwn(item, key));
110
+ exactKeys(item, [...ACTIVATION_PLAN_KEYS, ...optional], 'stored activation plan snapshot');
111
+ const gapSnapshot = objectRecord(item['gapSnapshot'], 'stored activation gap snapshot');
112
+ exactKeys(gapSnapshot, ['revision', 'inputDigest', 'roi', 'capability'], 'stored activation gap snapshot');
113
+ const dossier = objectRecord(item['dossier'], 'stored activation dossier');
114
+ exactKeys(dossier, ['catalogDigest', 'catalogProvenance', 'matchedCapabilities', 'authorities', 'packages'], 'stored activation dossier');
115
+ const ledger = objectRecord(item['ledger'], 'stored activation ledger');
116
+ exactKeys(ledger, ['id', 'path'], 'stored activation ledger');
117
+ const target = objectRecord(item['target'], 'stored activation target');
118
+ exactKeys(target, ['dshHome', 'profile', 'profilePath'], 'stored activation target');
119
+ const executor = objectRecord(item['executor'], 'stored activation executor');
120
+ exactKeys(executor, ['id', 'version', 'path', 'sha256'], 'stored activation executor');
121
+ let candidate;
122
+ try {
123
+ candidate = parseCatalog({ schemaVersion: 1, entries: [item['candidate']] }).entries[0];
124
+ }
125
+ catch {
126
+ throw new ControlPlaneStoreError('invalid-state', 'stored activation candidate snapshot is corrupt');
127
+ }
128
+ if (item['schemaVersion'] !== 4 || item['kind'] !== 'activation' || typeof item['id'] !== 'string'
129
+ || typeof item['gapId'] !== 'string' || typeof item['status'] !== 'string' || !ACTIVATION_STATUSES.has(item['status'])
130
+ || !Number.isSafeInteger(item['revision']) || Number(item['revision']) < 1 || !Number.isSafeInteger(item['createdAt'])
131
+ || !Number.isSafeInteger(item['expiresAt']) || Number(item['expiresAt']) <= Number(item['createdAt'])
132
+ || typeof item['profile'] !== 'string' || !PROFILE.test(item['profile']) || typeof item['installationId'] !== 'string'
133
+ || !UUID.test(item['installationId']) || typeof item['digest'] !== 'string' || !DIGEST.test(item['digest'])
134
+ || controlPlaneDigest(item['candidate']) !== controlPlaneDigest(candidate)
135
+ || !Number.isSafeInteger(gapSnapshot['revision']) || typeof gapSnapshot['inputDigest'] !== 'string'
136
+ || !DIGEST.test(gapSnapshot['inputDigest']) || typeof gapSnapshot['roi'] !== 'number' || !Number.isFinite(gapSnapshot['roi'])
137
+ || typeof gapSnapshot['capability'] !== 'string' || typeof dossier['catalogDigest'] !== 'string'
138
+ || !DIGEST.test(dossier['catalogDigest']) || dossier['catalogProvenance'] !== 'owner-provided-integrity-pinned'
139
+ || !Array.isArray(dossier['matchedCapabilities']) || !Array.isArray(dossier['authorities']) || !Array.isArray(dossier['packages'])
140
+ || typeof ledger['id'] !== 'string' || !UUID.test(ledger['id']) || typeof ledger['path'] !== 'string' || !isAbsolute(ledger['path'])
141
+ || target['profile'] !== item['profile'] || typeof target['dshHome'] !== 'string' || !isAbsolute(target['dshHome'])
142
+ || typeof target['profilePath'] !== 'string' || !isAbsolute(target['profilePath']) || typeof executor['id'] !== 'string'
143
+ || typeof executor['version'] !== 'string' || typeof executor['path'] !== 'string' || !isAbsolute(executor['path'])
144
+ || typeof executor['sha256'] !== 'string' || !DIGEST.test(executor['sha256'])) {
145
+ throw new ControlPlaneStoreError('invalid-state', 'stored activation plan snapshot is corrupt');
146
+ }
147
+ const immutable = { schemaVersion: 4, kind: 'activation', id: item['id'], gapId: item['gapId'], gapSnapshot,
148
+ profile: item['profile'], candidate, dossier, installationId: item['installationId'], ledger, target, executor,
149
+ createdAt: item['createdAt'], expiresAt: item['expiresAt'] };
150
+ if (controlPlaneDigest(immutable) !== item['digest'])
151
+ throw new ControlPlaneStoreError('invalid-state', 'stored activation snapshot digest is corrupt');
152
+ const approval = item['approval'] === undefined ? undefined : verifiedApprovalFromStored(item['approval'], item['id'], item['digest'], Number(item['createdAt']), Number(item['expiresAt']));
153
+ let activation;
154
+ if (item['activation'] !== undefined) {
155
+ const stored = objectRecord(item['activation'], 'stored activation identity');
156
+ const activationOptional = ['targetOriginallyExisted', 'failureCode'].filter(key => Object.hasOwn(stored, key));
157
+ exactKeys(stored, ['id', 'fence', 'updatedAt', ...activationOptional], 'stored activation identity');
158
+ if (typeof stored['id'] !== 'string' || !KEY.test(stored['id']) || !Number.isSafeInteger(stored['fence'])
159
+ || Number(stored['fence']) < 1 || !Number.isSafeInteger(stored['updatedAt'])
160
+ || Number(stored['updatedAt']) < Number(item['createdAt'])
161
+ || (stored['targetOriginallyExisted'] !== undefined && typeof stored['targetOriginallyExisted'] !== 'boolean')
162
+ || (stored['failureCode'] !== undefined && typeof stored['failureCode'] !== 'string')) {
163
+ throw new ControlPlaneStoreError('invalid-state', 'stored activation identity is corrupt');
164
+ }
165
+ activation = stored;
166
+ }
167
+ const status = item['status'];
168
+ const requiresApproval = status !== 'pending-approval';
169
+ const requiresActivation = !['pending-approval', 'approved'].includes(status);
170
+ if (requiresApproval !== (approval !== undefined) || requiresActivation !== (activation !== undefined)) {
171
+ throw new ControlPlaneStoreError('invalid-state', 'stored activation snapshot fields do not match its status');
172
+ }
173
+ return { ...immutable, digest: item['digest'], status, revision: Number(item['revision']),
174
+ ...(approval === undefined ? {} : { approval }), ...(activation === undefined ? {} : { activation }) };
175
+ }
176
+ function gapFromRow(row) {
177
+ if (!DIGEST.test(row.input_digest) || !Number.isSafeInteger(row.revision) || row.revision < 1)
178
+ throw new ControlPlaneStoreError('invalid-state', 'stored capability gap is corrupt');
179
+ return {
180
+ id: row.id, idempotencyKey: row.idempotency_key, inputDigest: row.input_digest,
181
+ capability: row.capability, context: row.context, expectedValue: row.expected_value,
182
+ frequency: row.frequency, estimatedCost: row.estimated_cost, risk: row.risk, roi: row.roi,
183
+ status: row.status, revision: row.revision, ...(row.candidate_id === null ? {} : { candidateId: row.candidate_id }),
184
+ createdAt: row.created_at, updatedAt: row.updated_at,
185
+ };
186
+ }
187
+ function activationFromRow(row) {
188
+ const candidate = parseCatalog({ schemaVersion: 1, entries: [JSON.parse(row.candidate_json)] }).entries[0];
189
+ const gapSnapshot = JSON.parse(row.gap_snapshot_json);
190
+ const dossier = JSON.parse(row.dossier_json);
191
+ if (candidate === undefined || !DIGEST.test(row.plan_digest) || !UUID.test(row.installation_id)
192
+ || !DIGEST.test(gapSnapshot.inputDigest) || !DIGEST.test(dossier.catalogDigest))
193
+ throw new ControlPlaneStoreError('invalid-state', 'stored activation plan is corrupt');
194
+ const immutable = {
195
+ schemaVersion: 4, kind: 'activation', id: row.id, gapId: row.gap_id, gapSnapshot,
196
+ profile: row.profile, candidate, dossier, installationId: row.installation_id,
197
+ ledger: { id: row.ledger_id, path: row.ledger_path },
198
+ target: { dshHome: row.dsh_home, profile: row.profile, profilePath: row.target_path },
199
+ executor: { id: row.executor_id, version: row.executor_version, path: row.executor_path, sha256: row.executor_digest },
200
+ createdAt: row.created_at, expiresAt: row.expires_at,
201
+ };
202
+ if (controlPlaneDigest(immutable) !== row.plan_digest)
203
+ throw new ControlPlaneStoreError('invalid-state', 'stored activation plan digest does not match its immutable dossier');
204
+ let approval;
205
+ if (row.approval_json !== null) {
206
+ let storedApproval;
207
+ try {
208
+ storedApproval = JSON.parse(row.approval_json);
209
+ }
210
+ catch {
211
+ throw new ControlPlaneStoreError('invalid-state', 'stored activation approval is corrupt');
212
+ }
213
+ approval = verifiedApprovalFromStored(storedApproval, row.id, row.plan_digest, row.created_at, row.expires_at);
214
+ }
215
+ const requiresApproval = row.status !== 'pending-approval';
216
+ const requiresActivation = !['pending-approval', 'approved'].includes(row.status);
217
+ if (requiresApproval !== (approval !== undefined) || requiresActivation !== (row.activation_id !== null)
218
+ || (row.activation_id === null && (row.activation_fence !== 0 || row.activation_lease_until !== null
219
+ || row.activation_target_existed !== null || row.failure_code !== null))
220
+ || (row.activation_id !== null && (!KEY.test(row.activation_id) || row.activation_fence < 1))
221
+ || (['rolled-back', 'activated'].includes(row.status) && row.activation_lease_until !== null)) {
222
+ throw new ControlPlaneStoreError('invalid-state', 'stored activation state is corrupt or incomplete');
223
+ }
224
+ return {
225
+ ...immutable, digest: row.plan_digest, status: row.status, revision: row.revision,
226
+ ...(approval === undefined ? {} : { approval }),
227
+ ...(row.activation_id === null ? {} : { activation: { id: row.activation_id, fence: row.activation_fence,
228
+ ...(row.activation_target_existed === null ? {} : { targetOriginallyExisted: row.activation_target_existed === 1 }),
229
+ ...(row.failure_code === null ? {} : { failureCode: row.failure_code }), updatedAt: row.updated_at } }),
230
+ };
231
+ }
232
+ function sourceFromRow(row) {
233
+ const gapSnapshot = JSON.parse(row.gap_snapshot_json);
234
+ const scope = JSON.parse(row.scope_json);
235
+ const immutable = { schemaVersion: 1, kind: 'source', id: row.id, gapId: row.gap_id, gapSnapshot,
236
+ repository: row.repository, worktree: row.worktree, baseCommit: row.base_commit, name: row.plugin_name,
237
+ generatorDigest: row.generator_digest, scope, createdAt: row.created_at, expiresAt: row.expires_at };
238
+ if (!COMMIT.test(row.base_commit) || !DIGEST.test(row.generator_digest) || !DIGEST.test(gapSnapshot.inputDigest)
239
+ || controlPlaneDigest(scope) !== controlPlaneDigest(expectedSourceScope(row.plugin_name))
240
+ || controlPlaneDigest(immutable) !== row.plan_digest)
241
+ throw new ControlPlaneStoreError('invalid-state', 'stored source plan is corrupt or digest-mismatched');
242
+ let approval;
243
+ if (row.approval_json !== null) {
244
+ let storedApproval;
245
+ try {
246
+ storedApproval = JSON.parse(row.approval_json);
247
+ }
248
+ catch {
249
+ throw new ControlPlaneStoreError('invalid-state', 'stored source approval is corrupt');
250
+ }
251
+ approval = verifiedApprovalFromStored(storedApproval, row.id, row.plan_digest, row.created_at, row.expires_at);
252
+ }
253
+ const sourceCheck = row.checked_tree_digest === null ? undefined : { treeDigest: row.checked_tree_digest,
254
+ patchDigest: row.checked_patch_digest, checkedAt: row.checked_at };
255
+ if ((row.checked_tree_digest === null) !== (row.checked_patch_digest === null)
256
+ || (row.checked_tree_digest === null) !== (row.checked_at === null)
257
+ || (sourceCheck !== undefined && (!DIGEST.test(sourceCheck.treeDigest) || !DIGEST.test(sourceCheck.patchDigest)
258
+ || !Number.isSafeInteger(sourceCheck.checkedAt) || sourceCheck.checkedAt < 0))) {
259
+ throw new ControlPlaneStoreError('invalid-state', 'stored source check evidence is corrupt');
260
+ }
261
+ let releaseAuthorization;
262
+ if (row.release_authorization_json !== null) {
263
+ try {
264
+ releaseAuthorization = parseVerifiedSourceReleaseAuthorization(JSON.parse(row.release_authorization_json));
265
+ }
266
+ catch {
267
+ throw new ControlPlaneStoreError('invalid-state', 'stored source release authorization is corrupt');
268
+ }
269
+ if (!DIGEST.test(row.release_authorization_digest ?? '')
270
+ || controlPlaneDigest(releaseAuthorization) !== row.release_authorization_digest || releaseAuthorization.planId !== row.id
271
+ || releaseAuthorization.planDigest !== row.plan_digest || releaseAuthorization.baseCommit !== row.base_commit
272
+ || releaseAuthorization.checkedTreeDigest !== row.checked_tree_digest || releaseAuthorization.checkedPatchDigest !== row.checked_patch_digest
273
+ || controlPlaneDigest(releaseAuthorization.scope) !== controlPlaneDigest(scope)) {
274
+ throw new ControlPlaneStoreError('invalid-state', 'stored source release authorization is not bound to its source plan');
275
+ }
276
+ }
277
+ if ((row.release_authorization_json === null) !== (row.release_authorization_digest === null)) {
278
+ throw new ControlPlaneStoreError('invalid-state', 'stored source release authorization digest is corrupt');
279
+ }
280
+ const activeRelease = expectedSourceRelease(row.status) !== undefined || row.status === 'publish-ambiguous' || row.status === 'release-complete';
281
+ if ((row.release_id === null) !== (row.release_fence === 0) || (activeRelease
282
+ && (row.release_id === null || releaseAuthorization === undefined || sourceCheck === undefined))) {
283
+ throw new ControlPlaneStoreError('invalid-state', 'stored source release state is incomplete');
284
+ }
285
+ return { ...immutable, digest: row.plan_digest, status: row.status, revision: row.revision, ...(approval === undefined ? {} : { approval }),
286
+ ...(sourceCheck === undefined ? {} : { sourceCheck }),
287
+ ...(releaseAuthorization === undefined ? {} : { releaseAuthorization }),
288
+ ...(row.release_id === null ? {} : { release: { id: row.release_id, fence: row.release_fence,
289
+ ...(row.release_failure_phase === null ? {} : { failurePhase: row.release_failure_phase }),
290
+ ...(row.release_failure_code === null ? {} : { failureCode: row.release_failure_code }), updatedAt: row.updated_at } }) };
291
+ }
292
+ function sourceSnapshotFromStored(value) {
293
+ const item = objectRecord(value, 'stored source plan snapshot');
294
+ const optional = ['approval', 'sourceCheck', 'releaseAuthorization', 'release']
295
+ .filter(key => Object.hasOwn(item, key));
296
+ exactKeys(item, [...SOURCE_PLAN_KEYS, ...optional], 'stored source plan snapshot');
297
+ const gapSnapshot = objectRecord(item['gapSnapshot'], 'stored source gap snapshot');
298
+ exactKeys(gapSnapshot, ['revision', 'inputDigest', 'roi', 'capability'], 'stored source gap snapshot');
299
+ if (item['schemaVersion'] !== 1 || item['kind'] !== 'source' || typeof item['id'] !== 'string'
300
+ || typeof item['gapId'] !== 'string' || typeof item['status'] !== 'string' || !SOURCE_STATUSES.has(item['status'])
301
+ || !Number.isSafeInteger(item['revision']) || Number(item['revision']) < 1 || !Number.isSafeInteger(item['createdAt'])
302
+ || !Number.isSafeInteger(item['expiresAt']) || Number(item['expiresAt']) <= Number(item['createdAt'])
303
+ || typeof item['digest'] !== 'string' || !DIGEST.test(item['digest']) || typeof item['repository'] !== 'string'
304
+ || typeof item['worktree'] !== 'string' || typeof item['baseCommit'] !== 'string' || !COMMIT.test(item['baseCommit'])
305
+ || typeof item['name'] !== 'string' || !PLUGIN_NAME.test(item['name']) || typeof item['generatorDigest'] !== 'string'
306
+ || !DIGEST.test(item['generatorDigest']) || !Array.isArray(item['scope'])
307
+ || controlPlaneDigest(item['scope']) !== controlPlaneDigest(expectedSourceScope(item['name']))
308
+ || !Number.isSafeInteger(gapSnapshot['revision']) || Number(gapSnapshot['revision']) < 1
309
+ || typeof gapSnapshot['inputDigest'] !== 'string' || !DIGEST.test(gapSnapshot['inputDigest'])
310
+ || typeof gapSnapshot['roi'] !== 'number' || !Number.isFinite(gapSnapshot['roi']) || typeof gapSnapshot['capability'] !== 'string') {
311
+ throw new ControlPlaneStoreError('invalid-state', 'stored source plan snapshot is corrupt');
312
+ }
313
+ const immutable = { schemaVersion: 1, kind: 'source', id: item['id'], gapId: item['gapId'], gapSnapshot,
314
+ repository: item['repository'], worktree: item['worktree'], baseCommit: item['baseCommit'], name: item['name'],
315
+ generatorDigest: item['generatorDigest'], scope: item['scope'], createdAt: item['createdAt'], expiresAt: item['expiresAt'] };
316
+ if (controlPlaneDigest(immutable) !== item['digest'])
317
+ throw new ControlPlaneStoreError('invalid-state', 'stored source plan snapshot digest is corrupt');
318
+ const approval = item['approval'] === undefined ? undefined : verifiedApprovalFromStored(item['approval'], item['id'], item['digest'], Number(item['createdAt']), Number(item['expiresAt']));
319
+ let sourceCheck;
320
+ if (item['sourceCheck'] !== undefined) {
321
+ const check = objectRecord(item['sourceCheck'], 'stored source check snapshot');
322
+ exactKeys(check, ['treeDigest', 'patchDigest', 'checkedAt'], 'stored source check snapshot');
323
+ if (typeof check['treeDigest'] !== 'string' || !DIGEST.test(check['treeDigest'])
324
+ || typeof check['patchDigest'] !== 'string' || !DIGEST.test(check['patchDigest'])
325
+ || !Number.isSafeInteger(check['checkedAt']) || Number(check['checkedAt']) < Number(item['createdAt'])) {
326
+ throw new ControlPlaneStoreError('invalid-state', 'stored source check snapshot is corrupt');
327
+ }
328
+ sourceCheck = check;
329
+ }
330
+ let releaseAuthorization;
331
+ if (item['releaseAuthorization'] !== undefined) {
332
+ try {
333
+ releaseAuthorization = parseVerifiedSourceReleaseAuthorization(item['releaseAuthorization']);
334
+ }
335
+ catch {
336
+ throw new ControlPlaneStoreError('invalid-state', 'stored source authorization snapshot is corrupt');
337
+ }
338
+ if (releaseAuthorization.planId !== item['id'] || releaseAuthorization.planDigest !== item['digest']
339
+ || releaseAuthorization.baseCommit !== item['baseCommit'] || sourceCheck === undefined
340
+ || releaseAuthorization.checkedTreeDigest !== sourceCheck.treeDigest
341
+ || releaseAuthorization.checkedPatchDigest !== sourceCheck.patchDigest
342
+ || controlPlaneDigest(releaseAuthorization.scope) !== controlPlaneDigest(item['scope'])) {
343
+ throw new ControlPlaneStoreError('invalid-state', 'stored source authorization snapshot is plan-mismatched');
344
+ }
345
+ }
346
+ let release;
347
+ if (item['release'] !== undefined) {
348
+ const stored = objectRecord(item['release'], 'stored source release snapshot');
349
+ const releaseOptional = ['failurePhase', 'failureCode'].filter(key => Object.hasOwn(stored, key));
350
+ exactKeys(stored, ['id', 'fence', 'updatedAt', ...releaseOptional], 'stored source release snapshot');
351
+ if (typeof stored['id'] !== 'string' || !KEY.test(stored['id']) || !Number.isSafeInteger(stored['fence'])
352
+ || Number(stored['fence']) < 1 || !Number.isSafeInteger(stored['updatedAt'])
353
+ || Number(stored['updatedAt']) < Number(item['createdAt'])
354
+ || (stored['failurePhase'] !== undefined && !['pr', 'review', 'merge', 'build', 'sign', 'publish',
355
+ 'registry-verify', 'catalog-admission'].includes(String(stored['failurePhase'])))
356
+ || (stored['failureCode'] !== undefined && (typeof stored['failureCode'] !== 'string' || stored['failureCode'] === ''))) {
357
+ throw new ControlPlaneStoreError('invalid-state', 'stored source release snapshot is corrupt');
358
+ }
359
+ release = stored;
360
+ }
361
+ const status = item['status'];
362
+ const requiresApproval = status !== 'pending-approval';
363
+ const requiresCheck = expectedSourceRelease(status) !== undefined || ['ready-for-human-review', 'release-complete',
364
+ 'release-failed', 'publish-ambiguous'].includes(status);
365
+ const requiresRelease = expectedSourceRelease(status) !== undefined || ['release-complete', 'release-failed', 'publish-ambiguous'].includes(status);
366
+ if (requiresApproval !== (approval !== undefined) || requiresCheck !== (sourceCheck !== undefined)
367
+ || requiresRelease !== (releaseAuthorization !== undefined && release !== undefined)
368
+ || (!requiresRelease && (releaseAuthorization !== undefined || release !== undefined))) {
369
+ throw new ControlPlaneStoreError('invalid-state', 'stored source plan snapshot fields do not match its status');
370
+ }
371
+ return { ...immutable, digest: item['digest'], status: item['status'], revision: Number(item['revision']),
372
+ ...(approval === undefined ? {} : { approval }), ...(sourceCheck === undefined ? {} : { sourceCheck }),
373
+ ...(releaseAuthorization === undefined ? {} : { releaseAuthorization }),
374
+ ...(release === undefined ? {} : { release }) };
375
+ }
376
+ const expectedAttestation = Object.freeze({
377
+ 'awaiting-reload': { phase: 'reload', next: 'awaiting-readiness' },
378
+ 'awaiting-readiness': { phase: 'readiness', next: 'awaiting-effect-blocked-replay' },
379
+ 'awaiting-effect-blocked-replay': { phase: 'effect-blocked-replay', next: 'awaiting-shadow' },
380
+ 'awaiting-shadow': { phase: 'shadow', next: 'awaiting-canary' },
381
+ 'awaiting-canary': { phase: 'canary', next: 'awaiting-soak' },
382
+ 'awaiting-soak': { phase: 'soak', next: 'awaiting-health' },
383
+ 'awaiting-health': { phase: 'health', next: 'commit-pending' },
384
+ });
385
+ export function expectedHostAttestation(status) {
386
+ return expectedAttestation[status];
387
+ }
388
+ function requestBinding(request) {
389
+ const { operationId: _operationId, requestedAt: _requestedAt, ...binding } = request;
390
+ return binding;
391
+ }
392
+ function hostOperationFromRow(row) {
393
+ const request = JSON.parse(row.request_json);
394
+ if (request.operationId !== row.operation_id || request.plan.id !== row.plan_id || request.phase !== row.phase
395
+ || controlPlaneDigest(request) !== row.request_digest || controlPlaneDigest(requestBinding(request)) !== row.binding_digest
396
+ || (row.status === 'pending') !== (row.receipt_json === null)) {
397
+ throw new ControlPlaneStoreError('invalid-state', 'stored Host attestation operation is corrupt');
398
+ }
399
+ const receipt = row.receipt_json === null ? undefined : JSON.parse(row.receipt_json);
400
+ if (receipt !== undefined && (receipt.operationId !== row.operation_id || controlPlaneDigest(receipt) !== row.receipt_digest)) {
401
+ throw new ControlPlaneStoreError('invalid-state', 'stored Host attestation receipt is corrupt');
402
+ }
403
+ return { planId: row.plan_id, phase: row.phase, operationId: row.operation_id, bindingDigest: row.binding_digest,
404
+ requestDigest: row.request_digest, request, status: row.status, ...(receipt === undefined ? {} : { receipt }),
405
+ createdAt: row.created_at, ...(row.completed_at === null ? {} : { completedAt: row.completed_at }),
406
+ ...(row.applied_at === null ? {} : { appliedAt: row.applied_at }) };
407
+ }
408
+ function releaseRequestBinding(request) {
409
+ const { operationId: _operationId, requestedAt: _requestedAt, ...binding } = request;
410
+ return binding;
411
+ }
412
+ function sourceReleaseOperationFromRow(row) {
413
+ let request;
414
+ try {
415
+ request = parseSourceReleaseRequest(JSON.parse(row.request_json));
416
+ }
417
+ catch (error) {
418
+ if (error instanceof ControlPlaneStoreError)
419
+ throw new ControlPlaneStoreError('invalid-state', `stored source release request is corrupt: ${error.message}`);
420
+ throw new ControlPlaneStoreError('invalid-state', 'stored source release request is corrupt');
421
+ }
422
+ const timestampsValid = Number.isSafeInteger(row.created_at) && row.created_at >= 0
423
+ && (row.completed_at === null || (Number.isSafeInteger(row.completed_at) && row.completed_at >= row.created_at))
424
+ && (row.applied_at === null || (row.completed_at !== null && Number.isSafeInteger(row.applied_at) && row.applied_at >= row.completed_at));
425
+ const stateValid = row.status === 'pending'
426
+ ? row.receipt_digest === null && row.receipt_json === null && row.completed_at === null && row.applied_at === null
427
+ : row.status === 'completed'
428
+ ? row.receipt_digest !== null && row.receipt_json !== null && row.completed_at !== null && row.applied_at === null
429
+ : row.status === 'applied' && row.receipt_digest !== null && row.receipt_json !== null
430
+ && row.completed_at !== null && row.applied_at !== null;
431
+ if (!timestampsValid || !stateValid || !DIGEST.test(row.binding_digest) || !DIGEST.test(row.request_digest)
432
+ || !Number.isSafeInteger(row.release_fence) || row.release_fence < 1 || !Number.isSafeInteger(row.attempt) || row.attempt < 1
433
+ || request.operationId !== row.operation_id || request.plan.id !== row.plan_id || request.phase !== row.phase
434
+ || request.release.id !== row.release_id || request.release.fence !== row.release_fence || request.attempt !== row.attempt
435
+ || controlPlaneDigest(request) !== row.request_digest || controlPlaneDigest(releaseRequestBinding(request)) !== row.binding_digest) {
436
+ throw new ControlPlaneStoreError('invalid-state', 'stored source release operation is corrupt');
437
+ }
438
+ let receipt;
439
+ if (row.receipt_json !== null) {
440
+ try {
441
+ receipt = parseSourceReleaseReceipt(JSON.parse(row.receipt_json));
442
+ }
443
+ catch (error) {
444
+ if (error instanceof ControlPlaneStoreError)
445
+ throw new ControlPlaneStoreError('invalid-state', `stored source release receipt is corrupt: ${error.message}`);
446
+ throw new ControlPlaneStoreError('invalid-state', 'stored source release receipt is corrupt');
447
+ }
448
+ if (receipt.operationId !== row.operation_id || receipt.planId !== row.plan_id || receipt.phase !== row.phase
449
+ || receipt.releaseId !== row.release_id || receipt.fence !== row.release_fence
450
+ || receipt.requestDigest !== row.request_digest || controlPlaneDigest(receipt) !== row.receipt_digest) {
451
+ throw new ControlPlaneStoreError('invalid-state', 'stored source release receipt is not bound to its operation');
452
+ }
453
+ }
454
+ return { planId: row.plan_id, phase: row.phase, operationId: row.operation_id, attempt: row.attempt, fence: row.release_fence,
455
+ bindingDigest: row.binding_digest, requestDigest: row.request_digest, request, status: row.status,
456
+ ...(receipt === undefined ? {} : { receipt }), createdAt: row.created_at,
457
+ ...(row.completed_at === null ? {} : { completedAt: row.completed_at }),
458
+ ...(row.applied_at === null ? {} : { appliedAt: row.applied_at }) };
459
+ }
460
+ function reconciliationBinding(request) {
461
+ const { operationId: _operationId, requestedAt: _requestedAt, ...binding } = request;
462
+ return binding;
463
+ }
464
+ function sourcePublishReconciliationFromRow(row) {
465
+ let request;
466
+ try {
467
+ request = parseSourcePublishReconciliationRequest(JSON.parse(row.request_json));
468
+ }
469
+ catch {
470
+ throw new ControlPlaneStoreError('invalid-state', 'stored publish reconciliation request is corrupt');
471
+ }
472
+ const stateValid = row.status === 'pending'
473
+ ? row.receipt_digest === null && row.receipt_json === null && row.completed_at === null && row.applied_at === null
474
+ : row.status === 'completed'
475
+ ? row.receipt_digest !== null && row.receipt_json !== null && row.completed_at !== null && row.applied_at === null
476
+ : row.status === 'applied' && row.receipt_digest !== null && row.receipt_json !== null && row.completed_at !== null && row.applied_at !== null;
477
+ if (!stateValid || !Number.isSafeInteger(row.attempt) || row.attempt < 1 || request.operationId !== row.operation_id
478
+ || request.plan.id !== row.plan_id || request.release.id !== row.release_id || request.release.fence !== row.release_fence
479
+ || request.attempt !== row.attempt
480
+ || controlPlaneDigest(request) !== row.request_digest || controlPlaneDigest(reconciliationBinding(request)) !== row.binding_digest) {
481
+ throw new ControlPlaneStoreError('invalid-state', 'stored publish reconciliation operation is corrupt');
482
+ }
483
+ let receipt;
484
+ if (row.receipt_json !== null) {
485
+ try {
486
+ receipt = parseSourcePublishReconciliationReceipt(JSON.parse(row.receipt_json));
487
+ }
488
+ catch {
489
+ throw new ControlPlaneStoreError('invalid-state', 'stored publish reconciliation receipt is corrupt');
490
+ }
491
+ if (receipt.operationId !== row.operation_id || receipt.planId !== row.plan_id || receipt.releaseId !== row.release_id
492
+ || receipt.fence !== row.release_fence || receipt.requestDigest !== row.request_digest
493
+ || controlPlaneDigest(receipt) !== row.receipt_digest)
494
+ throw new ControlPlaneStoreError('invalid-state', 'stored publish reconciliation receipt is not bound');
495
+ }
496
+ return { planId: row.plan_id, releaseId: row.release_id, fence: row.release_fence, attempt: row.attempt,
497
+ operationId: row.operation_id, bindingDigest: row.binding_digest, requestDigest: row.request_digest, request, status: row.status,
498
+ ...(receipt === undefined ? {} : { receipt }), createdAt: row.created_at,
499
+ ...(row.completed_at === null ? {} : { completedAt: row.completed_at }), ...(row.applied_at === null ? {} : { appliedAt: row.applied_at }) };
500
+ }
501
+ const expectedRelease = Object.freeze({
502
+ 'awaiting-pr': { phase: 'pr', next: 'awaiting-review' },
503
+ 'awaiting-review': { phase: 'review', next: 'awaiting-merge' },
504
+ 'awaiting-merge': { phase: 'merge', next: 'awaiting-build' },
505
+ 'awaiting-build': { phase: 'build', next: 'awaiting-sign' },
506
+ 'awaiting-sign': { phase: 'sign', next: 'awaiting-publish' },
507
+ 'awaiting-publish': { phase: 'publish', next: 'awaiting-registry-verify' },
508
+ 'awaiting-registry-verify': { phase: 'registry-verify', next: 'awaiting-catalog-admission' },
509
+ 'awaiting-catalog-admission': { phase: 'catalog-admission', next: 'release-complete' },
510
+ });
511
+ export function expectedSourceRelease(status) {
512
+ return expectedRelease[status];
513
+ }
514
+ function releaseArtifact(evidence) {
515
+ if (evidence.kind !== 'build')
516
+ throw new ControlPlaneStoreError('invalid-state', 'durable build evidence is missing');
517
+ return { candidateId: evidence.candidateId, sourceName: evidence.sourceName, packagePath: evidence.packagePath,
518
+ packageName: evidence.packageName, packageVersion: evidence.packageVersion, tarballPath: evidence.tarballPath,
519
+ tarballBytes: evidence.tarballBytes, tarballSha256: evidence.tarballSha256, tarballIntegrity: evidence.tarballIntegrity,
520
+ sbomPath: evidence.sbomPath, sbomSha256: evidence.sbomSha256, provenancePath: evidence.provenancePath,
521
+ provenanceSha256: evidence.provenanceSha256, mergedCommit: evidence.mergedCommit, dshBaseline: evidence.dshBaseline,
522
+ capabilities: evidence.capabilities, authorities: evidence.authorities, requires: evidence.requires };
523
+ }
524
+ export class ControlPlaneStore {
525
+ #database;
526
+ #now;
527
+ constructor(options) { this.#database = openControlPlaneDatabase(options.path); this.#now = options.now ?? Date.now; }
528
+ close() { this.#database.close(); }
529
+ recordGap(input) {
530
+ const normalized = {
531
+ idempotencyKey: bounded(input.idempotencyKey, 'idempotencyKey', 160), capability: bounded(input.capability, 'capability', 300),
532
+ context: bounded(input.context, 'context', 4_000), expectedValue: finite(input.expectedValue, 'expectedValue', 0, 1_000_000_000),
533
+ frequency: finite(input.frequency, 'frequency', 0.000_001, 1_000_000), estimatedCost: finite(input.estimatedCost, 'estimatedCost', 0.000_001, 1_000_000_000),
534
+ risk: finite(input.risk, 'risk', 0, 1),
535
+ };
536
+ if (!KEY.test(normalized.idempotencyKey))
537
+ throw new ControlPlaneStoreError('invalid-input', 'idempotencyKey has invalid syntax');
538
+ const inputDigest = controlPlaneDigest(normalized);
539
+ const prior = this.#database.prepare('SELECT * FROM capability_gaps WHERE idempotency_key = ?').get(normalized.idempotencyKey);
540
+ if (prior !== undefined) {
541
+ if (prior.input_digest !== inputDigest)
542
+ throw new ControlPlaneStoreError('conflict', 'capability gap idempotency key was reused with different input');
543
+ return gapFromRow(prior);
544
+ }
545
+ const now = this.#now();
546
+ const id = `gap-${randomUUID()}`;
547
+ const roi = Math.min(1_000_000_000, (normalized.expectedValue * normalized.frequency * (1 - normalized.risk)) / normalized.estimatedCost);
548
+ try {
549
+ this.#database.prepare(`INSERT INTO capability_gaps (id, idempotency_key, input_digest, capability, context,
550
+ expected_value, frequency, estimated_cost, risk, roi, status, candidate_id, revision, created_at, updated_at)
551
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 'open', NULL, 1, ?, ?)`).run(id, normalized.idempotencyKey, inputDigest, normalized.capability, normalized.context, normalized.expectedValue, normalized.frequency, normalized.estimatedCost, normalized.risk, roi, now, now);
552
+ }
553
+ catch (error) {
554
+ const raced = this.#database.prepare('SELECT * FROM capability_gaps WHERE idempotency_key = ?').get(normalized.idempotencyKey);
555
+ if (raced !== undefined && raced.input_digest === inputDigest)
556
+ return gapFromRow(raced);
557
+ throw error;
558
+ }
559
+ return this.getGap(id);
560
+ }
561
+ getGap(id) {
562
+ const row = this.#database.prepare('SELECT * FROM capability_gaps WHERE id = ?').get(id);
563
+ if (row === undefined)
564
+ throw new ControlPlaneStoreError('not-found', 'capability gap not found');
565
+ return gapFromRow(row);
566
+ }
567
+ listGaps(limit = 20) {
568
+ if (!Number.isSafeInteger(limit) || limit < 1 || limit > 100)
569
+ throw new ControlPlaneStoreError('invalid-input', 'gap limit must be 1..100');
570
+ return this.#database.prepare(`SELECT * FROM capability_gaps WHERE status = 'open' ORDER BY roi DESC, created_at, id LIMIT ?`).all(limit).map(gapFromRow);
571
+ }
572
+ createPlan(input) {
573
+ const profile = bounded(input.profile, 'profile', 64);
574
+ const idempotencyKey = bounded(input.idempotencyKey, 'idempotencyKey', 160);
575
+ if (!PROFILE.test(profile) || !KEY.test(idempotencyKey) || !UUID.test(input.installationId) || !UUID.test(input.ledger.id)
576
+ || !DIGEST.test(input.catalog.digest) || !DIGEST.test(input.executor.sha256)
577
+ || !isAbsolute(input.ledger.path) || !isAbsolute(input.executor.path))
578
+ throw new ControlPlaneStoreError('invalid-input', 'activation plan binding is invalid');
579
+ positiveInteger(input.ttlMs, 'ttlMs');
580
+ if (input.ttlMs < 60_000 || input.ttlMs > 86_400_000)
581
+ throw new ControlPlaneStoreError('invalid-input', 'ttlMs is invalid');
582
+ const candidate = parseCatalog({ schemaVersion: 1, entries: [input.candidate] }).entries[0];
583
+ const matchedCapabilities = [...new Set(input.matchedCapabilities.map(value => bounded(value, 'matched capability', 300)))].sort();
584
+ if (matchedCapabilities.length === 0)
585
+ throw new ControlPlaneStoreError('invalid-input', 'candidate must match the bound capability gap');
586
+ const packages = Object.freeze([{ package: candidate.package, version: candidate.version, integrity: candidate.integrity,
587
+ ...(candidate.registry === undefined ? {} : { registry: candidate.registry }) }, ...candidate.requires]);
588
+ const requestBinding = { operation: 'create-activation-plan', gapId: input.gapId, candidate, catalog: input.catalog,
589
+ matchedCapabilities, profile, target: input.target, installationId: input.installationId,
590
+ ledger: input.ledger, executor: input.executor, ttlMs: input.ttlMs };
591
+ const inputDigest = controlPlaneDigest(requestBinding);
592
+ const prior = this.#activationPlanReceiptByKey(idempotencyKey, 'create-activation-plan', inputDigest);
593
+ if (prior !== undefined)
594
+ return prior;
595
+ const gap = this.getGap(input.gapId);
596
+ if (gap.status !== 'open')
597
+ throw new ControlPlaneStoreError('invalid-state', 'only an open gap can create an activation plan');
598
+ if (gap.candidateId !== undefined && gap.candidateId !== candidate.id) {
599
+ throw new ControlPlaneStoreError('conflict', 'released capability gap is reserved for its exact admitted candidate');
600
+ }
601
+ if (!discover({ schemaVersion: 1, entries: [candidate] }, gap.capability).some(item => item.id === candidate.id)
602
+ || matchedCapabilities.some(value => !candidate.capabilities.includes(value))) {
603
+ throw new ControlPlaneStoreError('invalid-input', 'candidate dossier does not match the exact gap capability');
604
+ }
605
+ const gapSnapshot = Object.freeze({ revision: gap.revision, inputDigest: gap.inputDigest, roi: gap.roi, capability: gap.capability });
606
+ const dossier = Object.freeze({ catalogDigest: input.catalog.digest, catalogProvenance: input.catalog.provenance,
607
+ matchedCapabilities: Object.freeze(matchedCapabilities), authorities: Object.freeze([...candidate.authorities]), packages });
608
+ const now = this.#now();
609
+ const id = `plugin-${randomUUID()}`;
610
+ const expiresAt = now + input.ttlMs;
611
+ const immutable = { schemaVersion: 4, kind: 'activation', id, gapId: gap.id, gapSnapshot,
612
+ profile, candidate, dossier, installationId: input.installationId, ledger: input.ledger,
613
+ target: input.target, executor: input.executor,
614
+ createdAt: now, expiresAt };
615
+ const planDigest = controlPlaneDigest(immutable);
616
+ this.#database.exec('BEGIN IMMEDIATE');
617
+ try {
618
+ const raced = this.#activationPlanReceiptByKey(idempotencyKey, 'create-activation-plan', inputDigest);
619
+ if (raced !== undefined) {
620
+ this.#database.exec('COMMIT');
621
+ return raced;
622
+ }
623
+ const currentGap = this.getGap(gap.id);
624
+ if (currentGap.revision !== gap.revision || currentGap.inputDigest !== gap.inputDigest || currentGap.roi !== gap.roi
625
+ || currentGap.status !== 'open' || currentGap.candidateId !== gap.candidateId) {
626
+ throw new ControlPlaneStoreError('conflict', 'capability gap changed before plan creation');
627
+ }
628
+ this.#database.prepare('INSERT INTO gap_plan_claims (gap_id, plan_id, plan_kind, claimed_at) VALUES (?, ?, ?, ?)').run(gap.id, id, 'activation', now);
629
+ this.#database.prepare(`INSERT INTO activation_plans (id, plan_digest, gap_id, gap_snapshot_json, profile, candidate_json,
630
+ dossier_json, installation_id, ledger_id, ledger_path, dsh_home, target_path, executor_id, executor_version,
631
+ executor_path, executor_digest, status, revision, created_at,
632
+ expires_at, approval_json, activation_id, activation_fence, activation_lease_until, activation_target_existed, failure_code, updated_at)
633
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 'pending-approval', 1, ?, ?, NULL, NULL, 0, NULL, NULL, NULL, ?)`).run(id, planDigest, gap.id, JSON.stringify(gapSnapshot), profile, JSON.stringify(candidate), JSON.stringify(dossier), input.installationId, input.ledger.id, input.ledger.path, input.target.dshHome, input.target.profilePath, input.executor.id, input.executor.version, input.executor.path, input.executor.sha256, now, expiresAt, now);
634
+ this.#database.prepare(`UPDATE capability_gaps SET status = 'matched', candidate_id = ?, revision = revision + 1, updated_at = ? WHERE id = ? AND revision = ?`).run(candidate.id, now, gap.id, gap.revision);
635
+ const plan = this.getPlan(id);
636
+ const receipt = { idempotencyKey, operation: 'create-activation-plan', inputDigest, result: plan, createdAt: now };
637
+ this.#insertReceipt(receipt);
638
+ this.#database.exec('COMMIT');
639
+ return receipt;
640
+ }
641
+ catch (error) {
642
+ this.#database.exec('ROLLBACK');
643
+ throw error;
644
+ }
645
+ }
646
+ getPlan(id) {
647
+ const row = this.#database.prepare('SELECT * FROM activation_plans WHERE id = ?').get(id);
648
+ if (row === undefined)
649
+ throw new ControlPlaneStoreError('not-found', 'activation plan not found');
650
+ return activationFromRow(row);
651
+ }
652
+ createSourcePlan(input) {
653
+ const key = bounded(input.idempotencyKey, 'idempotencyKey', 160);
654
+ const name = bounded(input.name, 'name', 64);
655
+ if (!KEY.test(key) || !PLUGIN_NAME.test(name) || !COMMIT.test(input.baseCommit) || !DIGEST.test(input.generatorDigest)
656
+ || input.scope.length === 0 || input.scope.length > 32)
657
+ throw new ControlPlaneStoreError('invalid-input', 'source plan binding is invalid');
658
+ positiveInteger(input.ttlMs, 'ttlMs');
659
+ if (input.ttlMs < 60_000 || input.ttlMs > 86_400_000)
660
+ throw new ControlPlaneStoreError('invalid-input', 'ttlMs is invalid');
661
+ const scope = Object.freeze([...new Set(input.scope.map(value => bounded(value, 'scope', 500)))].sort());
662
+ if (controlPlaneDigest(scope) !== controlPlaneDigest(expectedSourceScope(name))) {
663
+ throw new ControlPlaneStoreError('invalid-input', 'source plan scope must bind the plugin tree and plugins catalog row exactly');
664
+ }
665
+ const requestBinding = { operation: 'create-source-plan', gapId: input.gapId, repository: input.repository,
666
+ worktree: input.worktree, baseCommit: input.baseCommit, name, generatorDigest: input.generatorDigest, scope, ttlMs: input.ttlMs };
667
+ const inputDigest = controlPlaneDigest(requestBinding);
668
+ const prior = this.#sourcePlanReceiptByKey(key, 'create-source-plan', inputDigest);
669
+ if (prior !== undefined)
670
+ return prior;
671
+ const gap = this.getGap(input.gapId);
672
+ if (gap.status !== 'open' || gap.candidateId !== undefined) {
673
+ throw new ControlPlaneStoreError('invalid-state', 'only an unreserved open gap can create a source plan');
674
+ }
675
+ const gapSnapshot = Object.freeze({ revision: gap.revision, inputDigest: gap.inputDigest, roi: gap.roi, capability: gap.capability });
676
+ const now = this.#now();
677
+ const id = `source-${randomUUID()}`;
678
+ const expiresAt = now + input.ttlMs;
679
+ const immutable = { schemaVersion: 1, kind: 'source', id, gapId: gap.id, gapSnapshot,
680
+ repository: input.repository, worktree: input.worktree, baseCommit: input.baseCommit, name,
681
+ generatorDigest: input.generatorDigest, scope, createdAt: now, expiresAt };
682
+ const digest = controlPlaneDigest(immutable);
683
+ this.#database.exec('BEGIN IMMEDIATE');
684
+ try {
685
+ this.#database.prepare('INSERT INTO gap_plan_claims (gap_id, plan_id, plan_kind, claimed_at) VALUES (?, ?, ?, ?)').run(gap.id, id, 'source', now);
686
+ this.#database.prepare(`INSERT INTO source_plans (id, plan_digest, gap_id, gap_snapshot_json, repository, worktree,
687
+ base_commit, plugin_name, generator_digest, scope_json, status, revision, created_at, expires_at, approval_json, updated_at)
688
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 'pending-approval', 1, ?, ?, NULL, ?)`).run(id, digest, gap.id, JSON.stringify(gapSnapshot), input.repository, input.worktree, input.baseCommit, name, input.generatorDigest, JSON.stringify(scope), now, expiresAt, now);
689
+ this.#database.prepare(`UPDATE capability_gaps SET status = 'matched', revision = revision + 1, updated_at = ? WHERE id = ? AND revision = ?`).run(now, gap.id, gap.revision);
690
+ const plan = this.getSourcePlan(id);
691
+ const receipt = { idempotencyKey: key, operation: 'create-source-plan', inputDigest, result: plan, createdAt: now };
692
+ this.#insertReceipt(receipt);
693
+ this.#database.exec('COMMIT');
694
+ return receipt;
695
+ }
696
+ catch (error) {
697
+ this.#database.exec('ROLLBACK');
698
+ throw error;
699
+ }
700
+ }
701
+ getSourcePlan(id) {
702
+ const row = this.#database.prepare('SELECT * FROM source_plans WHERE id = ?').get(id);
703
+ if (row === undefined)
704
+ throw new ControlPlaneStoreError('not-found', 'source plan not found');
705
+ return sourceFromRow(row);
706
+ }
707
+ async approve(input) {
708
+ return this.#approvePlan('activation', input);
709
+ }
710
+ async approveSource(input) {
711
+ return this.#approvePlan('source', input);
712
+ }
713
+ async #approvePlan(kind, input) {
714
+ const inputDigest = controlPlaneDigest({ operation: 'approve-plan', kind, planId: input.planId, expectedRevision: input.expectedRevision, receipt: input.receipt });
715
+ if (kind === 'source') {
716
+ const prior = this.#sourcePlanReceipt(input.idempotencyKey, 'approve-plan', inputDigest, input.planId);
717
+ if (prior !== undefined) {
718
+ if (prior.result.status !== 'approved' || prior.result.revision !== input.expectedRevision + 1
719
+ || controlPlaneDigest(prior.result.approval) !== controlPlaneDigest(projectedApproval(input.receipt))
720
+ || prior.result.sourceCheck !== undefined || prior.result.releaseAuthorization !== undefined || prior.result.release !== undefined) {
721
+ throw new ControlPlaneStoreError('invalid-state', 'stored source approval receipt is corrupt');
722
+ }
723
+ return prior;
724
+ }
725
+ }
726
+ else {
727
+ const prior = this.#activationPlanReceipt(input.idempotencyKey, 'approve-plan', inputDigest, input.planId);
728
+ if (prior !== undefined) {
729
+ if (prior.result.status !== 'approved' || prior.result.revision !== input.expectedRevision + 1
730
+ || controlPlaneDigest(prior.result.approval) !== controlPlaneDigest(projectedApproval(input.receipt))
731
+ || prior.result.activation !== undefined)
732
+ throw new ControlPlaneStoreError('invalid-state', 'stored activation approval receipt is corrupt');
733
+ return prior;
734
+ }
735
+ }
736
+ const plan = kind === 'activation' ? this.getPlan(input.planId) : this.getSourcePlan(input.planId);
737
+ const verified = await input.resolveAuthority(input.receipt).verify(input.receipt, plan);
738
+ if (plan.revision !== input.expectedRevision)
739
+ throw new ControlPlaneStoreError('conflict', 'plan revision conflict');
740
+ if (plan.status !== 'pending-approval')
741
+ throw new ControlPlaneStoreError('invalid-state', 'plan is not pending approval');
742
+ if (this.#now() > plan.expiresAt || verified.decision !== 'approved')
743
+ throw new ControlPlaneStoreError(verified.decision === 'approved' ? 'expired' : 'invalid-state', 'plan approval is not currently applicable');
744
+ const table = kind === 'activation' ? 'activation_plans' : 'source_plans';
745
+ const now = this.#now();
746
+ this.#database.exec('BEGIN IMMEDIATE');
747
+ try {
748
+ const statement = this.#database.prepare(`UPDATE ${table} SET status = 'approved', revision = revision + 1, approval_json = ?,
749
+ ${kind === 'activation' ? 'approval_receipt_json = ?,' : ''} updated_at = ?
750
+ WHERE id = ? AND status = 'pending-approval' AND revision = ? AND plan_digest = ?`);
751
+ const result = kind === 'activation'
752
+ ? statement.run(JSON.stringify(verified), JSON.stringify(input.receipt), now, plan.id, input.expectedRevision, plan.digest)
753
+ : statement.run(JSON.stringify(verified), now, plan.id, input.expectedRevision, plan.digest);
754
+ if (Number(result.changes) !== 1)
755
+ throw new ControlPlaneStoreError('conflict', 'plan changed while approval was being applied');
756
+ const output = kind === 'activation' ? this.getPlan(plan.id) : this.getSourcePlan(plan.id);
757
+ const receipt = { idempotencyKey: input.idempotencyKey, operation: 'approve-plan', inputDigest, result: output, createdAt: now };
758
+ this.#insertReceipt(receipt);
759
+ this.#database.exec('COMMIT');
760
+ return receipt;
761
+ }
762
+ catch (error) {
763
+ this.#database.exec('ROLLBACK');
764
+ throw error;
765
+ }
766
+ }
767
+ async claimActivation(input) {
768
+ const now = this.#now();
769
+ positiveInteger(input.leaseMs, 'leaseMs');
770
+ if (input.leaseMs < 5_000 || input.leaseMs > 300_000)
771
+ throw new ControlPlaneStoreError('invalid-input', 'leaseMs is invalid');
772
+ this.#database.exec('BEGIN IMMEDIATE');
773
+ try {
774
+ const row = this.#database.prepare('SELECT * FROM activation_plans WHERE id = ?').get(input.planId);
775
+ if (row === undefined)
776
+ throw new ControlPlaneStoreError('not-found', 'activation plan not found');
777
+ const plan = activationFromRow(row);
778
+ if (plan.revision !== input.expectedRevision)
779
+ throw new ControlPlaneStoreError('conflict', 'activation plan revision conflict');
780
+ if (row.approval_receipt_json === null || plan.approval === undefined) {
781
+ throw new ControlPlaneStoreError('invalid-state', 'activation approval signature is unavailable');
782
+ }
783
+ let approvalReceipt;
784
+ try {
785
+ approvalReceipt = parseApprovalReceipt(JSON.parse(row.approval_receipt_json));
786
+ }
787
+ catch {
788
+ throw new ControlPlaneStoreError('invalid-state', 'stored activation approval receipt is corrupt');
789
+ }
790
+ const verifiedApproval = await input.resolveApprovalAuthority(approvalReceipt).verify(approvalReceipt, plan);
791
+ if (controlPlaneDigest(verifiedApproval) !== controlPlaneDigest(plan.approval)) {
792
+ throw new ControlPlaneStoreError('conflict', 'activation approval changed during claim verification');
793
+ }
794
+ const recoverable = plan.status === 'staging' || plan.status === 'rollback-pending' || plan.status === 'commit-pending';
795
+ if (plan.status === 'approved') {
796
+ if (now > plan.expiresAt)
797
+ throw new ControlPlaneStoreError('expired', 'activation plan expired before its first claim');
798
+ const targetOwner = this.#database.prepare(`SELECT id FROM activation_plans WHERE target_path = ? AND id <> ? AND status IN (
799
+ 'staging', 'awaiting-reload', 'awaiting-readiness', 'awaiting-effect-blocked-replay', 'awaiting-shadow',
800
+ 'awaiting-canary', 'awaiting-soak', 'awaiting-health', 'commit-pending', 'rollback-pending') LIMIT 1`).get(plan.target.profilePath, plan.id);
801
+ if (targetOwner !== undefined)
802
+ throw new ControlPlaneStoreError('conflict', 'target profile already has an active activation');
803
+ }
804
+ else if (!(recoverable && Number(row.activation_lease_until ?? 0) < now))
805
+ throw new ControlPlaneStoreError('invalid-state', 'activation plan cannot be claimed');
806
+ const activationId = row.activation_id ?? `activation-${randomUUID()}`;
807
+ const status = plan.status === 'approved' ? 'staging' : plan.status;
808
+ const result = this.#database.prepare(`UPDATE activation_plans SET status = ?, revision = revision + 1, activation_id = ?,
809
+ activation_fence = activation_fence + 1, activation_lease_until = ?, updated_at = ? WHERE id = ? AND revision = ?`).run(status, activationId, now + input.leaseMs, now, plan.id, input.expectedRevision);
810
+ if (Number(result.changes) !== 1)
811
+ throw new ControlPlaneStoreError('conflict', 'activation plan changed while being claimed');
812
+ const claimed = this.getPlan(plan.id);
813
+ this.#database.exec('COMMIT');
814
+ return claimed;
815
+ }
816
+ catch (error) {
817
+ this.#database.exec('ROLLBACK');
818
+ throw error;
819
+ }
820
+ }
821
+ heartbeatActivation(input) {
822
+ const now = this.#now();
823
+ positiveInteger(input.leaseMs, 'leaseMs');
824
+ const result = this.#database.prepare(`UPDATE activation_plans SET activation_lease_until = ?, updated_at = ?
825
+ WHERE id = ? AND revision = ? AND activation_fence = ? AND status IN ('staging', 'rollback-pending', 'commit-pending') AND activation_lease_until >= ?`).run(now + input.leaseMs, now, input.planId, input.expectedRevision, input.fence, now);
826
+ if (Number(result.changes) !== 1)
827
+ throw new ControlPlaneStoreError('conflict', 'activation heartbeat lost its revision/fence/lease');
828
+ return this.getPlan(input.planId);
829
+ }
830
+ recordActivationTargetBaseline(input) {
831
+ const now = this.#now();
832
+ this.#database.exec('BEGIN IMMEDIATE');
833
+ try {
834
+ const row = this.#database.prepare('SELECT * FROM activation_plans WHERE id = ?').get(input.planId);
835
+ if (row === undefined)
836
+ throw new ControlPlaneStoreError('not-found', 'activation plan not found');
837
+ const plan = activationFromRow(row);
838
+ if (plan.revision !== input.expectedRevision || plan.activation?.fence !== input.fence || plan.status !== 'staging'
839
+ || Number(row.activation_lease_until ?? 0) < now)
840
+ throw new ControlPlaneStoreError('conflict', 'activation lost its claim before recording the target baseline');
841
+ if (row.activation_target_existed !== null && (row.activation_target_existed === 1) !== input.existed) {
842
+ throw new ControlPlaneStoreError('conflict', 'activation target baseline is immutable');
843
+ }
844
+ if (row.activation_target_existed === null) {
845
+ this.#database.prepare(`UPDATE activation_plans SET activation_target_existed = ?, updated_at = ?
846
+ WHERE id = ? AND revision = ? AND activation_fence = ? AND activation_target_existed IS NULL`).run(input.existed ? 1 : 0, now, plan.id, plan.revision, input.fence);
847
+ }
848
+ const result = this.getPlan(plan.id);
849
+ this.#database.exec('COMMIT');
850
+ return result;
851
+ }
852
+ catch (error) {
853
+ this.#database.exec('ROLLBACK');
854
+ throw error;
855
+ }
856
+ }
857
+ /**
858
+ * Hold SQLite's cross-process write lock for the complete command or
859
+ * destructive filesystem operation. A lease timeout is therefore never a
860
+ * licence for another worker to mutate the profile while this callback is
861
+ * still running: its claim must first acquire the same kernel-backed DB lock.
862
+ */
863
+ async withActivationFileSystemGuard(input, action) {
864
+ positiveInteger(input.leaseMs, 'leaseMs');
865
+ try {
866
+ this.#database.exec('BEGIN IMMEDIATE');
867
+ }
868
+ catch {
869
+ throw new ControlPlaneStoreError('conflict', 'activation filesystem mutex is held');
870
+ }
871
+ try {
872
+ const assertOwner = (requireLiveLease) => {
873
+ const row = this.#database.prepare('SELECT * FROM activation_plans WHERE id = ?').get(input.planId);
874
+ if (row === undefined)
875
+ throw new ControlPlaneStoreError('not-found', 'activation plan not found');
876
+ const plan = activationFromRow(row);
877
+ if (plan.revision !== input.expectedRevision || plan.activation?.fence !== input.fence || plan.status !== input.status
878
+ || (requireLiveLease && Number(row.activation_lease_until ?? 0) < this.#now()))
879
+ throw new ControlPlaneStoreError('conflict', 'activation no longer owns the filesystem fence');
880
+ return row;
881
+ };
882
+ assertOwner(true);
883
+ const started = this.#now();
884
+ this.#database.prepare('UPDATE activation_plans SET activation_lease_until = ?, updated_at = ? WHERE id = ?').run(started + input.leaseMs, started, input.planId);
885
+ const result = await action();
886
+ assertOwner(false);
887
+ const finished = this.#now();
888
+ this.#database.prepare('UPDATE activation_plans SET activation_lease_until = ?, updated_at = ? WHERE id = ?').run(finished + input.leaseMs, finished, input.planId);
889
+ this.#database.exec('COMMIT');
890
+ return result;
891
+ }
892
+ catch (error) {
893
+ this.#database.exec('ROLLBACK');
894
+ throw error;
895
+ }
896
+ }
897
+ /** Serialize lock-file lifecycle operations even after a plan became terminal. */
898
+ async withExclusiveWrite(action) {
899
+ try {
900
+ this.#database.exec('BEGIN IMMEDIATE');
901
+ }
902
+ catch {
903
+ throw new ControlPlaneStoreError('conflict', 'control-plane mutex is held');
904
+ }
905
+ try {
906
+ const result = await action();
907
+ this.#database.exec('COMMIT');
908
+ return result;
909
+ }
910
+ catch (error) {
911
+ this.#database.exec('ROLLBACK');
912
+ throw error;
913
+ }
914
+ }
915
+ assertActivationFence(input) {
916
+ const plan = this.getPlan(input.planId);
917
+ if (plan.revision !== input.expectedRevision || plan.activation?.fence !== input.fence || !input.statuses.includes(plan.status))
918
+ throw new ControlPlaneStoreError('conflict', 'activation no longer owns the exact revision/fence/status');
919
+ return plan;
920
+ }
921
+ prepareHostAttestationOperation(input) {
922
+ const now = this.#now();
923
+ this.#database.exec('BEGIN IMMEDIATE');
924
+ try {
925
+ const plan = this.getPlan(input.planId);
926
+ const expected = expectedAttestation[plan.status];
927
+ if (expected === undefined)
928
+ throw new ControlPlaneStoreError('invalid-state', 'activation is not awaiting a Host attestation operation');
929
+ if (plan.revision !== input.expectedRevision || plan.activation?.fence !== input.expectedFence) {
930
+ throw new ControlPlaneStoreError('conflict', 'Host attestation operation targets a stale activation revision/fence');
931
+ }
932
+ if (input.requirements.kind !== expected.phase)
933
+ throw new ControlPlaneStoreError('invalid-input', 'Host attestation requirements do not match the awaited phase');
934
+ if (!Number.isSafeInteger(input.receiptTtlMs) || input.receiptTtlMs < 1_000 || input.receiptTtlMs > 300_000) {
935
+ throw new ControlPlaneStoreError('invalid-input', 'Host attestation receipt TTL is invalid');
936
+ }
937
+ if (expected.phase === 'reload') {
938
+ const previous = this.#database.prepare(`SELECT max(attestation.host_generation) AS generation
939
+ FROM host_attestations AS attestation JOIN activation_plans AS activation ON activation.id = attestation.plan_id
940
+ WHERE activation.installation_id = ?`).get(plan.installationId);
941
+ if (input.requirements.kind !== 'reload' || input.requirements.previousHostGeneration !== (previous.generation ?? 0)) {
942
+ throw new ControlPlaneStoreError('conflict', 'reload operation does not bind the durable prior Host generation');
943
+ }
944
+ }
945
+ const operationId = `host-operation-${randomUUID()}`;
946
+ const request = { schemaVersion: 1, kind: 'dsh-host-attestation-request', operationId,
947
+ requestedAt: now, receiptTtlMs: input.receiptTtlMs, installationId: plan.installationId, ledger: plan.ledger, plan: { id: plan.id, digest: plan.digest },
948
+ activation: { id: plan.activation.id, fence: plan.activation.fence }, profile: { name: plan.profile, path: plan.target.profilePath },
949
+ issuer: input.issuer, phase: expected.phase, requirements: input.requirements };
950
+ const bindingDigest = controlPlaneDigest(requestBinding(request));
951
+ const priorRow = this.#database.prepare('SELECT * FROM host_attestation_operations WHERE plan_id = ? AND phase = ?')
952
+ .get(plan.id, expected.phase);
953
+ if (priorRow !== undefined) {
954
+ const prior = hostOperationFromRow(priorRow);
955
+ if (prior.bindingDigest !== bindingDigest)
956
+ throw new ControlPlaneStoreError('conflict', 'durable Host operation payload changed for the same phase');
957
+ this.#database.exec('COMMIT');
958
+ return prior;
959
+ }
960
+ this.#database.prepare(`INSERT INTO host_attestation_operations (plan_id, phase, operation_id, binding_digest,
961
+ request_digest, request_json, status, receipt_digest, receipt_json, created_at, completed_at, applied_at)
962
+ VALUES (?, ?, ?, ?, ?, ?, 'pending', NULL, NULL, ?, NULL, NULL)`).run(plan.id, expected.phase, operationId, bindingDigest, controlPlaneDigest(request), JSON.stringify(request), now);
963
+ const operation = this.getHostAttestationOperation(operationId);
964
+ this.#database.exec('COMMIT');
965
+ return operation;
966
+ }
967
+ catch (error) {
968
+ this.#database.exec('ROLLBACK');
969
+ throw error;
970
+ }
971
+ }
972
+ getHostAttestationOperation(operationId) {
973
+ const row = this.#database.prepare('SELECT * FROM host_attestation_operations WHERE operation_id = ?').get(operationId);
974
+ if (row === undefined)
975
+ throw new ControlPlaneStoreError('not-found', 'Host attestation operation not found');
976
+ return hostOperationFromRow(row);
977
+ }
978
+ latestHostGeneration(installationId) {
979
+ if (!UUID.test(installationId))
980
+ throw new ControlPlaneStoreError('invalid-input', 'installation id is invalid');
981
+ const row = this.#database.prepare(`SELECT max(attestation.host_generation) AS generation
982
+ FROM host_attestations AS attestation JOIN activation_plans AS activation ON activation.id = attestation.plan_id
983
+ WHERE activation.installation_id = ?`).get(installationId);
984
+ return row.generation ?? 0;
985
+ }
986
+ /**
987
+ * Keep the SQLite writer mutex for the complete external attestor call. The
988
+ * operation id was committed before this method, so process death retries the
989
+ * same request while concurrent workers cannot cause a second canary call.
990
+ */
991
+ async runHostAttestationOperation(input) {
992
+ try {
993
+ this.#database.exec('BEGIN IMMEDIATE');
994
+ }
995
+ catch {
996
+ throw new ControlPlaneStoreError('conflict', 'Host attestation single-flight is held');
997
+ }
998
+ try {
999
+ const operation = this.getHostAttestationOperation(input.operationId);
1000
+ const plan = this.getPlan(operation.planId);
1001
+ const expected = expectedAttestation[plan.status];
1002
+ if (plan.revision !== input.expectedRevision || plan.activation?.fence !== input.expectedFence
1003
+ || expected?.phase !== operation.phase || operation.requestDigest !== controlPlaneDigest(operation.request)) {
1004
+ throw new ControlPlaneStoreError('conflict', 'Host attestation operation lost its plan revision/fence/phase');
1005
+ }
1006
+ if (operation.receipt !== undefined) {
1007
+ this.#database.exec('COMMIT');
1008
+ return operation.receipt;
1009
+ }
1010
+ const receipt = await input.execute(operation.request);
1011
+ await input.resolveAuthority(receipt).verify(receipt, plan, operation.request);
1012
+ const now = this.#now();
1013
+ const result = this.#database.prepare(`UPDATE host_attestation_operations SET status = 'completed', receipt_digest = ?,
1014
+ receipt_json = ?, completed_at = ? WHERE operation_id = ? AND status = 'pending' AND request_digest = ?`).run(controlPlaneDigest(receipt), JSON.stringify(receipt), now, operation.operationId, operation.requestDigest);
1015
+ if (Number(result.changes) !== 1)
1016
+ throw new ControlPlaneStoreError('conflict', 'Host attestation operation completion lost its single-flight');
1017
+ this.#database.exec('COMMIT');
1018
+ return receipt;
1019
+ }
1020
+ catch (error) {
1021
+ this.#database.exec('ROLLBACK');
1022
+ throw error;
1023
+ }
1024
+ }
1025
+ advanceActivation(input) {
1026
+ const allowed = { staging: ['awaiting-reload', 'rollback-pending'],
1027
+ 'rollback-pending': ['rolled-back'], 'commit-pending': ['activated', 'rollback-pending'] };
1028
+ if (!allowed[input.from]?.includes(input.to))
1029
+ throw new ControlPlaneStoreError('invalid-input', 'invalid activation transition');
1030
+ const now = this.#now();
1031
+ this.#database.exec('BEGIN IMMEDIATE');
1032
+ try {
1033
+ const result = this.#database.prepare(`UPDATE activation_plans SET status = ?, revision = revision + 1,
1034
+ activation_lease_until = CASE
1035
+ WHEN ? IN ('rolled-back', 'activated', 'awaiting-reload') THEN NULL
1036
+ ELSE activation_lease_until END,
1037
+ failure_code = COALESCE(?, failure_code), updated_at = ?
1038
+ WHERE id = ? AND revision = ? AND activation_fence = ? AND status = ? AND activation_lease_until >= ?`).run(input.to, input.to, input.failureCode ?? null, now, input.planId, input.expectedRevision, input.fence, input.from, now);
1039
+ if (Number(result.changes) !== 1)
1040
+ throw new ControlPlaneStoreError('conflict', 'activation transition lost its CAS/fencing claim');
1041
+ const plan = this.getPlan(input.planId);
1042
+ if (input.to === 'rolled-back' || input.to === 'activated') {
1043
+ if (input.to === 'activated')
1044
+ this.#database.prepare(`UPDATE capability_gaps SET status = 'closed', revision = revision + 1, updated_at = ? WHERE id = ?`).run(now, plan.gapId);
1045
+ this.#finishActivation(plan, input.fence, now);
1046
+ }
1047
+ this.#database.exec('COMMIT');
1048
+ return plan;
1049
+ }
1050
+ catch (error) {
1051
+ this.#database.exec('ROLLBACK');
1052
+ throw error;
1053
+ }
1054
+ }
1055
+ async applyHostAttestation(input) {
1056
+ const inputDigest = controlPlaneDigest({ operation: 'host-attestation', planId: input.planId,
1057
+ expectedRevision: input.expectedRevision, expectedFence: input.expectedFence, receipt: input.receipt });
1058
+ const replay = this.#activationPlanReceipt(input.idempotencyKey, 'host-attestation', inputDigest, input.planId);
1059
+ if (replay !== undefined) {
1060
+ const transition = Object.values(expectedAttestation).find(item => item.phase === input.receipt.phase);
1061
+ const expectedStatus = input.receipt.outcome === 'passed' && transition !== undefined
1062
+ ? transition.next : 'rollback-pending';
1063
+ const expectedFailure = input.receipt.outcome === 'passed' ? undefined : 'host-attestation-failed';
1064
+ if (replay.result.status !== expectedStatus || replay.result.revision !== input.expectedRevision + 1
1065
+ || replay.result.activation?.id !== input.receipt.activationId || replay.result.activation.fence !== input.expectedFence
1066
+ || replay.result.activation.failureCode !== expectedFailure || replay.result.activation.updatedAt !== replay.createdAt) {
1067
+ throw new ControlPlaneStoreError('invalid-state', 'stored Host attestation operation receipt is corrupt');
1068
+ }
1069
+ return replay;
1070
+ }
1071
+ const plan = this.getPlan(input.planId);
1072
+ const expected = expectedAttestation[plan.status];
1073
+ if (expected === undefined)
1074
+ throw new ControlPlaneStoreError('invalid-state', 'activation is not awaiting a host attestation');
1075
+ if (plan.revision !== input.expectedRevision || plan.activation?.fence !== input.expectedFence)
1076
+ throw new ControlPlaneStoreError('conflict', 'host attestation targets a stale activation revision/fence');
1077
+ const operation = this.getHostAttestationOperation(input.receipt.operationId);
1078
+ if (operation.planId !== plan.id || operation.phase !== expected.phase || operation.status !== 'completed'
1079
+ || operation.receipt === undefined || controlPlaneDigest(operation.receipt) !== controlPlaneDigest(input.receipt)) {
1080
+ throw new ControlPlaneStoreError('conflict', 'Host attestation was not completed by the durable phase operation');
1081
+ }
1082
+ const verified = await input.resolveAuthority(input.receipt).verify(input.receipt, plan, operation.request);
1083
+ const now = this.#now();
1084
+ const nextStatus = verified.outcome === 'passed' ? expected.next : 'rollback-pending';
1085
+ this.#database.exec('BEGIN IMMEDIATE');
1086
+ try {
1087
+ const previousGeneration = this.#database.prepare(`SELECT max(attestation.host_generation) AS generation
1088
+ FROM host_attestations AS attestation JOIN activation_plans AS activation ON activation.id = attestation.plan_id
1089
+ WHERE activation.installation_id = ?`).get(plan.installationId);
1090
+ if (previousGeneration.generation !== null && verified.hostGeneration < previousGeneration.generation)
1091
+ throw new ControlPlaneStoreError('conflict', 'host generation regressed');
1092
+ this.#database.prepare(`INSERT INTO host_attestations (plan_id, phase, receipt_id, receipt_digest, receipt_json, host_generation, created_at)
1093
+ VALUES (?, ?, ?, ?, ?, ?, ?)`).run(plan.id, verified.phase, verified.receiptId, controlPlaneDigest(input.receipt), JSON.stringify(verified), verified.hostGeneration, now);
1094
+ const result = this.#database.prepare(`UPDATE activation_plans SET status = ?, revision = revision + 1,
1095
+ activation_lease_until = NULL,
1096
+ failure_code = CASE WHEN ? = 'rollback-pending' THEN 'host-attestation-failed' ELSE failure_code END, updated_at = ?
1097
+ WHERE id = ? AND status = ? AND revision = ? AND activation_fence = ?`).run(nextStatus, nextStatus, now, plan.id, plan.status, plan.revision, input.expectedFence);
1098
+ if (Number(result.changes) !== 1)
1099
+ throw new ControlPlaneStoreError('conflict', 'host attestation lost its plan CAS');
1100
+ const applied = this.#database.prepare(`UPDATE host_attestation_operations SET status = 'applied', applied_at = ?
1101
+ WHERE operation_id = ? AND status = 'completed' AND receipt_digest = ?`).run(now, operation.operationId, controlPlaneDigest(input.receipt));
1102
+ if (Number(applied.changes) !== 1)
1103
+ throw new ControlPlaneStoreError('conflict', 'Host attestation operation lost its apply CAS');
1104
+ const output = this.getPlan(plan.id);
1105
+ const operationReceipt = { idempotencyKey: input.idempotencyKey, operation: 'host-attestation', inputDigest, result: output, createdAt: now };
1106
+ this.#insertReceipt(operationReceipt);
1107
+ this.#database.exec('COMMIT');
1108
+ return operationReceipt;
1109
+ }
1110
+ catch (error) {
1111
+ this.#database.exec('ROLLBACK');
1112
+ throw error;
1113
+ }
1114
+ }
1115
+ #finishActivation(plan, fence, now) {
1116
+ const activationId = plan.activation?.id;
1117
+ if (activationId === undefined)
1118
+ throw new ControlPlaneStoreError('invalid-state', 'terminal activation has no identity');
1119
+ const inputDigest = controlPlaneDigest({ planId: plan.id, planDigest: plan.digest, activationId, fence, status: plan.status, failureCode: plan.activation?.failureCode });
1120
+ this.#insertReceipt({ idempotencyKey: `activation:${activationId}`, operation: 'activate-plan', inputDigest, result: plan, createdAt: now });
1121
+ if (plan.status === 'rolled-back') {
1122
+ this.#database.prepare('DELETE FROM gap_plan_claims WHERE gap_id = ? AND plan_id = ?').run(plan.gapId, plan.id);
1123
+ this.#database.prepare(`UPDATE capability_gaps SET status = 'open', candidate_id = NULL, revision = revision + 1, updated_at = ? WHERE id = ?`).run(now, plan.gapId);
1124
+ }
1125
+ }
1126
+ beginSourceChecks(input) {
1127
+ const now = this.#now();
1128
+ const result = this.#database.prepare(`UPDATE source_plans SET status = 'running-local-checks', revision = revision + 1, updated_at = ?
1129
+ WHERE id = ? AND revision = ? AND status = 'approved'`).run(now, input.planId, input.expectedRevision);
1130
+ if (Number(result.changes) !== 1)
1131
+ throw new ControlPlaneStoreError('conflict', 'source plan changed before local checks');
1132
+ return this.getSourcePlan(input.planId);
1133
+ }
1134
+ finishSourceChecks(input) {
1135
+ const now = this.#now();
1136
+ const status = input.succeeded ? 'ready-for-human-review' : 'local-checks-failed';
1137
+ if (input.succeeded && (!DIGEST.test(input.checkedTreeDigest ?? '') || !DIGEST.test(input.checkedPatchDigest ?? ''))) {
1138
+ throw new ControlPlaneStoreError('invalid-input', 'successful source checks require exact tree and patch digests');
1139
+ }
1140
+ if (!input.succeeded && (input.checkedTreeDigest !== undefined || input.checkedPatchDigest !== undefined)) {
1141
+ throw new ControlPlaneStoreError('invalid-input', 'failed source checks cannot carry successful check evidence');
1142
+ }
1143
+ this.#database.exec('BEGIN IMMEDIATE');
1144
+ try {
1145
+ const result = this.#database.prepare(`UPDATE source_plans SET status = ?, revision = revision + 1,
1146
+ checked_tree_digest = ?, checked_patch_digest = ?, checked_at = ?, updated_at = ?
1147
+ WHERE id = ? AND revision = ? AND status = 'running-local-checks'`).run(status, input.checkedTreeDigest ?? null, input.checkedPatchDigest ?? null, input.succeeded ? now : null, now, input.planId, input.expectedRevision);
1148
+ if (Number(result.changes) !== 1)
1149
+ throw new ControlPlaneStoreError('conflict', 'source plan changed while local checks ran');
1150
+ const output = this.getSourcePlan(input.planId);
1151
+ if (!input.succeeded) {
1152
+ this.#database.prepare('DELETE FROM gap_plan_claims WHERE gap_id = ? AND plan_id = ?').run(output.gapId, output.id);
1153
+ this.#database.prepare(`UPDATE capability_gaps SET status = 'open', revision = revision + 1, updated_at = ? WHERE id = ?`).run(now, output.gapId);
1154
+ }
1155
+ const digest = controlPlaneDigest({ planId: output.id, planDigest: output.digest, status, sourceCheck: output.sourceCheck });
1156
+ this.#insertReceipt({ idempotencyKey: `source-checks:${output.id}`, operation: 'source-local-checks', inputDigest: digest, result: output, createdAt: now });
1157
+ this.#database.exec('COMMIT');
1158
+ return output;
1159
+ }
1160
+ catch (error) {
1161
+ this.#database.exec('ROLLBACK');
1162
+ throw error;
1163
+ }
1164
+ }
1165
+ async startSourceRelease(input) {
1166
+ const key = bounded(input.idempotencyKey, 'idempotencyKey', 160);
1167
+ if (!KEY.test(key))
1168
+ throw new ControlPlaneStoreError('invalid-input', 'idempotencyKey has invalid syntax');
1169
+ const inputDigest = controlPlaneDigest({ operation: 'start-source-release', planId: input.planId,
1170
+ expectedRevision: input.expectedRevision, authorization: input.authorization });
1171
+ const replay = this.#sourcePlanReceipt(key, 'start-source-release', inputDigest, input.planId);
1172
+ if (replay !== undefined) {
1173
+ let verifiedAuthorization;
1174
+ try {
1175
+ const parsed = parseSourceReleaseAuthorization(input.authorization);
1176
+ verifiedAuthorization = { ...parsed, signatureDigest: createHash('sha256')
1177
+ .update(Buffer.from(parsed.signature, 'base64')).digest('hex') };
1178
+ }
1179
+ catch {
1180
+ throw new ControlPlaneStoreError('invalid-state', 'source release replay authorization is corrupt');
1181
+ }
1182
+ if (replay.result.status !== 'awaiting-pr' || replay.result.revision !== input.expectedRevision + 1
1183
+ || replay.result.release?.fence !== 1 || replay.result.release.failureCode !== undefined
1184
+ || replay.result.release.failurePhase !== undefined || replay.result.release.updatedAt !== replay.createdAt
1185
+ || controlPlaneDigest(replay.result.releaseAuthorization) !== controlPlaneDigest(verifiedAuthorization)) {
1186
+ throw new ControlPlaneStoreError('invalid-state', 'stored source release start receipt is corrupt');
1187
+ }
1188
+ return replay;
1189
+ }
1190
+ const plan = this.getSourcePlan(input.planId);
1191
+ if (plan.revision !== input.expectedRevision)
1192
+ throw new ControlPlaneStoreError('conflict', 'source release authorization targets a stale plan revision');
1193
+ if (plan.status !== 'ready-for-human-review' || plan.sourceCheck === undefined) {
1194
+ throw new ControlPlaneStoreError('invalid-state', 'source release requires completed checks and fresh human review');
1195
+ }
1196
+ const verified = await input.resolveAuthority(input.authorization).verify(input.authorization, plan);
1197
+ const now = this.#now();
1198
+ if (now > plan.expiresAt || now > verified.expiresAt)
1199
+ throw new ControlPlaneStoreError('expired', 'source release authorization is no longer applicable');
1200
+ this.#database.exec('BEGIN IMMEDIATE');
1201
+ try {
1202
+ const releaseId = `release-${randomUUID()}`;
1203
+ const result = this.#database.prepare(`UPDATE source_plans SET status = 'awaiting-pr', revision = revision + 1,
1204
+ release_authorization_json = ?, release_authorization_digest = ?, release_id = ?, release_fence = release_fence + 1,
1205
+ release_failure_phase = NULL, release_failure_code = NULL, updated_at = ?
1206
+ WHERE id = ? AND status = 'ready-for-human-review' AND revision = ? AND plan_digest = ?
1207
+ AND checked_tree_digest = ? AND checked_patch_digest = ? AND release_id IS NULL`).run(JSON.stringify(verified), controlPlaneDigest(verified), releaseId, now, plan.id, input.expectedRevision, plan.digest, plan.sourceCheck.treeDigest, plan.sourceCheck.patchDigest);
1208
+ if (Number(result.changes) !== 1)
1209
+ throw new ControlPlaneStoreError('conflict', 'source plan changed while release authorization was applied');
1210
+ const output = this.getSourcePlan(plan.id);
1211
+ const operationReceipt = { idempotencyKey: key, operation: 'start-source-release', inputDigest, result: output, createdAt: now };
1212
+ this.#insertReceipt(operationReceipt);
1213
+ this.#database.exec('COMMIT');
1214
+ return operationReceipt;
1215
+ }
1216
+ catch (error) {
1217
+ this.#database.exec('ROLLBACK');
1218
+ throw error;
1219
+ }
1220
+ }
1221
+ async prepareSourceReleaseOperation(input) {
1222
+ const now = this.#now();
1223
+ this.#database.exec('BEGIN IMMEDIATE');
1224
+ try {
1225
+ const plan = this.getSourcePlan(input.planId);
1226
+ const expected = expectedSourceRelease(plan.status);
1227
+ if (expected === undefined)
1228
+ throw new ControlPlaneStoreError('invalid-state', 'source plan is not awaiting a release operation');
1229
+ if (plan.revision !== input.expectedRevision || plan.release?.fence !== input.expectedFence) {
1230
+ throw new ControlPlaneStoreError('conflict', 'source release operation targets a stale revision/fence');
1231
+ }
1232
+ if (plan.releaseAuthorization === undefined || plan.sourceCheck === undefined) {
1233
+ throw new ControlPlaneStoreError('invalid-state', 'source release operation has no durable post-check authorization');
1234
+ }
1235
+ await reverifySourceReleaseAuthorization(plan.releaseAuthorization, plan, input.resolveAuthorizationAuthority);
1236
+ this.#assertReleaseEnvironment(input, expected.phase);
1237
+ const previous = this.#previousReleaseEvidence(plan, expected.phase);
1238
+ const priorRow = this.#database.prepare(`SELECT * FROM source_release_operations WHERE plan_id = ? AND phase = ?
1239
+ ORDER BY attempt DESC LIMIT 1`).get(plan.id, expected.phase);
1240
+ const attempt = priorRow !== undefined && priorRow.release_fence === plan.release.fence
1241
+ ? priorRow.attempt : (priorRow?.attempt ?? 0) + 1;
1242
+ if (priorRow !== undefined && priorRow.release_fence === plan.release.fence) {
1243
+ const prior = sourceReleaseOperationFromRow(priorRow);
1244
+ const expectedRequest = this.#sourceReleaseRequest(plan, expected.phase, prior.operationId, prior.attempt, prior.request.requestedAt, input, previous);
1245
+ if (prior.bindingDigest !== controlPlaneDigest(releaseRequestBinding(expectedRequest))) {
1246
+ throw new ControlPlaneStoreError('conflict', 'durable release operation payload changed for the same phase/fence');
1247
+ }
1248
+ this.#database.exec('COMMIT');
1249
+ return prior;
1250
+ }
1251
+ const operationId = `release-operation-${randomUUID()}`;
1252
+ const request = this.#sourceReleaseRequest(plan, expected.phase, operationId, attempt, now, input, previous);
1253
+ const bindingDigest = controlPlaneDigest(releaseRequestBinding(request));
1254
+ this.#database.prepare(`INSERT INTO source_release_operations (plan_id, phase, release_id, release_fence, attempt,
1255
+ operation_id, binding_digest, request_digest, request_json, status, receipt_digest, receipt_json, created_at, completed_at, applied_at)
1256
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, 'pending', NULL, NULL, ?, NULL, NULL)`).run(plan.id, expected.phase, plan.release.id, plan.release.fence, attempt, operationId, bindingDigest, controlPlaneDigest(request), JSON.stringify(request), now);
1257
+ const operation = this.getSourceReleaseOperation(operationId);
1258
+ this.#database.exec('COMMIT');
1259
+ return operation;
1260
+ }
1261
+ catch (error) {
1262
+ this.#database.exec('ROLLBACK');
1263
+ throw error;
1264
+ }
1265
+ }
1266
+ getSourceReleaseOperation(operationId) {
1267
+ const row = this.#database.prepare('SELECT * FROM source_release_operations WHERE operation_id = ?').get(operationId);
1268
+ if (row === undefined)
1269
+ throw new ControlPlaneStoreError('not-found', 'source release operation not found');
1270
+ return sourceReleaseOperationFromRow(row);
1271
+ }
1272
+ sourceReleaseCandidate(planId) {
1273
+ const plan = this.getSourcePlan(planId);
1274
+ if (plan.status !== 'release-complete') {
1275
+ throw new ControlPlaneStoreError('invalid-state', 'source candidate is not admitted until release completes');
1276
+ }
1277
+ const row = this.#database.prepare(`SELECT * FROM source_release_operations WHERE plan_id = ? AND phase = 'build'
1278
+ AND status = 'applied' ORDER BY attempt DESC LIMIT 1`).get(plan.id);
1279
+ if (row === undefined)
1280
+ throw new ControlPlaneStoreError('invalid-state', 'source release has no applied build artifact');
1281
+ const operation = sourceReleaseOperationFromRow(row);
1282
+ const evidence = operation.receipt?.evidence;
1283
+ if (operation.receipt?.outcome !== 'passed' || evidence?.kind !== 'build') {
1284
+ throw new ControlPlaneStoreError('invalid-state', 'source release build evidence is not successful');
1285
+ }
1286
+ const authorization = plan.releaseAuthorization;
1287
+ if (authorization === undefined)
1288
+ throw new ControlPlaneStoreError('invalid-state', 'source release has no verified authorization');
1289
+ const artifact = releaseArtifact(evidence);
1290
+ return parseCatalog({ schemaVersion: 1, entries: [{ id: artifact.candidateId, package: artifact.packageName,
1291
+ version: artifact.packageVersion, integrity: artifact.tarballIntegrity, dshBaseline: artifact.dshBaseline,
1292
+ registry: { id: authorization.releasePolicy.registryId, locator: authorization.releasePolicy.registryLocator,
1293
+ reference: authorization.releasePolicy.registryReference },
1294
+ capabilities: artifact.capabilities, authorities: artifact.authorities, requires: artifact.requires }] }).entries[0];
1295
+ }
1296
+ async runSourceReleaseOperation(input) {
1297
+ try {
1298
+ this.#database.exec('BEGIN IMMEDIATE');
1299
+ }
1300
+ catch {
1301
+ throw new ControlPlaneStoreError('conflict', 'source release single-flight is held');
1302
+ }
1303
+ try {
1304
+ const operation = this.getSourceReleaseOperation(input.operationId);
1305
+ const plan = this.getSourcePlan(operation.planId);
1306
+ const expected = expectedSourceRelease(plan.status);
1307
+ if (plan.revision !== input.expectedRevision || plan.release?.fence !== input.expectedFence
1308
+ || operation.fence !== input.expectedFence || expected?.phase !== operation.phase
1309
+ || operation.requestDigest !== controlPlaneDigest(operation.request)) {
1310
+ throw new ControlPlaneStoreError('conflict', 'source release operation lost its plan revision/fence/phase');
1311
+ }
1312
+ if (operation.receipt !== undefined) {
1313
+ this.#database.exec('COMMIT');
1314
+ return operation.receipt;
1315
+ }
1316
+ await reverifySourceReleaseAuthorization(operation.request.authorization, plan, input.resolveAuthorizationAuthority);
1317
+ const receipt = await input.execute(operation.request);
1318
+ await input.resolveAuthority(receipt).verify(receipt, plan, operation.request);
1319
+ const now = this.#now();
1320
+ const result = this.#database.prepare(`UPDATE source_release_operations SET status = 'completed', receipt_digest = ?,
1321
+ receipt_json = ?, completed_at = ? WHERE operation_id = ? AND status = 'pending' AND request_digest = ?
1322
+ AND release_fence = ?`).run(controlPlaneDigest(receipt), JSON.stringify(receipt), now, operation.operationId, operation.requestDigest, input.expectedFence);
1323
+ if (Number(result.changes) !== 1)
1324
+ throw new ControlPlaneStoreError('conflict', 'source release completion lost its single-flight');
1325
+ this.#database.exec('COMMIT');
1326
+ return receipt;
1327
+ }
1328
+ catch (error) {
1329
+ this.#database.exec('ROLLBACK');
1330
+ throw error;
1331
+ }
1332
+ }
1333
+ async applySourceRelease(input) {
1334
+ const key = bounded(input.idempotencyKey, 'idempotencyKey', 160);
1335
+ if (!KEY.test(key))
1336
+ throw new ControlPlaneStoreError('invalid-input', 'idempotencyKey has invalid syntax');
1337
+ const inputDigest = controlPlaneDigest({ operation: 'source-release', planId: input.planId, expectedRevision: input.expectedRevision,
1338
+ expectedFence: input.expectedFence, receipt: input.receipt });
1339
+ const replay = this.#sourcePlanReceipt(key, 'source-release', inputDigest, input.planId);
1340
+ if (replay !== undefined) {
1341
+ const expectedStatus = input.receipt.outcome === 'passed'
1342
+ ? Object.values(expectedRelease).find(item => item.phase === input.receipt.phase)?.next ?? 'release-failed'
1343
+ : input.receipt.outcome === 'ambiguous' ? 'publish-ambiguous' : 'release-failed';
1344
+ const expectedFailure = input.receipt.outcome === 'failed' && input.receipt.evidence.kind === 'failure'
1345
+ ? `${input.receipt.evidence.code}${input.receipt.evidence.remoteState === 'unchanged' ? '' : `:${input.receipt.evidence.remoteState}`}`
1346
+ : input.receipt.outcome === 'ambiguous' ? 'publish-ambiguous' : undefined;
1347
+ if (replay.result.status !== expectedStatus || replay.result.revision !== input.expectedRevision + 1
1348
+ || replay.result.release?.fence !== input.expectedFence || replay.result.release.failureCode !== expectedFailure
1349
+ || replay.result.release.failurePhase !== (input.receipt.outcome === 'passed' ? undefined : input.receipt.phase)
1350
+ || replay.result.release.updatedAt !== replay.createdAt) {
1351
+ throw new ControlPlaneStoreError('invalid-state', 'stored source release apply receipt is corrupt');
1352
+ }
1353
+ return replay;
1354
+ }
1355
+ const plan = this.getSourcePlan(input.planId);
1356
+ const expected = expectedSourceRelease(plan.status);
1357
+ if (expected === undefined)
1358
+ throw new ControlPlaneStoreError('invalid-state', 'source plan is not awaiting a release receipt');
1359
+ if (plan.revision !== input.expectedRevision || plan.release?.fence !== input.expectedFence) {
1360
+ throw new ControlPlaneStoreError('conflict', 'source release receipt targets a stale revision/fence');
1361
+ }
1362
+ const operation = this.getSourceReleaseOperation(input.receipt.operationId);
1363
+ if (operation.planId !== plan.id || operation.phase !== expected.phase || operation.fence !== input.expectedFence
1364
+ || operation.status !== 'completed' || operation.receipt === undefined
1365
+ || controlPlaneDigest(operation.receipt) !== controlPlaneDigest(input.receipt)) {
1366
+ throw new ControlPlaneStoreError('conflict', 'release receipt was not completed by the durable fenced phase operation');
1367
+ }
1368
+ const verified = await input.resolveAuthority(input.receipt).verify(input.receipt, plan, operation.request);
1369
+ let nextStatus;
1370
+ let failurePhase = null;
1371
+ let failureCode = null;
1372
+ if (verified.outcome === 'passed')
1373
+ nextStatus = expected.next;
1374
+ else if (verified.outcome === 'ambiguous') {
1375
+ if (expected.phase !== 'publish')
1376
+ throw new ControlPlaneStoreError('invalid-state', 'only publish may enter ambiguous state');
1377
+ nextStatus = 'publish-ambiguous';
1378
+ failurePhase = 'publish';
1379
+ failureCode = 'publish-ambiguous';
1380
+ }
1381
+ else {
1382
+ nextStatus = 'release-failed';
1383
+ failurePhase = expected.phase;
1384
+ failureCode = verified.evidence.kind === 'failure'
1385
+ ? `${verified.evidence.code}${verified.evidence.remoteState === 'unchanged' ? '' : `:${verified.evidence.remoteState}`}` : 'release-failed';
1386
+ }
1387
+ const now = this.#now();
1388
+ this.#database.exec('BEGIN IMMEDIATE');
1389
+ try {
1390
+ const result = this.#database.prepare(`UPDATE source_plans SET status = ?, revision = revision + 1,
1391
+ release_failure_phase = ?, release_failure_code = ?, updated_at = ?
1392
+ WHERE id = ? AND status = ? AND revision = ? AND release_id = ? AND release_fence = ?`).run(nextStatus, failurePhase, failureCode, now, plan.id, plan.status, plan.revision, plan.release.id, input.expectedFence);
1393
+ if (Number(result.changes) !== 1)
1394
+ throw new ControlPlaneStoreError('conflict', 'source release receipt lost its plan CAS');
1395
+ const applied = this.#database.prepare(`UPDATE source_release_operations SET status = 'applied', applied_at = ?
1396
+ WHERE operation_id = ? AND status = 'completed' AND receipt_digest = ? AND release_fence = ?`).run(now, operation.operationId, controlPlaneDigest(input.receipt), input.expectedFence);
1397
+ if (Number(applied.changes) !== 1)
1398
+ throw new ControlPlaneStoreError('conflict', 'source release operation lost its apply CAS');
1399
+ const output = this.getSourcePlan(plan.id);
1400
+ if (nextStatus === 'release-complete') {
1401
+ const candidateId = verified.evidence.kind === 'catalog-admission' ? verified.evidence.candidate.id : undefined;
1402
+ if (candidateId === undefined)
1403
+ throw new ControlPlaneStoreError('invalid-state', 'release completion has no admitted candidate');
1404
+ this.#database.prepare(`UPDATE capability_gaps SET status = 'open', candidate_id = ?, revision = revision + 1, updated_at = ?
1405
+ WHERE id = ? AND status = 'matched'`).run(candidateId, now, output.gapId);
1406
+ this.#database.prepare('DELETE FROM gap_plan_claims WHERE gap_id = ? AND plan_id = ?').run(output.gapId, output.id);
1407
+ }
1408
+ else if (nextStatus === 'release-failed' && verified.evidence.kind === 'failure'
1409
+ && verified.evidence.remoteState === 'unchanged') {
1410
+ this.#database.prepare('DELETE FROM gap_plan_claims WHERE gap_id = ? AND plan_id = ?').run(output.gapId, output.id);
1411
+ this.#database.prepare(`UPDATE capability_gaps SET status = 'open', candidate_id = NULL, revision = revision + 1, updated_at = ?
1412
+ WHERE id = ? AND status = 'matched'`).run(now, output.gapId);
1413
+ }
1414
+ const operationReceipt = { idempotencyKey: key, operation: 'source-release', inputDigest, result: output, createdAt: now };
1415
+ this.#insertReceipt(operationReceipt);
1416
+ this.#database.exec('COMMIT');
1417
+ return operationReceipt;
1418
+ }
1419
+ catch (error) {
1420
+ this.#database.exec('ROLLBACK');
1421
+ throw error;
1422
+ }
1423
+ }
1424
+ async prepareSourcePublishReconciliation(input) {
1425
+ const now = this.#now();
1426
+ this.#database.exec('BEGIN IMMEDIATE');
1427
+ try {
1428
+ const plan = this.getSourcePlan(input.planId);
1429
+ if (plan.status !== 'publish-ambiguous' || plan.revision !== input.expectedRevision || plan.release?.fence !== input.expectedFence) {
1430
+ throw new ControlPlaneStoreError('conflict', 'publish reconciliation targets a stale ambiguous release');
1431
+ }
1432
+ if (plan.releaseAuthorization === undefined)
1433
+ throw new ControlPlaneStoreError('invalid-state', 'publish reconciliation lacks release authorization');
1434
+ await reverifySourceReleaseAuthorization(plan.releaseAuthorization, plan, input.resolveAuthorizationAuthority);
1435
+ this.#assertReconciliationEnvironment(input);
1436
+ const publishRow = this.#database.prepare(`SELECT * FROM source_release_operations WHERE plan_id = ? AND phase = 'publish'
1437
+ AND release_fence = ? ORDER BY attempt DESC LIMIT 1`).get(plan.id, input.expectedFence);
1438
+ if (publishRow === undefined)
1439
+ throw new ControlPlaneStoreError('invalid-state', 'ambiguous release has no durable publish operation');
1440
+ const publish = sourceReleaseOperationFromRow(publishRow);
1441
+ if (publish.status !== 'applied' || publish.receipt?.outcome !== 'ambiguous' || publish.receipt.evidence.kind !== 'publish-ambiguity') {
1442
+ throw new ControlPlaneStoreError('invalid-state', 'publish reconciliation has no durable ambiguity evidence');
1443
+ }
1444
+ if (publish.request.phase !== 'publish')
1445
+ throw new ControlPlaneStoreError('invalid-state', 'ambiguous operation is not publish');
1446
+ const priorRow = this.#database.prepare(`SELECT * FROM source_publish_reconciliations WHERE plan_id = ?
1447
+ ORDER BY attempt DESC LIMIT 1`).get(plan.id);
1448
+ const reusable = priorRow?.release_fence === plan.release.fence && priorRow.status !== 'applied';
1449
+ const attempt = reusable ? priorRow.attempt : (priorRow?.attempt ?? 0) + 1;
1450
+ const operationId = reusable ? priorRow.operation_id : `publish-reconciliation-${randomUUID()}`;
1451
+ const requestedAt = reusable ? priorRow.created_at : now;
1452
+ const request = { schemaVersion: 1, kind: 'dsh-source-publish-reconciliation-request',
1453
+ operationId, attempt, requestedAt, receiptTtlMs: input.receiptTtlMs, installationId: input.installationId, ledger: input.ledger,
1454
+ plan: { id: plan.id, digest: plan.digest, revision: plan.revision }, release: { id: plan.release.id, fence: plan.release.fence },
1455
+ authorization: plan.releaseAuthorization, adapter: input.adapter, registry: input.registry,
1456
+ ambiguousPublish: { operationId: publish.operationId, receiptId: publish.receipt.receiptId,
1457
+ receiptDigest: controlPlaneDigest(publish.receipt), evidenceDigest: publish.receipt.evidenceDigest },
1458
+ artifact: { packageName: publish.request.input.artifact.packageName, packageVersion: publish.request.input.artifact.packageVersion,
1459
+ tarballSha256: publish.request.input.artifact.tarballSha256, tarballIntegrity: publish.request.input.artifact.tarballIntegrity },
1460
+ expectedArtifactStatementDigest: publish.request.input.artifactStatementDigest,
1461
+ expectedArtifactSignatureDigest: createHash('sha256').update(Buffer.from(publish.request.input.artifactSignature, 'base64')).digest('hex'),
1462
+ expectedRegistryReference: plan.releaseAuthorization.releasePolicy.registryReference };
1463
+ const parsed = parseSourcePublishReconciliationRequest(request);
1464
+ const bindingDigest = controlPlaneDigest(reconciliationBinding(parsed));
1465
+ if (reusable && priorRow !== undefined) {
1466
+ const prior = sourcePublishReconciliationFromRow(priorRow);
1467
+ if (prior.bindingDigest !== bindingDigest)
1468
+ throw new ControlPlaneStoreError('conflict', 'durable publish reconciliation payload changed');
1469
+ this.#database.exec('COMMIT');
1470
+ return prior;
1471
+ }
1472
+ this.#database.prepare(`INSERT INTO source_publish_reconciliations (plan_id, release_id, release_fence, attempt, operation_id,
1473
+ binding_digest, request_digest, request_json, status, receipt_digest, receipt_json, created_at, completed_at, applied_at)
1474
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, 'pending', NULL, NULL, ?, NULL, NULL)`).run(plan.id, plan.release.id, plan.release.fence, attempt, operationId, bindingDigest, controlPlaneDigest(parsed), JSON.stringify(parsed), now);
1475
+ const operation = this.getSourcePublishReconciliationOperation(operationId);
1476
+ this.#database.exec('COMMIT');
1477
+ return operation;
1478
+ }
1479
+ catch (error) {
1480
+ this.#database.exec('ROLLBACK');
1481
+ throw error;
1482
+ }
1483
+ }
1484
+ getSourcePublishReconciliationOperation(operationId) {
1485
+ const row = this.#database.prepare('SELECT * FROM source_publish_reconciliations WHERE operation_id = ?').get(operationId);
1486
+ if (row === undefined)
1487
+ throw new ControlPlaneStoreError('not-found', 'publish reconciliation operation not found');
1488
+ return sourcePublishReconciliationFromRow(row);
1489
+ }
1490
+ async runSourcePublishReconciliation(input) {
1491
+ try {
1492
+ this.#database.exec('BEGIN IMMEDIATE');
1493
+ }
1494
+ catch {
1495
+ throw new ControlPlaneStoreError('conflict', 'publish reconciliation single-flight is held');
1496
+ }
1497
+ try {
1498
+ const operation = this.getSourcePublishReconciliationOperation(input.operationId);
1499
+ const plan = this.getSourcePlan(operation.planId);
1500
+ if (plan.status !== 'publish-ambiguous' || plan.revision !== input.expectedRevision || plan.release?.fence !== input.expectedFence
1501
+ || operation.fence !== input.expectedFence)
1502
+ throw new ControlPlaneStoreError('conflict', 'publish reconciliation lost its revision/fence');
1503
+ if (operation.receipt !== undefined) {
1504
+ this.#database.exec('COMMIT');
1505
+ return operation.receipt;
1506
+ }
1507
+ await reverifySourceReleaseAuthorization(operation.request.authorization, plan, input.resolveAuthorizationAuthority);
1508
+ const receipt = await input.execute(operation.request);
1509
+ await input.resolveAuthority(receipt).verify(receipt, plan, operation.request);
1510
+ const now = this.#now();
1511
+ const result = this.#database.prepare(`UPDATE source_publish_reconciliations SET status = 'completed',
1512
+ receipt_digest = ?, receipt_json = ?, completed_at = ? WHERE operation_id = ? AND status = 'pending' AND request_digest = ?`).run(controlPlaneDigest(receipt), JSON.stringify(receipt), now, operation.operationId, operation.requestDigest);
1513
+ if (Number(result.changes) !== 1)
1514
+ throw new ControlPlaneStoreError('conflict', 'publish reconciliation completion lost single-flight');
1515
+ this.#database.exec('COMMIT');
1516
+ return receipt;
1517
+ }
1518
+ catch (error) {
1519
+ this.#database.exec('ROLLBACK');
1520
+ throw error;
1521
+ }
1522
+ }
1523
+ async reconcileSourcePublish(input) {
1524
+ const inputDigest = controlPlaneDigest({ operation: 'reconcile-source-publish', planId: input.planId, expectedRevision: input.expectedRevision,
1525
+ expectedFence: input.expectedFence, receipt: input.receipt });
1526
+ const replay = this.#sourcePlanReceipt(input.idempotencyKey, 'reconcile-source-publish', inputDigest, input.planId);
1527
+ if (replay !== undefined) {
1528
+ const outcome = input.receipt.evidence.outcome;
1529
+ const expectedStatus = outcome === 'exists-match' ? 'awaiting-registry-verify'
1530
+ : outcome === 'absent' ? 'awaiting-publish' : outcome === 'digest-conflict' ? 'release-failed' : 'publish-ambiguous';
1531
+ const expectedFence = outcome === 'absent' ? input.expectedFence + 1 : input.expectedFence;
1532
+ const expectedFailure = outcome === 'digest-conflict' ? 'publish-digest-conflict'
1533
+ : outcome === 'unknown' ? 'publish-ambiguous' : undefined;
1534
+ if (replay.result.status !== expectedStatus || replay.result.revision !== input.expectedRevision + 1
1535
+ || replay.result.release?.fence !== expectedFence || replay.result.release.failureCode !== expectedFailure
1536
+ || replay.result.release.failurePhase !== (outcome === 'exists-match' || outcome === 'absent' ? undefined : 'publish')
1537
+ || replay.result.release.updatedAt !== replay.createdAt) {
1538
+ throw new ControlPlaneStoreError('invalid-state', 'stored publish reconciliation apply receipt is corrupt');
1539
+ }
1540
+ return replay;
1541
+ }
1542
+ const plan = this.getSourcePlan(input.planId);
1543
+ const operation = this.getSourcePublishReconciliationOperation(input.receipt.operationId);
1544
+ if (plan.status !== 'publish-ambiguous' || plan.revision !== input.expectedRevision || plan.release?.fence !== input.expectedFence
1545
+ || operation.planId !== plan.id || operation.fence !== input.expectedFence || operation.status !== 'completed'
1546
+ || operation.receipt === undefined || controlPlaneDigest(operation.receipt) !== controlPlaneDigest(input.receipt)) {
1547
+ throw new ControlPlaneStoreError('conflict', 'publish reconciliation receipt is not the completed fenced operation');
1548
+ }
1549
+ const verified = await input.resolveAuthority(input.receipt).verify(input.receipt, plan, operation.request);
1550
+ const outcome = verified.evidence.outcome;
1551
+ const now = this.#now();
1552
+ this.#database.exec('BEGIN IMMEDIATE');
1553
+ try {
1554
+ let nextStatus = 'publish-ambiguous';
1555
+ let nextFence = input.expectedFence;
1556
+ let failurePhase = 'publish';
1557
+ let failureCode = 'publish-ambiguous';
1558
+ if (outcome === 'exists-match') {
1559
+ nextStatus = 'awaiting-registry-verify';
1560
+ failurePhase = null;
1561
+ failureCode = null;
1562
+ }
1563
+ else if (outcome === 'absent') {
1564
+ nextStatus = 'awaiting-publish';
1565
+ nextFence += 1;
1566
+ failurePhase = null;
1567
+ failureCode = null;
1568
+ }
1569
+ else if (outcome === 'digest-conflict') {
1570
+ nextStatus = 'release-failed';
1571
+ failureCode = 'publish-digest-conflict';
1572
+ }
1573
+ const result = this.#database.prepare(`UPDATE source_plans SET status = ?, revision = revision + 1, release_fence = ?,
1574
+ release_failure_phase = ?, release_failure_code = ?, updated_at = ?
1575
+ WHERE id = ? AND status = 'publish-ambiguous' AND revision = ? AND release_id = ? AND release_fence = ?`).run(nextStatus, nextFence, failurePhase, failureCode, now, plan.id, input.expectedRevision, plan.release.id, input.expectedFence);
1576
+ if (Number(result.changes) !== 1)
1577
+ throw new ControlPlaneStoreError('conflict', 'publish reconciliation lost its CAS/fence');
1578
+ const applied = this.#database.prepare(`UPDATE source_publish_reconciliations SET status = 'applied', applied_at = ?
1579
+ WHERE operation_id = ? AND status = 'completed' AND receipt_digest = ?`).run(now, operation.operationId, controlPlaneDigest(input.receipt));
1580
+ if (Number(applied.changes) !== 1)
1581
+ throw new ControlPlaneStoreError('conflict', 'publish reconciliation lost its apply CAS');
1582
+ const output = this.getSourcePlan(plan.id);
1583
+ const operationReceipt = { idempotencyKey: input.idempotencyKey, operation: 'reconcile-source-publish', inputDigest, result: output, createdAt: now };
1584
+ this.#insertReceipt(operationReceipt);
1585
+ this.#database.exec('COMMIT');
1586
+ return operationReceipt;
1587
+ }
1588
+ catch (error) {
1589
+ this.#database.exec('ROLLBACK');
1590
+ throw error;
1591
+ }
1592
+ }
1593
+ #assertReconciliationEnvironment(input) {
1594
+ if (!UUID.test(input.installationId) || !UUID.test(input.ledger.id) || !isAbsolute(input.ledger.path)
1595
+ || !KEY.test(input.registry.id) || !KEY.test(input.adapter.id) || !DIGEST.test(input.adapter.sha256)
1596
+ || !isAbsolute(input.adapter.path) || input.adapter.id === '' || input.receiptTtlMs < 1_000 || input.receiptTtlMs > 300_000) {
1597
+ throw new ControlPlaneStoreError('invalid-input', 'publish reconciliation environment is invalid');
1598
+ }
1599
+ }
1600
+ #assertReleaseEnvironment(input, phase) {
1601
+ if (!UUID.test(input.installationId) || !UUID.test(input.ledger.id) || !isAbsolute(input.ledger.path)
1602
+ || !KEY.test(bounded(input.registry.id, 'registry.id', 160)) || !KEY.test(bounded(input.catalog.id, 'catalog.id', 160))
1603
+ || !isAbsolute(input.catalog.path) || !KEY.test(bounded(input.adapter.id, 'adapter.id', 160))
1604
+ || !DIGEST.test(input.adapter.sha256) || !isAbsolute(input.adapter.path)
1605
+ || !KEY.test(bounded(input.adapter.authority, 'adapter.authority', 160)) || !KEY.test(bounded(input.adapter.keyId, 'adapter.keyId', 160))
1606
+ || (input.adapter.interpreter !== null && (!isAbsolute(input.adapter.interpreter.path) || !DIGEST.test(input.adapter.interpreter.sha256)))) {
1607
+ throw new ControlPlaneStoreError('invalid-input', 'source release environment binding is invalid');
1608
+ }
1609
+ bounded(input.ledger.path, 'ledger.path', 2_000);
1610
+ bounded(input.registry.locator, 'registry.locator', 2_000);
1611
+ bounded(input.catalog.path, 'catalog.path', 2_000);
1612
+ bounded(input.adapter.version, 'adapter.version', 160);
1613
+ if (!Number.isSafeInteger(input.receiptTtlMs) || input.receiptTtlMs < 1_000 || input.receiptTtlMs > 300_000) {
1614
+ throw new ControlPlaneStoreError('invalid-input', 'source release receipt TTL is invalid');
1615
+ }
1616
+ if (phase === 'catalog-admission' && (!DIGEST.test(input.catalog.expectedBeforeDigest ?? '')
1617
+ || !DIGEST.test(input.catalog.expectedAfterDigest ?? '') || input.catalog.expectedBeforeDigest === input.catalog.expectedAfterDigest)) {
1618
+ throw new ControlPlaneStoreError('invalid-input', 'catalog admission requires exact distinct before/after catalog digests');
1619
+ }
1620
+ }
1621
+ #previousReleaseEvidence(plan, phase) {
1622
+ const ordered = ['pr', 'review', 'merge', 'build', 'sign', 'publish', 'registry-verify', 'catalog-admission'];
1623
+ const before = ordered.slice(0, ordered.indexOf(phase));
1624
+ const result = {};
1625
+ for (const previousPhase of before) {
1626
+ const row = this.#database.prepare(`SELECT * FROM source_release_operations WHERE plan_id = ? AND phase = ?
1627
+ AND status = 'applied' ORDER BY attempt DESC LIMIT 1`).get(plan.id, previousPhase);
1628
+ if (row === undefined)
1629
+ throw new ControlPlaneStoreError('invalid-state', `source release is missing applied ${previousPhase} evidence`);
1630
+ const operation = sourceReleaseOperationFromRow(row);
1631
+ const receipt = operation.receipt;
1632
+ if (receipt === undefined || receipt.outcome !== 'passed' || receipt.evidence.kind === 'failure'
1633
+ || receipt.evidence.kind === 'publish-ambiguity') {
1634
+ if (previousPhase === 'publish' && receipt?.outcome === 'ambiguous'
1635
+ && this.#hasMatchingPublishReconciliation(plan.id, operation.fence))
1636
+ continue;
1637
+ throw new ControlPlaneStoreError('invalid-state', `source release has no successful ${previousPhase} evidence`);
1638
+ }
1639
+ result[previousPhase] = receipt.evidence;
1640
+ }
1641
+ return result;
1642
+ }
1643
+ #sourceReleaseRequest(plan, phase, operationId, attempt, requestedAt, binding, evidence) {
1644
+ if (plan.release === undefined || plan.releaseAuthorization === undefined || plan.sourceCheck === undefined) {
1645
+ throw new ControlPlaneStoreError('invalid-state', 'source release request is missing durable authorization bindings');
1646
+ }
1647
+ const base = { schemaVersion: 1, kind: 'dsh-source-release-request', operationId, attempt, requestedAt,
1648
+ receiptTtlMs: binding.receiptTtlMs, installationId: binding.installationId, ledger: binding.ledger,
1649
+ plan: { id: plan.id, digest: plan.digest, revision: plan.revision }, release: { id: plan.release.id, fence: plan.release.fence },
1650
+ authorization: plan.releaseAuthorization, adapter: binding.adapter, registry: binding.registry,
1651
+ catalog: { id: binding.catalog.id, path: binding.catalog.path } };
1652
+ const pr = evidence.pr;
1653
+ const review = evidence.review;
1654
+ const merge = evidence.merge;
1655
+ const build = evidence.build;
1656
+ const signed = evidence.sign;
1657
+ const verified = evidence['registry-verify'];
1658
+ const published = evidence.publish ?? ((phase === 'registry-verify' || phase === 'catalog-admission')
1659
+ && build?.kind === 'build' && signed?.kind === 'sign' ? this.#reconciledPublishEvidence(plan, build, signed) : undefined);
1660
+ if (phase === 'pr')
1661
+ return { ...base, phase, input: { repository: plan.repository, worktree: plan.worktree,
1662
+ baseCommit: plan.baseCommit, name: plan.name, scope: plan.scope, expectedTreeDigest: plan.sourceCheck.treeDigest,
1663
+ expectedPatchDigest: plan.sourceCheck.patchDigest } };
1664
+ if (phase === 'review' && pr?.kind === 'pr')
1665
+ return { ...base, phase, input: { prId: pr.prId,
1666
+ headCommit: pr.headCommit, baseCommit: pr.baseCommit, prEvidenceDigest: controlPlaneDigest(pr) } };
1667
+ if (phase === 'merge' && pr?.kind === 'pr' && review?.kind === 'review')
1668
+ return { ...base, phase, input: { prId: pr.prId,
1669
+ headCommit: pr.headCommit, reviewId: review.reviewId, reviewEvidenceDigest: controlPlaneDigest(review),
1670
+ targetBranch: plan.releaseAuthorization.releasePolicy.targetBranch } };
1671
+ if (phase === 'build' && merge?.kind === 'merge') {
1672
+ const policy = plan.releaseAuthorization.releasePolicy;
1673
+ return { ...base, phase, input: { repository: plan.repository, mergeCommit: merge.mergeCommit,
1674
+ mergeEvidenceDigest: controlPlaneDigest(merge), name: plan.name, expectedCandidateId: policy.candidateId,
1675
+ expectedPackageName: policy.packageName, expectedPackageVersion: policy.packageVersion, expectedPackagePath: policy.packagePath,
1676
+ expectedDshBaseline: policy.dshBaseline, expectedCapabilities: policy.capabilities, expectedAuthorities: policy.authorities,
1677
+ expectedRequires: policy.requires } };
1678
+ }
1679
+ if (phase === 'sign' && build?.kind === 'build')
1680
+ return { ...base, phase, input: { artifact: releaseArtifact(build),
1681
+ buildEvidenceDigest: controlPlaneDigest(build) } };
1682
+ if (phase === 'publish' && build?.kind === 'build' && signed?.kind === 'sign')
1683
+ return { ...base, phase, input: {
1684
+ artifact: releaseArtifact(build), artifactStatementDigest: signed.artifactStatementDigest,
1685
+ artifactSignature: signed.artifactSignature, signEvidenceDigest: controlPlaneDigest(signed)
1686
+ } };
1687
+ if (phase === 'registry-verify' && build?.kind === 'build' && signed?.kind === 'sign') {
1688
+ const publish = published?.kind === 'publish' ? published : this.#reconciledPublishEvidence(plan, build, signed);
1689
+ return { ...base, phase, input: { artifact: releaseArtifact(build), artifactStatementDigest: signed.artifactStatementDigest,
1690
+ artifactSignature: signed.artifactSignature, registryReference: publish.registryReference,
1691
+ publishEvidenceDigest: controlPlaneDigest(publish) } };
1692
+ }
1693
+ if (phase === 'catalog-admission' && build?.kind === 'build' && signed?.kind === 'sign' && published?.kind === 'publish'
1694
+ && verified?.kind === 'registry-verify' && binding.catalog.expectedBeforeDigest !== undefined
1695
+ && binding.catalog.expectedAfterDigest !== undefined) {
1696
+ const registryVerification = this.#successfulSourceReleaseOperation(plan, 'registry-verify');
1697
+ if (registryVerification.request.phase !== 'registry-verify') {
1698
+ throw new ControlPlaneStoreError('invalid-state', 'durable registry verification request has the wrong phase');
1699
+ }
1700
+ const artifact = releaseArtifact(build);
1701
+ const candidate = parseCatalog({ schemaVersion: 1, entries: [{ id: artifact.candidateId, package: artifact.packageName,
1702
+ version: artifact.packageVersion, integrity: artifact.tarballIntegrity, dshBaseline: artifact.dshBaseline,
1703
+ registry: { id: plan.releaseAuthorization.releasePolicy.registryId, locator: plan.releaseAuthorization.releasePolicy.registryLocator,
1704
+ reference: published.registryReference },
1705
+ capabilities: artifact.capabilities, authorities: artifact.authorities, requires: artifact.requires }] }).entries[0];
1706
+ return { ...base, phase, input: { artifact, artifactStatementDigest: signed.artifactStatementDigest,
1707
+ artifactSignature: signed.artifactSignature, registryReference: published.registryReference,
1708
+ registryVerificationRequest: registryVerification.request, registryVerificationReceipt: registryVerification.receipt,
1709
+ verificationEvidenceDigest: controlPlaneDigest(verified), expectedBeforeCatalogDigest: binding.catalog.expectedBeforeDigest,
1710
+ expectedAfterCatalogDigest: binding.catalog.expectedAfterDigest, candidate } };
1711
+ }
1712
+ throw new ControlPlaneStoreError('invalid-state', `source release cannot derive the exact ${phase} request`);
1713
+ }
1714
+ #successfulSourceReleaseOperation(plan, phase) {
1715
+ const row = this.#database.prepare(`SELECT * FROM source_release_operations WHERE plan_id = ? AND phase = ?
1716
+ AND status = 'applied' ORDER BY attempt DESC LIMIT 1`).get(plan.id, phase);
1717
+ if (row === undefined)
1718
+ throw new ControlPlaneStoreError('invalid-state', `source release is missing applied ${phase} receipt`);
1719
+ const operation = sourceReleaseOperationFromRow(row);
1720
+ const receipt = operation.receipt;
1721
+ if (receipt === undefined || receipt.outcome !== 'passed' || receipt.phase !== phase
1722
+ || operation.request.release.id !== plan.release?.id || operation.request.release.fence !== plan.release.fence) {
1723
+ throw new ControlPlaneStoreError('invalid-state', `source release has no current successful ${phase} receipt`);
1724
+ }
1725
+ return { ...operation, receipt };
1726
+ }
1727
+ #reconciledPublishEvidence(plan, build, signed) {
1728
+ const row = this.#database.prepare(`SELECT * FROM source_publish_reconciliations WHERE plan_id = ? AND release_id = ?
1729
+ AND status = 'applied' ORDER BY attempt DESC LIMIT 1`).get(plan.id, plan.release.id);
1730
+ if (row === undefined)
1731
+ throw new ControlPlaneStoreError('invalid-state', 'registry verification has no publish or reconciliation evidence');
1732
+ const reconciliation = sourcePublishReconciliationFromRow(row);
1733
+ const evidence = reconciliation.receipt?.evidence;
1734
+ if (reconciliation.receipt === undefined || evidence?.outcome !== 'exists-match' || evidence.registryReference === null) {
1735
+ throw new ControlPlaneStoreError('invalid-state', 'publish reconciliation did not prove an exact registry match');
1736
+ }
1737
+ const policy = plan.releaseAuthorization.releasePolicy;
1738
+ return { kind: 'publish', registryId: policy.registryId,
1739
+ registryReference: evidence.registryReference, packageName: build.packageName,
1740
+ packageVersion: build.packageVersion, tarballSha256: build.tarballSha256, tarballIntegrity: build.tarballIntegrity,
1741
+ artifactStatementDigest: signed.artifactStatementDigest,
1742
+ artifactSignatureDigest: signed.artifactSignatureDigest, signEvidenceDigest: controlPlaneDigest(signed), immutable: true };
1743
+ }
1744
+ #hasMatchingPublishReconciliation(planId, fence) {
1745
+ const row = this.#database.prepare(`SELECT * FROM source_publish_reconciliations WHERE plan_id = ? AND release_fence = ?
1746
+ AND status = 'applied' ORDER BY attempt DESC LIMIT 1`).get(planId, fence);
1747
+ if (row === undefined)
1748
+ return false;
1749
+ return sourcePublishReconciliationFromRow(row).receipt?.evidence.outcome === 'exists-match';
1750
+ }
1751
+ health() {
1752
+ const row = this.#database.prepare(`SELECT
1753
+ (SELECT count(*) FROM capability_gaps WHERE status = 'open') AS gaps,
1754
+ (SELECT count(*) FROM activation_plans WHERE status = 'approved') + (SELECT count(*) FROM source_plans WHERE status = 'approved') AS ready_plans,
1755
+ (SELECT count(*) FROM activation_plans WHERE status IN ('staging', 'awaiting-reload', 'awaiting-readiness',
1756
+ 'awaiting-effect-blocked-replay', 'awaiting-shadow', 'awaiting-canary', 'awaiting-soak', 'awaiting-health', 'commit-pending')) AS active_activations,
1757
+ (SELECT count(*) FROM activation_plans WHERE status = 'rolled-back') +
1758
+ (SELECT count(*) FROM source_plans WHERE status IN ('local-checks-failed', 'release-failed')) AS failed,
1759
+ (SELECT count(*) FROM activation_plans WHERE status = 'rollback-pending') AS rollback_pending`).get();
1760
+ return { gaps: row.gaps, readyPlans: row.ready_plans, activeActivations: row.active_activations, failed: row.failed, rollbackPending: row.rollback_pending };
1761
+ }
1762
+ #sourcePlanReceipt(idempotencyKey, operation, inputDigest, planId) {
1763
+ const receipt = this.#receipt(idempotencyKey, operation, inputDigest);
1764
+ if (receipt === undefined)
1765
+ return undefined;
1766
+ const result = sourceSnapshotFromStored(receipt.result);
1767
+ const authoritative = this.getSourcePlan(planId);
1768
+ if (result.id !== planId || result.digest !== authoritative.digest || result.gapId !== authoritative.gapId
1769
+ || result.revision > authoritative.revision
1770
+ || (result.approval !== undefined && controlPlaneDigest(result.approval) !== controlPlaneDigest(authoritative.approval))
1771
+ || (result.sourceCheck !== undefined && controlPlaneDigest(result.sourceCheck) !== controlPlaneDigest(authoritative.sourceCheck))
1772
+ || (result.releaseAuthorization !== undefined
1773
+ && controlPlaneDigest(result.releaseAuthorization) !== controlPlaneDigest(authoritative.releaseAuthorization))
1774
+ || (result.release !== undefined && (result.release.id !== authoritative.release?.id
1775
+ || result.release.fence > (authoritative.release?.fence ?? 0) || result.release.updatedAt !== receipt.createdAt))) {
1776
+ throw new ControlPlaneStoreError('invalid-state', 'stored source operation receipt is not bound to authoritative state');
1777
+ }
1778
+ return { ...receipt, result };
1779
+ }
1780
+ #sourcePlanReceiptByKey(idempotencyKey, operation, inputDigest) {
1781
+ const receipt = this.#receipt(idempotencyKey, operation, inputDigest);
1782
+ if (receipt === undefined)
1783
+ return undefined;
1784
+ const snapshot = sourceSnapshotFromStored(receipt.result);
1785
+ const authoritative = this.getSourcePlan(snapshot.id);
1786
+ const replayBinding = { operation: 'create-source-plan', gapId: snapshot.gapId, repository: snapshot.repository,
1787
+ worktree: snapshot.worktree, baseCommit: snapshot.baseCommit, name: snapshot.name, generatorDigest: snapshot.generatorDigest,
1788
+ scope: snapshot.scope, ttlMs: snapshot.expiresAt - snapshot.createdAt };
1789
+ if (snapshot.digest !== authoritative.digest || snapshot.gapId !== authoritative.gapId
1790
+ || snapshot.revision !== 1 || snapshot.status !== 'pending-approval' || snapshot.createdAt !== receipt.createdAt
1791
+ || snapshot.approval !== undefined || snapshot.sourceCheck !== undefined || snapshot.releaseAuthorization !== undefined
1792
+ || snapshot.release !== undefined || controlPlaneDigest(replayBinding) !== inputDigest) {
1793
+ throw new ControlPlaneStoreError('invalid-state', 'stored source operation receipt is not bound to authoritative state');
1794
+ }
1795
+ return { ...receipt, result: snapshot };
1796
+ }
1797
+ #activationPlanReceipt(idempotencyKey, operation, inputDigest, planId) {
1798
+ const receipt = this.#receipt(idempotencyKey, operation, inputDigest);
1799
+ if (receipt === undefined)
1800
+ return undefined;
1801
+ const snapshot = activationSnapshotFromStored(receipt.result);
1802
+ const authoritative = this.getPlan(planId);
1803
+ if (snapshot.id !== planId || snapshot.digest !== authoritative.digest || snapshot.gapId !== authoritative.gapId
1804
+ || snapshot.revision > authoritative.revision
1805
+ || (snapshot.approval !== undefined && controlPlaneDigest(snapshot.approval) !== controlPlaneDigest(authoritative.approval))
1806
+ || (snapshot.activation !== undefined && (snapshot.activation.id !== authoritative.activation?.id
1807
+ || snapshot.activation.fence > (authoritative.activation?.fence ?? 0)))) {
1808
+ throw new ControlPlaneStoreError('invalid-state', 'stored activation operation receipt is not bound to authoritative state');
1809
+ }
1810
+ return { ...receipt, result: snapshot };
1811
+ }
1812
+ #activationPlanReceiptByKey(idempotencyKey, operation, inputDigest) {
1813
+ const receipt = this.#receipt(idempotencyKey, operation, inputDigest);
1814
+ if (receipt === undefined)
1815
+ return undefined;
1816
+ const snapshot = activationSnapshotFromStored(receipt.result);
1817
+ const authoritative = this.getPlan(snapshot.id);
1818
+ const replayBinding = { operation: 'create-activation-plan', gapId: snapshot.gapId, candidate: snapshot.candidate,
1819
+ catalog: { digest: snapshot.dossier.catalogDigest, provenance: snapshot.dossier.catalogProvenance },
1820
+ matchedCapabilities: snapshot.dossier.matchedCapabilities, profile: snapshot.profile, target: snapshot.target,
1821
+ installationId: snapshot.installationId, ledger: snapshot.ledger, executor: snapshot.executor,
1822
+ ttlMs: snapshot.expiresAt - snapshot.createdAt };
1823
+ if (snapshot.digest !== authoritative.digest || snapshot.gapId !== authoritative.gapId || snapshot.revision !== 1
1824
+ || snapshot.status !== 'pending-approval' || snapshot.createdAt !== receipt.createdAt
1825
+ || snapshot.approval !== undefined || snapshot.activation !== undefined || controlPlaneDigest(replayBinding) !== inputDigest) {
1826
+ throw new ControlPlaneStoreError('invalid-state', 'stored activation creation receipt is not bound to authoritative state');
1827
+ }
1828
+ return { ...receipt, result: snapshot };
1829
+ }
1830
+ #receipt(idempotencyKey, operation, inputDigest) {
1831
+ const row = this.#database.prepare('SELECT * FROM operation_receipts WHERE idempotency_key = ?').get(idempotencyKey);
1832
+ if (row === undefined)
1833
+ return undefined;
1834
+ if (row.operation !== operation || row.input_digest !== inputDigest)
1835
+ throw new ControlPlaneStoreError('conflict', 'operation idempotency key was reused with different input');
1836
+ if (!DIGEST.test(row.result_digest ?? '') || controlPlaneOperationReceiptDigest(idempotencyKey, row.operation, row.input_digest, row.result_json, row.created_at) !== row.result_digest) {
1837
+ throw new ControlPlaneStoreError('invalid-state', 'stored operation receipt result is corrupt');
1838
+ }
1839
+ return { idempotencyKey, operation, inputDigest, result: JSON.parse(row.result_json), createdAt: row.created_at };
1840
+ }
1841
+ #insertReceipt(receipt) {
1842
+ const resultJson = JSON.stringify(receipt.result);
1843
+ this.#database.prepare(`INSERT INTO operation_receipts
1844
+ (idempotency_key, operation, input_digest, result_json, result_digest, created_at) VALUES (?, ?, ?, ?, ?, ?)`)
1845
+ .run(receipt.idempotencyKey, receipt.operation, receipt.inputDigest, resultJson, controlPlaneOperationReceiptDigest(receipt.idempotencyKey, receipt.operation, receipt.inputDigest, resultJson, receipt.createdAt), receipt.createdAt);
1846
+ }
1847
+ }
1848
+ //# sourceMappingURL=store.js.map