@dsh-enhanced/plugin-control-plane 0.1.47 → 0.1.49

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 (167) hide show
  1. package/README.md +183 -15
  2. package/bin/dsh-live-qualification-authority.js +15 -0
  3. package/bin/dsh-local-release-adapter.js +328 -8
  4. package/bin/dsh-systemd-host-attestor.js +359 -9
  5. package/bin/dsh-systemd-host-authority.js +12 -0
  6. package/lib/adoption-coordinator.d.ts +3 -1
  7. package/lib/adoption-coordinator.d.ts.map +1 -1
  8. package/lib/adoption-coordinator.js +24 -9
  9. package/lib/adoption-coordinator.js.map +1 -1
  10. package/lib/cli.d.ts.map +1 -1
  11. package/lib/cli.js +33 -14
  12. package/lib/cli.js.map +1 -1
  13. package/lib/creation-capability-feedback.d.ts +23 -0
  14. package/lib/creation-capability-feedback.d.ts.map +1 -0
  15. package/lib/creation-capability-feedback.js +132 -0
  16. package/lib/creation-capability-feedback.js.map +1 -0
  17. package/lib/creation-capability-journal.d.ts +66 -0
  18. package/lib/creation-capability-journal.d.ts.map +1 -0
  19. package/lib/creation-capability-journal.js +751 -0
  20. package/lib/creation-capability-journal.js.map +1 -0
  21. package/lib/creation-capability-runtime.d.ts +63 -0
  22. package/lib/creation-capability-runtime.d.ts.map +1 -0
  23. package/lib/creation-capability-runtime.js +564 -0
  24. package/lib/creation-capability-runtime.js.map +1 -0
  25. package/lib/creation-capability-source.d.ts +37 -0
  26. package/lib/creation-capability-source.d.ts.map +1 -0
  27. package/lib/creation-capability-source.js +328 -0
  28. package/lib/creation-capability-source.js.map +1 -0
  29. package/lib/creation-capability-types.d.ts +252 -0
  30. package/lib/creation-capability-types.d.ts.map +1 -0
  31. package/lib/creation-capability-types.js +2 -0
  32. package/lib/creation-capability-types.js.map +1 -0
  33. package/lib/deployment-readiness.d.ts +16 -0
  34. package/lib/deployment-readiness.d.ts.map +1 -1
  35. package/lib/deployment-readiness.js +48 -1
  36. package/lib/deployment-readiness.js.map +1 -1
  37. package/lib/effect-blocked-replay.js +2 -2
  38. package/lib/effect-blocked-replay.js.map +1 -1
  39. package/lib/foreground-deployment-runtime.d.ts +3 -0
  40. package/lib/foreground-deployment-runtime.d.ts.map +1 -1
  41. package/lib/foreground-deployment-runtime.js +55 -13
  42. package/lib/foreground-deployment-runtime.js.map +1 -1
  43. package/lib/foreground-deployment.d.ts.map +1 -1
  44. package/lib/foreground-deployment.js +5 -0
  45. package/lib/foreground-deployment.js.map +1 -1
  46. package/lib/host-attestor.d.ts +2 -0
  47. package/lib/host-attestor.d.ts.map +1 -1
  48. package/lib/host-attestor.js +8 -1
  49. package/lib/host-attestor.js.map +1 -1
  50. package/lib/host-inputs.d.ts +13 -0
  51. package/lib/host-inputs.d.ts.map +1 -0
  52. package/lib/host-inputs.js +162 -0
  53. package/lib/host-inputs.js.map +1 -0
  54. package/lib/host-maintenance.d.ts +61 -0
  55. package/lib/host-maintenance.d.ts.map +1 -0
  56. package/lib/host-maintenance.js +158 -0
  57. package/lib/host-maintenance.js.map +1 -0
  58. package/lib/host-readiness.d.ts +5 -0
  59. package/lib/host-readiness.d.ts.map +1 -0
  60. package/lib/host-readiness.js +22 -0
  61. package/lib/host-readiness.js.map +1 -0
  62. package/lib/index.d.ts +16 -0
  63. package/lib/index.d.ts.map +1 -1
  64. package/lib/index.js +7 -0
  65. package/lib/index.js.map +1 -1
  66. package/lib/live-qualification-authority.d.ts +32 -0
  67. package/lib/live-qualification-authority.d.ts.map +1 -0
  68. package/lib/live-qualification-authority.js +280 -0
  69. package/lib/live-qualification-authority.js.map +1 -0
  70. package/lib/live-qualification-client.d.ts +4 -0
  71. package/lib/live-qualification-client.d.ts.map +1 -0
  72. package/lib/live-qualification-client.js +22 -0
  73. package/lib/live-qualification-client.js.map +1 -0
  74. package/lib/live-qualification-runtime.d.ts +78 -0
  75. package/lib/live-qualification-runtime.d.ts.map +1 -0
  76. package/lib/live-qualification-runtime.js +367 -0
  77. package/lib/live-qualification-runtime.js.map +1 -0
  78. package/lib/live-qualification.d.ts +73 -0
  79. package/lib/live-qualification.d.ts.map +1 -0
  80. package/lib/live-qualification.js +164 -0
  81. package/lib/live-qualification.js.map +1 -0
  82. package/lib/owner-task-gaps.d.ts +2 -0
  83. package/lib/owner-task-gaps.d.ts.map +1 -1
  84. package/lib/owner-task-gaps.js +9 -1
  85. package/lib/owner-task-gaps.js.map +1 -1
  86. package/lib/runtime-epoch.d.ts +69 -0
  87. package/lib/runtime-epoch.d.ts.map +1 -0
  88. package/lib/runtime-epoch.js +139 -0
  89. package/lib/runtime-epoch.js.map +1 -0
  90. package/lib/runtime-observer.d.ts.map +1 -1
  91. package/lib/runtime-observer.js +42 -6
  92. package/lib/runtime-observer.js.map +1 -1
  93. package/lib/service.d.ts +247 -4
  94. package/lib/service.d.ts.map +1 -1
  95. package/lib/service.js +1332 -27
  96. package/lib/service.js.map +1 -1
  97. package/lib/source-adoption-authority.d.ts +3 -0
  98. package/lib/source-adoption-authority.d.ts.map +1 -1
  99. package/lib/source-adoption-authority.js +13 -3
  100. package/lib/source-adoption-authority.js.map +1 -1
  101. package/lib/source-adoption-runner.d.ts +3 -0
  102. package/lib/source-adoption-runner.d.ts.map +1 -1
  103. package/lib/source-adoption-runner.js +16 -4
  104. package/lib/source-adoption-runner.js.map +1 -1
  105. package/lib/source-baseline.d.ts +37 -0
  106. package/lib/source-baseline.d.ts.map +1 -0
  107. package/lib/source-baseline.js +250 -0
  108. package/lib/source-baseline.js.map +1 -0
  109. package/lib/source-build.d.ts +17 -1
  110. package/lib/source-build.d.ts.map +1 -1
  111. package/lib/source-build.js +501 -7
  112. package/lib/source-build.js.map +1 -1
  113. package/lib/source-context.d.ts +18 -0
  114. package/lib/source-context.d.ts.map +1 -1
  115. package/lib/source-context.js +85 -11
  116. package/lib/source-context.js.map +1 -1
  117. package/lib/source-creation.d.ts +106 -0
  118. package/lib/source-creation.d.ts.map +1 -0
  119. package/lib/source-creation.js +664 -0
  120. package/lib/source-creation.js.map +1 -0
  121. package/lib/source-job-types.d.ts +19 -0
  122. package/lib/source-job-types.d.ts.map +1 -1
  123. package/lib/source-jobs.d.ts +30 -3
  124. package/lib/source-jobs.d.ts.map +1 -1
  125. package/lib/source-jobs.js +382 -40
  126. package/lib/source-jobs.js.map +1 -1
  127. package/lib/source-maintenance.d.ts +66 -0
  128. package/lib/source-maintenance.d.ts.map +1 -0
  129. package/lib/source-maintenance.js +177 -0
  130. package/lib/source-maintenance.js.map +1 -0
  131. package/lib/source-revision.d.ts +57 -0
  132. package/lib/source-revision.d.ts.map +1 -0
  133. package/lib/source-revision.js +175 -0
  134. package/lib/source-revision.js.map +1 -0
  135. package/lib/source-workspace.d.ts +19 -14
  136. package/lib/source-workspace.d.ts.map +1 -1
  137. package/lib/source-workspace.js +90 -17
  138. package/lib/source-workspace.js.map +1 -1
  139. package/lib/sqlite.d.ts +13 -3
  140. package/lib/sqlite.d.ts.map +1 -1
  141. package/lib/sqlite.js +362 -1
  142. package/lib/sqlite.js.map +1 -1
  143. package/lib/store.d.ts +258 -3
  144. package/lib/store.d.ts.map +1 -1
  145. package/lib/store.js +2211 -95
  146. package/lib/store.js.map +1 -1
  147. package/lib/systemd-host-authority.d.ts +68 -0
  148. package/lib/systemd-host-authority.d.ts.map +1 -0
  149. package/lib/systemd-host-authority.js +424 -0
  150. package/lib/systemd-host-authority.js.map +1 -0
  151. package/lib/task-observation-evidence.d.ts +32 -0
  152. package/lib/task-observation-evidence.d.ts.map +1 -0
  153. package/lib/task-observation-evidence.js +62 -0
  154. package/lib/task-observation-evidence.js.map +1 -0
  155. package/lib/task-observation-runtime.d.ts +2 -3
  156. package/lib/task-observation-runtime.d.ts.map +1 -1
  157. package/lib/task-observation-runtime.js +7 -39
  158. package/lib/task-observation-runtime.js.map +1 -1
  159. package/lib/task-observation-store.js +1 -1
  160. package/lib/trust.d.ts.map +1 -1
  161. package/lib/trust.js +0 -0
  162. package/lib/trust.js.map +1 -1
  163. package/lib/types.d.ts +43 -6
  164. package/lib/types.d.ts.map +1 -1
  165. package/lib/version.d.ts +1 -1
  166. package/lib/version.js +1 -1
  167. package/package.json +21 -14
package/lib/store.js CHANGED
@@ -1,20 +1,37 @@
1
+ import { validateLiveQualificationTerms, assertLiveQualificationBatch, parseLiveQualificationReceipt, verifyLiveQualificationReceipt } from './live-qualification.js';
1
2
  import { createHash, randomUUID } from 'node:crypto';
2
- import { basename, isAbsolute } from 'node:path';
3
+ import { createPublicKey, verify } from 'node:crypto';
4
+ import { spawnSync } from 'node:child_process';
5
+ import { realpathSync } from 'node:fs';
6
+ import { basename, isAbsolute, posix, resolve, win32 } from 'node:path';
3
7
  import { discover, parseCatalog } from './catalog.js';
4
8
  import { parseApprovalReceipt } from './approval.js';
5
9
  import { parseSourcePublishReconciliationReceipt, parseSourcePublishReconciliationRequest, parseSourceReleaseAuthorization, parseSourceReleaseReceipt, parseSourceReleaseRequest, parseVerifiedSourceReleaseAuthorization } from './release.js';
6
- import { controlPlaneOperationReceiptDigest, controlPlaneSchemaVersion, openControlPlaneDatabase } from './sqlite.js';
10
+ import { controlPlaneOperationReceiptDigest, controlPlaneSchemaVersion, openControlPlaneDatabase, openControlPlaneDatabaseReadOnly } from './sqlite.js';
7
11
  import { validateSourceBuildConfig } from './source-build.js';
12
+ import { validateSourceBaselineConfig, verifySourceBaselineHistory } from './source-baseline.js';
8
13
  import { validateScopedPluginFiles } from './source-workspace.js';
14
+ import { validateCreationCapabilitySource } from './creation-capability-source.js';
15
+ import { validateSourceRevisionBinding } from './source-revision.js';
16
+ import { validateSourceCreationFiles, validateSourceCreationGrant } from './source-creation.js';
17
+ import { sourceGrowthRunDigest, validatePluginRevisionRegressionCertificate, validatePluginRevisionVerificationCertificate, validatePluginCreationVerificationCertificate, validateSourceGrowthRunBinding } from '@dsh-enhanced/assistant-growth-contract';
9
18
  import { validateAdoptionHandoffTerms } from './adoption-handoff.js';
10
19
  import { assertForegroundDeployment, assertForegroundTask } from './foreground-deployment.js';
11
20
  import { assertTaskObservationBatch, getTaskObservationRecord, readTaskObservationContext } from './task-observation-store.js';
21
+ import { parseRuntimeEpochRequest, parseRuntimeEpochReceipt, verifyRuntimeEpochReceipt } from './runtime-epoch.js';
22
+ import { runtimeIdentityDigest } from './foreground-deployment.js';
23
+ import { readHostMaintenanceRecords, verifyHostMaintenanceChain, hostMaintenanceCanonical, hostMaintenanceDigest } from './host-maintenance.js';
24
+ import { parseSourceMaintenanceRecord, sameSourceMaintenancePublicKey, sourceMaintenanceDigest, sourceBaselineChain, verifySourceMaintenanceRecords } from './source-maintenance.js';
25
+ import { resolveTrustKey } from './trust.js';
12
26
  const KEY = /^[A-Za-z0-9][A-Za-z0-9._:-]{0,159}$/u;
13
27
  const PROFILE = /^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$/u;
14
28
  const PLUGIN_NAME = /^(?=.{1,64}$)[a-z][a-z0-9]*(?:-[a-z0-9]+)*$/u;
15
29
  const DIGEST = /^[a-f0-9]{64}$/u;
16
30
  const COMMIT = /^[a-f0-9]{40}$/u;
17
31
  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;
32
+ const MAX_PREPARED_ARTIFACT_BYTES = 32 * 1024 * 1024;
33
+ const MAX_STORED_ARTIFACT_BYTES = 128 * 1024 * 1024;
34
+ const MAX_STORED_ARTIFACTS = 256;
18
35
  const SOURCE_STATUSES = new Set(['expired', 'pending-approval', 'approved', 'running-local-checks', 'ready-for-human-review',
19
36
  'local-checks-failed', 'awaiting-pr', 'awaiting-review', 'awaiting-merge', 'awaiting-build', 'awaiting-sign', 'awaiting-publish',
20
37
  'awaiting-registry-verify', 'awaiting-catalog-admission', 'release-complete', 'release-failed', 'publish-ambiguous']);
@@ -22,9 +39,26 @@ const SOURCE_PLAN_KEYS = ['schemaVersion', 'kind', 'id', 'gapId', 'gapSnapshot',
22
39
  'expiresAt', 'digest', 'repository', 'worktree', 'baseCommit', 'name', 'generatorDigest', 'scope'];
23
40
  const ACTIVATION_PLAN_KEYS = ['schemaVersion', 'kind', 'id', 'gapId', 'gapSnapshot', 'status', 'revision', 'createdAt',
24
41
  'expiresAt', 'profile', 'candidate', 'dossier', 'installationId', 'ledger', 'target', 'executor', 'digest'];
25
- const ACTIVATION_STATUSES = new Set(['pending-approval', 'approved', 'staging', 'awaiting-reload', 'awaiting-readiness',
42
+ const ACTIVATION_STATUSES = new Set(['pending-approval', 'approved', 'staging', 'awaiting-reload', 'awaiting-readiness', 'awaiting-live-tasks',
26
43
  'awaiting-effect-blocked-replay', 'awaiting-shadow', 'awaiting-canary', 'awaiting-soak', 'awaiting-health', 'commit-pending',
27
44
  'rollback-pending', 'activated', 'rolled-back']);
45
+ export function validateHostDeploymentInputs(value) {
46
+ if (!Array.isArray(value) || value.length < 1 || value.length > 128) {
47
+ throw new ControlPlaneStoreError('invalid-input', 'Host deployment inputs require 1..128 relative paths');
48
+ }
49
+ const seen = new Set();
50
+ for (const input of value) {
51
+ if (typeof input !== 'string' || input.length === 0 || Buffer.byteLength(input) > 4096
52
+ || input !== input.normalize('NFC') || input !== input.trim() || input.includes('\\')
53
+ || [...input].some(character => { const point = character.codePointAt(0); return point < 32 || point === 127; })
54
+ || posix.isAbsolute(input) || win32.parse(input).root !== ''
55
+ || input.split('/').some(part => part === '' || part === '.' || part === '..')
56
+ || posix.normalize(input) !== input || seen.has(input)) {
57
+ throw new ControlPlaneStoreError('invalid-input', 'Host deployment input is not a unique normalized relative path');
58
+ }
59
+ seen.add(input);
60
+ }
61
+ }
28
62
  export class ControlPlaneStoreError extends Error {
29
63
  code;
30
64
  constructor(code, message) {
@@ -33,6 +67,50 @@ export class ControlPlaneStoreError extends Error {
33
67
  this.name = 'ControlPlaneStoreError';
34
68
  }
35
69
  }
70
+ function sourceMaintenanceGit(cwd, args) {
71
+ const result = spawnSync('git', ['-c', 'core.hooksPath=/dev/null', '-c', 'protocol.allow=never',
72
+ '-c', 'gc.auto=0', ...args], { cwd, encoding: 'utf8', timeout: 30_000, maxBuffer: 16384,
73
+ env: { LANG: 'C', LC_ALL: 'C', GIT_CONFIG_NOSYSTEM: '1', GIT_CONFIG_GLOBAL: '/dev/null',
74
+ GIT_CONFIG_SYSTEM: '/dev/null', GIT_CONFIG_COUNT: '0', GIT_ALLOW_PROTOCOL: 'file',
75
+ GIT_NO_REPLACE_OBJECTS: '1', GIT_TERMINAL_PROMPT: '0', GIT_NO_LAZY_FETCH: '1' } });
76
+ if (result.error || result.status !== 0 || result.signal || typeof result.stdout !== 'string') {
77
+ throw new ControlPlaneStoreError('conflict', 'source maintenance Git evidence failed');
78
+ }
79
+ return result.stdout.trimEnd();
80
+ }
81
+ function sourceMaintenanceDirectRef(cwd, ref) {
82
+ const fields = sourceMaintenanceGit(cwd, ['for-each-ref', '--format=%(refname)%00%(objectname)%00%(symref)', ref]).split('\0');
83
+ if (fields.length !== 3 || fields[0] !== ref || !COMMIT.test(fields[1] ?? '') || fields[2] !== '') {
84
+ throw new ControlPlaneStoreError('conflict', 'source maintenance Git ref is missing or symbolic');
85
+ }
86
+ return fields[1];
87
+ }
88
+ function assertSourceMaintenanceGit(record, physicalHome, logicalHome, requireCurrentRefs = true) {
89
+ if (realpathSync(physicalHome) !== physicalHome)
90
+ throw new ControlPlaneStoreError('invalid-input', 'source maintenance physical Home is not canonical');
91
+ const project = (path) => {
92
+ if (!path.startsWith(`${logicalHome}/`))
93
+ throw new ControlPlaneStoreError('invalid-input', 'source maintenance Git path is outside Home');
94
+ return `${physicalHome}${path.slice(logicalHome.length)}`;
95
+ };
96
+ const repository = project(record.repository), remote = project(record.baseline.remote);
97
+ if (realpathSync(repository) !== repository || realpathSync(remote) !== remote
98
+ || sourceMaintenanceGit(repository, ['rev-parse', '--show-toplevel']) !== repository
99
+ || sourceMaintenanceGit(remote, ['rev-parse', '--is-bare-repository']) !== 'true') {
100
+ throw new ControlPlaneStoreError('conflict', 'source maintenance Git repository changed');
101
+ }
102
+ const branch = requireCurrentRefs ? sourceMaintenanceDirectRef(remote, `refs/heads/${record.baseline.targetBranch}`) : undefined;
103
+ const managed = requireCurrentRefs ? sourceMaintenanceDirectRef(repository, record.baseline.ref) : undefined;
104
+ if (requireCurrentRefs && (branch !== record.candidateTip || managed !== record.candidateTip)
105
+ || sourceMaintenanceGit(repository, ['rev-parse', `${record.candidateTip}^{tree}`]) !== record.sourceTree) {
106
+ throw new ControlPlaneStoreError('conflict', 'source maintenance candidate Git tip or tree changed');
107
+ }
108
+ for (const [earlier, later] of [[record.previousTip, record.candidateTip], [record.upstreamCommit, record.candidateTip],
109
+ [record.before.sourceCommit, record.previousTip], [record.after.sourceCommit, record.candidateTip],
110
+ [record.before.sourceCommit, record.after.sourceCommit]]) {
111
+ sourceMaintenanceGit(repository, ['merge-base', '--is-ancestor', earlier, later]);
112
+ }
113
+ }
36
114
  function canonical(value) {
37
115
  if (Array.isArray(value))
38
116
  return `[${value.map(canonical).join(',')}]`;
@@ -73,9 +151,45 @@ function positiveInteger(value, field) {
73
151
  */
74
152
  export const MODIFY_GENERATOR_DIGEST = createHash('sha256').update('dsh-source-modify-no-generator-v1').digest('hex');
75
153
  function expectedSourceScope(name, mode) {
76
- return mode === 'create'
77
- ? Object.freeze(['plugins/README.md', `plugins/${name}`].sort())
78
- : Object.freeze([`plugins/${name}`]);
154
+ return mode === 'prepared-create' || mode === 'prepared-revise'
155
+ ? Object.freeze(['plugins/README.md', `plugins/${name}`, 'pnpm-lock.yaml'].sort())
156
+ : mode === 'create'
157
+ ? Object.freeze(['plugins/README.md', `plugins/${name}`].sort())
158
+ : Object.freeze([`plugins/${name}`]);
159
+ }
160
+ function creationBindingFromStored(value) {
161
+ const binding = objectRecord(value, 'source creation binding');
162
+ exactKeys(binding, ['grant', 'generatorDigest', ...(Object.hasOwn(binding, 'growthRun') ? ['growthRun'] : [])], 'source creation binding');
163
+ try {
164
+ validateSourceCreationGrant(binding['grant']);
165
+ }
166
+ catch {
167
+ throw new ControlPlaneStoreError('invalid-input', 'source creation grant is invalid');
168
+ }
169
+ if (typeof binding['generatorDigest'] !== 'string' || !DIGEST.test(binding['generatorDigest'])) {
170
+ throw new ControlPlaneStoreError('invalid-input', 'source creation generator digest is invalid');
171
+ }
172
+ let growthRun;
173
+ if (Object.hasOwn(binding, 'growthRun')) {
174
+ try {
175
+ validateSourceGrowthRunBinding(binding['growthRun']);
176
+ }
177
+ catch {
178
+ throw new ControlPlaneStoreError('invalid-input', 'source creation growth run is invalid');
179
+ }
180
+ growthRun = structuredClone(binding['growthRun']);
181
+ }
182
+ return Object.freeze({ grant: Object.freeze(structuredClone(binding['grant'])), generatorDigest: binding['generatorDigest'],
183
+ ...(growthRun === undefined ? {} : { growthRun: Object.freeze(growthRun) }) });
184
+ }
185
+ function revisionBindingFromStored(value) {
186
+ try {
187
+ validateSourceRevisionBinding(value);
188
+ }
189
+ catch {
190
+ throw new ControlPlaneStoreError('invalid-input', 'source revision binding is invalid');
191
+ }
192
+ return Object.freeze(structuredClone(value));
79
193
  }
80
194
  function objectRecord(value, label) {
81
195
  if (typeof value !== 'object' || value === null || Array.isArray(value)) {
@@ -124,7 +238,8 @@ function activationSnapshotFromStored(value) {
124
238
  exactKeys(gapSnapshot, ['revision', 'inputDigest', 'roi', 'capability'], 'stored activation gap snapshot');
125
239
  const dossier = objectRecord(item['dossier'], 'stored activation dossier');
126
240
  exactKeys(dossier, ['catalogDigest', 'catalogProvenance', 'matchedCapabilities', 'authorities', 'packages',
127
- ...(Object.hasOwn(dossier, 'handoff') ? ['handoff'] : [])], 'stored activation dossier');
241
+ ...(Object.hasOwn(dossier, 'handoff') ? ['handoff'] : []), ...(Object.hasOwn(dossier, 'liveQualification') ? ['liveQualification'] : []),
242
+ ...(Object.hasOwn(dossier, 'hostDeploymentInputs') ? ['hostDeploymentInputs'] : [])], 'stored activation dossier');
128
243
  const ledger = objectRecord(item['ledger'], 'stored activation ledger');
129
244
  exactKeys(ledger, ['id', 'path'], 'stored activation ledger');
130
245
  const target = objectRecord(item['target'], 'stored activation target');
@@ -157,6 +272,16 @@ function activationSnapshotFromStored(value) {
157
272
  || typeof executor['sha256'] !== 'string' || !DIGEST.test(executor['sha256'])) {
158
273
  throw new ControlPlaneStoreError('invalid-state', 'stored activation plan snapshot is corrupt');
159
274
  }
275
+ if (dossier['liveQualification'] !== undefined)
276
+ validateLiveQualificationTerms(dossier['liveQualification']);
277
+ if (dossier['hostDeploymentInputs'] !== undefined) {
278
+ try {
279
+ validateHostDeploymentInputs(dossier['hostDeploymentInputs']);
280
+ }
281
+ catch {
282
+ throw new ControlPlaneStoreError('invalid-state', 'stored Host deployment inputs are corrupt');
283
+ }
284
+ }
160
285
  if (dossier['handoff'] !== undefined) {
161
286
  try {
162
287
  validateAdoptionHandoffTerms(dossier['handoff']);
@@ -300,6 +425,141 @@ function activationCoreFiles(value, targetPath, label) {
300
425
  }
301
426
  return value;
302
427
  }
428
+ function hostInputPins(value, inputs, targetPath, label) {
429
+ if (!Array.isArray(value) || value.length !== inputs.length)
430
+ throw new ControlPlaneStoreError('invalid-state', `${label} does not cover signed inputs`);
431
+ const paths = new Set();
432
+ for (let index = 0; index < value.length; index++) {
433
+ const item = value[index];
434
+ if (!item || typeof item !== 'object' || Array.isArray(item)
435
+ || Object.keys(item).sort().join('\0') !== 'input\0path\0sha256' || item.input !== inputs[index]
436
+ || typeof item.path !== 'string' || !isAbsolute(item.path) || resolve(item.path) !== item.path
437
+ || !item.path.startsWith(`${targetPath}/`) || paths.has(item.path)
438
+ || typeof item.sha256 !== 'string' || !DIGEST.test(item.sha256)) {
439
+ throw new ControlPlaneStoreError('invalid-state', `${label} contains an unbound or unsafe file pin`);
440
+ }
441
+ paths.add(item.path);
442
+ }
443
+ return value;
444
+ }
445
+ function hostInputWitnessFromRow(database, plan, project = true) {
446
+ const row = database.prepare('SELECT witness_json,witness_digest FROM activation_host_input_witnesses WHERE plan_id=?')
447
+ .get(plan.id);
448
+ if (!row)
449
+ return undefined;
450
+ if (Buffer.byteLength(row.witness_json) > 262_144)
451
+ throw new ControlPlaneStoreError('invalid-state', 'Host input witness exceeds storage bound');
452
+ let witness;
453
+ try {
454
+ witness = JSON.parse(row.witness_json);
455
+ }
456
+ catch {
457
+ throw new ControlPlaneStoreError('invalid-state', 'Host input witness JSON is corrupt');
458
+ }
459
+ if (!witness || typeof witness !== 'object' || Array.isArray(witness)
460
+ || Object.keys(witness).sort().join('\0') !== ['schemaVersion', 'kind', 'planId', 'planDigest', 'activationId', 'fence', 'createdAt',
461
+ 'inputs', 'profileFiles', 'deploymentFiles', 'baselineDeploymentFiles', 'digest'].sort().join('\0')
462
+ || witness.schemaVersion !== 1 || witness.kind !== 'dsh-host-input-witness'
463
+ || !plan.dossier.hostDeploymentInputs || witness.planId !== plan.id || witness.planDigest !== plan.digest
464
+ || !plan.activation || witness.activationId !== plan.activation.id || !Number.isSafeInteger(witness.fence) || witness.fence < 1
465
+ || (witness.fence !== plan.activation.fence && !(['rollback-pending', 'rolled-back'].includes(plan.status) && witness.fence < plan.activation.fence))
466
+ || !Number.isSafeInteger(witness.createdAt) || witness.createdAt < plan.createdAt
467
+ || controlPlaneDigest(witness) !== row.witness_digest)
468
+ throw new ControlPlaneStoreError('invalid-state', 'Host input witness identity changed');
469
+ const { digest, ...unsigned } = witness;
470
+ if (digest !== controlPlaneDigest(unsigned) || !Array.isArray(witness.inputs)
471
+ || controlPlaneDigest(witness.inputs) !== controlPlaneDigest(plan.dossier.hostDeploymentInputs)) {
472
+ throw new ControlPlaneStoreError('invalid-state', 'Host input witness digest or signed input set changed');
473
+ }
474
+ const core = activationCoreFiles(witness.profileFiles, plan.target.profilePath, 'Host input witness core files');
475
+ if (core.some(file => file.sha256 === null))
476
+ throw new ControlPlaneStoreError('invalid-state', 'Host input witness has an absent core file');
477
+ hostInputPins(witness.deploymentFiles, witness.inputs, plan.target.profilePath, 'Host input witness deployment files');
478
+ if (plan.activation.targetOriginallyExisted) {
479
+ hostInputPins(witness.baselineDeploymentFiles, witness.inputs, plan.target.profilePath, 'Host input witness baseline files');
480
+ }
481
+ else if (!Array.isArray(witness.baselineDeploymentFiles) || witness.baselineDeploymentFiles.length !== 0) {
482
+ throw new ControlPlaneStoreError('invalid-state', 'absent original target has Host input baseline files');
483
+ }
484
+ if (!project)
485
+ return witness;
486
+ const latest = readHostMaintenanceContext(database, plan.id).records.at(-1);
487
+ if (!latest)
488
+ return witness;
489
+ const { digest: _digest, ...original } = witness;
490
+ const effective = { ...original, profileFiles: latest.after.profileFiles,
491
+ deploymentFiles: latest.after.deploymentFiles, baselineDeploymentFiles: latest.after.baselineDeploymentFiles };
492
+ return { ...effective, digest: controlPlaneDigest(effective) };
493
+ }
494
+ /** Offline update seam. Reads original history before verifying its maintenance chain. */
495
+ export function readHostMaintenanceContext(database, planId) {
496
+ const row = database.prepare('SELECT * FROM activation_plans WHERE id=?').get(planId);
497
+ if (!row)
498
+ throw new ControlPlaneStoreError('not-found', 'maintenance plan absent');
499
+ const plan = activationFromRow(row), records = readHostMaintenanceRecords(database, planId);
500
+ const witness = hostInputWitnessFromRow(database, plan, false);
501
+ const readinessRow = database.prepare(`SELECT operation.* FROM host_attestation_operations operation
502
+ JOIN host_attestations attestation ON attestation.plan_id=operation.plan_id AND attestation.phase=operation.phase
503
+ AND attestation.receipt_digest=operation.receipt_digest
504
+ WHERE operation.plan_id=? AND operation.phase='readiness' AND operation.status='applied'`).get(planId);
505
+ const readiness = readinessRow ? hostOperationFromRow(readinessRow).receipt : undefined;
506
+ if (records.length) {
507
+ if (!witness || !readiness || readiness.outcome !== 'passed')
508
+ throw new ControlPlaneStoreError('invalid-state', 'maintenance lost original readiness');
509
+ verifyHostMaintenanceChain(records, plan, readiness, witness);
510
+ const checkpoint = database.prepare('SELECT baseline_json FROM activation_deployment_checkpoints WHERE plan_id=?').get(plan.id);
511
+ if (!checkpoint || controlPlaneDigest(JSON.parse(String(checkpoint.baseline_json))) !== controlPlaneDigest(records[0].before.profileFiles)) {
512
+ throw new ControlPlaneStoreError('invalid-state', 'maintenance original deployment checkpoint changed');
513
+ }
514
+ }
515
+ return { plan, witness, readiness, records };
516
+ }
517
+ /** Caller owns the stopped Home transaction; this append never rewrites historical evidence. */
518
+ export function appendHostMaintenance(database, record) {
519
+ database.exec('SAVEPOINT append_host_maintenance');
520
+ try {
521
+ // Acquire the SQLite writer reservation before reading the current lineage.
522
+ // A savepoint composes with the installer's enclosing offline transaction.
523
+ database.exec('UPDATE deployment_host_maintenance SET sequence=sequence WHERE 0');
524
+ appendHostMaintenanceLocked(database, record);
525
+ database.exec('RELEASE append_host_maintenance');
526
+ }
527
+ catch (error) {
528
+ database.exec('ROLLBACK TO append_host_maintenance; RELEASE append_host_maintenance');
529
+ throw error;
530
+ }
531
+ }
532
+ function appendHostMaintenanceLocked(database, record) {
533
+ const context = readHostMaintenanceContext(database, record.plan.id), { plan, witness, readiness, records } = context;
534
+ const prior = records.find(item => item.transactionId === record.transactionId);
535
+ if (prior) {
536
+ if (hostMaintenanceDigest(prior) !== hostMaintenanceDigest(record))
537
+ throw new ControlPlaneStoreError('conflict', 'maintenance transaction changed');
538
+ return;
539
+ }
540
+ const current = readCurrentRuntimeEpochDeployment(database, plan.target.profilePath);
541
+ if (current.plan.id !== plan.id)
542
+ throw new ControlPlaneStoreError('conflict', 'maintenance deployment was superseded');
543
+ const watch = database.prepare('SELECT state,activation_id,fence FROM activation_watch WHERE plan_id=?').get(plan.id);
544
+ if (plan.status !== 'activated' || !witness || !readiness || readiness.outcome !== 'passed'
545
+ || watch?.state !== 'watching' || watch.activation_id !== plan.activation?.id || watch.fence !== plan.activation?.fence) {
546
+ throw new ControlPlaneStoreError('invalid-state', 'maintenance requires a watched successful deployment');
547
+ }
548
+ verifyHostMaintenanceChain([...records, record], plan, readiness, witness);
549
+ const checkpoint = database.prepare('SELECT baseline_json FROM activation_deployment_checkpoints WHERE plan_id=?').get(plan.id);
550
+ if (!checkpoint || controlPlaneDigest(JSON.parse(String(checkpoint.baseline_json))) !== controlPlaneDigest(witness.profileFiles)) {
551
+ throw new ControlPlaneStoreError('invalid-state', 'maintenance original deployment checkpoint changed');
552
+ }
553
+ database.prepare('INSERT INTO deployment_host_maintenance VALUES (?,?,?,?,?)')
554
+ .run(plan.id, record.sequence, record.transactionId, JSON.stringify(record), hostMaintenanceDigest(record));
555
+ }
556
+ function effectiveActivationFromRow(database, row) {
557
+ const plan = activationFromRow(row);
558
+ if (!database.prepare('SELECT 1 FROM deployment_host_maintenance WHERE plan_id=? LIMIT 1').get(plan.id))
559
+ return plan;
560
+ const latest = readHostMaintenanceContext(database, plan.id).records.at(-1);
561
+ return latest && plan.activation ? { ...plan, activation: { ...plan.activation, targetBaselineFiles: latest.after.baselineFiles } } : plan;
562
+ }
303
563
  function activationFromRow(row) {
304
564
  const candidate = parseCatalog({ schemaVersion: 1, entries: [JSON.parse(row.candidate_json)] }).entries[0];
305
565
  const gapSnapshot = JSON.parse(row.gap_snapshot_json);
@@ -307,6 +567,16 @@ function activationFromRow(row) {
307
567
  if (candidate === undefined || !DIGEST.test(row.plan_digest) || !UUID.test(row.installation_id)
308
568
  || !DIGEST.test(gapSnapshot.inputDigest) || !DIGEST.test(dossier.catalogDigest))
309
569
  throw new ControlPlaneStoreError('invalid-state', 'stored activation plan is corrupt');
570
+ if (dossier.liveQualification !== undefined)
571
+ validateLiveQualificationTerms(dossier.liveQualification);
572
+ if (dossier.hostDeploymentInputs !== undefined) {
573
+ try {
574
+ validateHostDeploymentInputs(dossier.hostDeploymentInputs);
575
+ }
576
+ catch {
577
+ throw new ControlPlaneStoreError('invalid-state', 'stored Host deployment inputs are corrupt');
578
+ }
579
+ }
310
580
  if (dossier.handoff !== undefined) {
311
581
  try {
312
582
  validateAdoptionHandoffTerms(dossier.handoff);
@@ -388,15 +658,18 @@ function preparedEvidenceFromStored(value) {
388
658
  const commands = item['commands'].map((entry, index) => {
389
659
  const command = objectRecord(entry, `stored source prepared evidence command ${index}`);
390
660
  exactKeys(command, ['command', 'args', 'exitCode', 'durationMs', 'logDigest'], `stored source prepared evidence command ${index}`);
661
+ const args = command['args'];
662
+ const scriptIndex = command['command'] === 'docker' && Array.isArray(args) && args.at(-2) === '-ceu'
663
+ ? args.length - 1 : -1;
391
664
  if (typeof command['command'] !== 'string' || command['command'] === '' || command['command'].length > 64
392
- || !Array.isArray(command['args']) || command['args'].length > 64
393
- || command['args'].some(arg => typeof arg !== 'string' || arg.length > 4096)
665
+ || !Array.isArray(args) || args.length > 96
666
+ || args.some((arg, argIndex) => typeof arg !== 'string' || arg.length > (argIndex === scriptIndex ? 32_768 : 4096))
394
667
  || command['exitCode'] !== 0
395
668
  || !Number.isSafeInteger(command['durationMs']) || Number(command['durationMs']) < 0
396
669
  || !DIGEST.test(String(command['logDigest']))) {
397
670
  throw new ControlPlaneStoreError('invalid-state', `stored source prepared evidence command ${index} is corrupt`);
398
671
  }
399
- return Object.freeze({ command: String(command['command']), args: Object.freeze([...command['args']]),
672
+ return Object.freeze({ command: String(command['command']), args: Object.freeze([...args]),
400
673
  exitCode: 0, durationMs: Number(command['durationMs']), logDigest: String(command['logDigest']) });
401
674
  });
402
675
  const pack = objectRecord(item['pack'], 'stored source prepared evidence pack');
@@ -415,8 +688,21 @@ function preparedEvidenceFromStored(value) {
415
688
  sizeBytes: Number(pack['sizeBytes']), sha256: String(pack['sha256']) }),
416
689
  preparedAt: Number(item['preparedAt']) });
417
690
  }
691
+ function preparedArtifactBytesFromRow(row, pack) {
692
+ if (row.pack_sha256 !== pack.sha256 || !Number.isSafeInteger(row.size_bytes)
693
+ || row.size_bytes < 1 || row.size_bytes > MAX_PREPARED_ARTIFACT_BYTES
694
+ || row.size_bytes !== pack.sizeBytes || !(row.bytes instanceof Uint8Array)
695
+ || !Number.isSafeInteger(row.created_at) || row.created_at < 0) {
696
+ throw new ControlPlaneStoreError('invalid-state', 'stored prepared source artifact metadata is corrupt');
697
+ }
698
+ const bytes = Buffer.from(row.bytes);
699
+ if (bytes.length !== row.size_bytes || createHash('sha256').update(bytes).digest('hex') !== pack.sha256) {
700
+ throw new ControlPlaneStoreError('invalid-state', 'stored prepared source artifact bytes are corrupt');
701
+ }
702
+ return bytes;
703
+ }
418
704
  function sourceFromRow(row) {
419
- if (row.mode !== 'create' && row.mode !== 'modify') {
705
+ if (row.mode !== 'create' && row.mode !== 'modify' && row.mode !== 'prepared-create' && row.mode !== 'prepared-revise') {
420
706
  throw new ControlPlaneStoreError('invalid-state', 'stored source plan has an unknown mode');
421
707
  }
422
708
  const gapSnapshot = JSON.parse(row.gap_snapshot_json);
@@ -439,9 +725,22 @@ function sourceFromRow(row) {
439
725
  throw new ControlPlaneStoreError('invalid-state', 'stored source prepared evidence is corrupt');
440
726
  }
441
727
  }
442
- if ((row.mode === 'modify') !== (preparedEvidence !== undefined)
443
- || (row.mode === 'modify' && (row.checked_tree_digest === null || row.checked_patch_digest === null || row.checked_at === null))
444
- || (row.mode === 'modify' && ['running-local-checks', 'local-checks-failed'].includes(row.status))) {
728
+ let creation;
729
+ if (row.creation_json !== null) {
730
+ try {
731
+ creation = creationBindingFromStored(JSON.parse(row.creation_json));
732
+ }
733
+ catch {
734
+ throw new ControlPlaneStoreError('invalid-state', 'stored source creation binding is corrupt');
735
+ }
736
+ }
737
+ const revision = row.revision_json === null ? undefined : revisionBindingFromStored(JSON.parse(row.revision_json));
738
+ const prepared = row.mode === 'modify' || row.mode === 'prepared-create' || row.mode === 'prepared-revise';
739
+ if (prepared !== (preparedEvidence !== undefined) || (row.mode === 'prepared-create') !== (creation !== undefined) || (row.mode === 'prepared-revise') !== (revision !== undefined)
740
+ || ((row.mode === 'prepared-create' || row.mode === 'prepared-revise') && (!['pending-approval', 'expired'].includes(row.status) || (creation ?? revision)?.generatorDigest !== row.generator_digest
741
+ || row.approval_json !== null || row.release_authorization_json !== null || row.release_id !== null))
742
+ || (prepared && (row.checked_tree_digest === null || row.checked_patch_digest === null || row.checked_at === null))
743
+ || (prepared && ['running-local-checks', 'local-checks-failed'].includes(row.status))) {
445
744
  throw new ControlPlaneStoreError('invalid-state', 'stored source plan mode does not match its evidence or status');
446
745
  }
447
746
  let approval;
@@ -463,8 +762,8 @@ function sourceFromRow(row) {
463
762
  || !Number.isSafeInteger(sourceCheck.checkedAt) || sourceCheck.checkedAt < 0))) {
464
763
  throw new ControlPlaneStoreError('invalid-state', 'stored source check evidence is corrupt');
465
764
  }
466
- const digestBinding = row.mode === 'modify'
467
- ? { ...immutable, mode: row.mode, sourceCheck, preparedEvidence }
765
+ const digestBinding = prepared
766
+ ? { ...immutable, mode: row.mode, ...(creation === undefined ? {} : { creation }), ...(revision === undefined ? {} : { sourceRevision: revision }), sourceCheck, preparedEvidence }
468
767
  : immutable;
469
768
  if (controlPlaneDigest(digestBinding) !== row.plan_digest) {
470
769
  throw new ControlPlaneStoreError('invalid-state', 'stored source plan is corrupt or digest-mismatched');
@@ -493,7 +792,7 @@ function sourceFromRow(row) {
493
792
  && (row.release_id === null || releaseAuthorization === undefined || sourceCheck === undefined))) {
494
793
  throw new ControlPlaneStoreError('invalid-state', 'stored source release state is incomplete');
495
794
  }
496
- return { ...immutable, mode: row.mode, digest: row.plan_digest, status: row.status, revision: row.revision,
795
+ return { ...immutable, mode: row.mode, ...(creation === undefined ? {} : { creation }), ...(revision === undefined ? {} : { sourceRevision: revision }), digest: row.plan_digest, status: row.status, revision: row.revision,
497
796
  ...(approval === undefined ? {} : { approval }),
498
797
  ...(sourceCheck === undefined ? {} : { sourceCheck }),
499
798
  ...(preparedEvidence === undefined ? {} : { preparedEvidence }),
@@ -507,14 +806,14 @@ function sourceSnapshotFromStored(value) {
507
806
  // `mode` was introduced in schema v13; receipt rows written by older binaries
508
807
  // omit it and are backfilled to 'create'. `preparedEvidence` exists only on
509
808
  // 'modify' plans.
510
- const optional = ['approval', 'sourceCheck', 'preparedEvidence', 'releaseAuthorization', 'release']
809
+ const optional = ['approval', 'sourceCheck', 'preparedEvidence', 'creation', 'sourceRevision', 'releaseAuthorization', 'release']
511
810
  .filter(key => Object.hasOwn(item, key));
512
811
  exactKeys(item, [...SOURCE_PLAN_KEYS, 'mode', ...optional], 'stored source plan snapshot');
513
812
  const rawMode = item['mode'];
514
813
  const mode = rawMode === undefined
515
814
  ? 'create'
516
- : rawMode === 'create' || rawMode === 'modify' ? rawMode : 'create';
517
- const modeValid = rawMode === undefined || rawMode === 'create' || rawMode === 'modify';
815
+ : rawMode === 'create' || rawMode === 'modify' || rawMode === 'prepared-create' || rawMode === 'prepared-revise' ? rawMode : 'create';
816
+ const modeValid = rawMode === undefined || rawMode === 'create' || rawMode === 'modify' || rawMode === 'prepared-create' || rawMode === 'prepared-revise';
518
817
  const gapSnapshot = objectRecord(item['gapSnapshot'], 'stored source gap snapshot');
519
818
  exactKeys(gapSnapshot, ['revision', 'inputDigest', 'roi', 'capability'], 'stored source gap snapshot');
520
819
  if (item['schemaVersion'] !== 1 || item['kind'] !== 'source' || typeof item['id'] !== 'string'
@@ -527,8 +826,9 @@ function sourceSnapshotFromStored(value) {
527
826
  || typeof item['name'] !== 'string' || !PLUGIN_NAME.test(item['name']) || typeof item['generatorDigest'] !== 'string'
528
827
  || !DIGEST.test(item['generatorDigest']) || !Array.isArray(item['scope'])
529
828
  || controlPlaneDigest(item['scope']) !== controlPlaneDigest(expectedSourceScope(item['name'], mode))
530
- || (mode === 'modify' && item['preparedEvidence'] === undefined)
531
- || (mode === 'create' && item['preparedEvidence'] !== undefined)
829
+ || ((mode === 'modify' || mode === 'prepared-create' || mode === 'prepared-revise') !== (item['preparedEvidence'] !== undefined))
830
+ || ((mode === 'prepared-create') !== (item['creation'] !== undefined))
831
+ || ((mode === 'prepared-revise') !== (item['sourceRevision'] !== undefined))
532
832
  || !Number.isSafeInteger(gapSnapshot['revision']) || Number(gapSnapshot['revision']) < 1
533
833
  || typeof gapSnapshot['inputDigest'] !== 'string' || !DIGEST.test(gapSnapshot['inputDigest'])
534
834
  || typeof gapSnapshot['roi'] !== 'number' || !Number.isFinite(gapSnapshot['roi']) || typeof gapSnapshot['capability'] !== 'string') {
@@ -538,6 +838,10 @@ function sourceSnapshotFromStored(value) {
538
838
  repository: item['repository'], worktree: item['worktree'], baseCommit: item['baseCommit'], name: item['name'],
539
839
  generatorDigest: item['generatorDigest'], scope: item['scope'], createdAt: item['createdAt'], expiresAt: item['expiresAt'] };
540
840
  const preparedEvidence = item['preparedEvidence'] === undefined ? undefined : preparedEvidenceFromStored(item['preparedEvidence']);
841
+ const creation = item['creation'] === undefined ? undefined : creationBindingFromStored(item['creation']);
842
+ const revision = item['sourceRevision'] === undefined ? undefined : revisionBindingFromStored(item['sourceRevision']);
843
+ if ((mode === 'prepared-create' || mode === 'prepared-revise') && (creation ?? revision)?.generatorDigest !== item['generatorDigest'])
844
+ throw new ControlPlaneStoreError('invalid-state', 'stored source creation generator changed');
541
845
  const approval = item['approval'] === undefined ? undefined : verifiedApprovalFromStored(item['approval'], item['id'], item['digest'], Number(item['createdAt']), Number(item['expiresAt']));
542
846
  let sourceCheck;
543
847
  if (item['sourceCheck'] !== undefined) {
@@ -550,7 +854,8 @@ function sourceSnapshotFromStored(value) {
550
854
  }
551
855
  sourceCheck = check;
552
856
  }
553
- const digestBinding = mode === 'modify' ? { ...immutable, mode, sourceCheck, preparedEvidence } : immutable;
857
+ const digestBinding = mode === 'modify' || mode === 'prepared-create' || mode === 'prepared-revise'
858
+ ? { ...immutable, mode, ...(creation === undefined ? {} : { creation }), ...(revision === undefined ? {} : { sourceRevision: revision }), sourceCheck, preparedEvidence } : immutable;
554
859
  if (controlPlaneDigest(digestBinding) !== item['digest'])
555
860
  throw new ControlPlaneStoreError('invalid-state', 'stored source plan snapshot digest is corrupt');
556
861
  let releaseAuthorization;
@@ -591,18 +896,19 @@ function sourceSnapshotFromStored(value) {
591
896
  // running-local-checks / local-checks-failed states.
592
897
  const postCheckStates = ['ready-for-human-review', 'release-complete',
593
898
  'release-failed', 'publish-ambiguous'];
594
- const requiresCheck = mode === 'modify'
899
+ const requiresCheck = mode === 'modify' || mode === 'prepared-create' || mode === 'prepared-revise'
595
900
  || expectedSourceRelease(status) !== undefined || postCheckStates.includes(status);
596
901
  const requiresRelease = expectedSourceRelease(status) !== undefined || ['release-complete', 'release-failed', 'publish-ambiguous'].includes(status);
597
- if ((mode === 'modify' && (['running-local-checks', 'local-checks-failed'].includes(status) || sourceCheck === undefined))
902
+ if (((mode === 'modify' || mode === 'prepared-create' || mode === 'prepared-revise') && (['running-local-checks', 'local-checks-failed'].includes(status) || sourceCheck === undefined))
903
+ || ((mode === 'prepared-create' || mode === 'prepared-revise') && (!['pending-approval', 'expired'].includes(status) || approval !== undefined || releaseAuthorization !== undefined || release !== undefined))
598
904
  || (status !== 'expired' && requiresApproval !== (approval !== undefined))
599
- || (status === 'expired' && mode !== 'modify')
905
+ || (status === 'expired' && mode !== 'modify' && mode !== 'prepared-create' && mode !== 'prepared-revise')
600
906
  || requiresCheck !== (sourceCheck !== undefined)
601
907
  || requiresRelease !== (releaseAuthorization !== undefined && release !== undefined)
602
908
  || (!requiresRelease && (releaseAuthorization !== undefined || release !== undefined))) {
603
909
  throw new ControlPlaneStoreError('invalid-state', 'stored source plan snapshot fields do not match its status');
604
910
  }
605
- return { ...immutable, mode, digest: item['digest'], status: item['status'], revision: Number(item['revision']),
911
+ return { ...immutable, mode, ...(creation === undefined ? {} : { creation }), ...(revision === undefined ? {} : { sourceRevision: revision }), digest: item['digest'], status: item['status'], revision: Number(item['revision']),
606
912
  ...(approval === undefined ? {} : { approval }), ...(sourceCheck === undefined ? {} : { sourceCheck }),
607
913
  ...(preparedEvidence === undefined ? {} : { preparedEvidence }),
608
914
  ...(releaseAuthorization === undefined ? {} : { releaseAuthorization }),
@@ -783,7 +1089,8 @@ function releaseArtifact(evidence) {
783
1089
  function sourceJobIntentFromStored(value) {
784
1090
  const intent = objectRecord(value, 'source job intent');
785
1091
  exactKeys(intent, ['authority', 'owner', 'ownerDigest', 'trustDigest', 'repository', 'name', 'gapId', 'gapRevision', 'gapDigest',
786
- 'baseCommit', 'files', 'ttlMs', 'build', 'worktree', 'containerName'], 'source job intent');
1092
+ 'baseCommit', 'files', 'ttlMs', 'build', 'worktree', 'containerName', ...(Object.hasOwn(intent, 'baseline') ? ['baseline'] : []),
1093
+ ...(Object.hasOwn(intent, 'mode') ? ['mode'] : []), ...(Object.hasOwn(intent, 'creation') ? ['creation'] : []), ...(Object.hasOwn(intent, 'revision') ? ['revision'] : [])], 'source job intent');
787
1094
  const authority = objectRecord(intent['authority'], 'source job authority');
788
1095
  exactKeys(authority, ['id', 'digest', 'expiresAt', 'maxSubmissions'], 'source job authority');
789
1096
  const owner = objectRecord(intent['owner'], 'source job owner');
@@ -805,7 +1112,10 @@ function sourceJobIntentFromStored(value) {
805
1112
  || typeof intent['baseCommit'] !== 'string' || !COMMIT.test(intent['baseCommit'])
806
1113
  || !Array.isArray(intent['files']) || intent['files'].length === 0 || intent['files'].length > 64
807
1114
  || !Number.isSafeInteger(intent['ttlMs']) || Number(intent['ttlMs']) < 60_000 || Number(intent['ttlMs']) > 86_400_000
808
- || typeof intent['containerName'] !== 'string' || !/^dsh-source-job-[a-f0-9]{64}$/u.test(intent['containerName'])) {
1115
+ || typeof intent['containerName'] !== 'string' || !/^dsh-source-job-[a-f0-9]{64}$/u.test(intent['containerName'])
1116
+ || (intent['mode'] !== undefined && intent['mode'] !== 'create' && intent['mode'] !== 'revise-created')
1117
+ || (intent['mode'] === 'create') !== Object.hasOwn(intent, 'creation')
1118
+ || (intent['mode'] === 'revise-created') !== Object.hasOwn(intent, 'revision')) {
809
1119
  throw new ControlPlaneStoreError('invalid-input', 'source job intent is invalid');
810
1120
  }
811
1121
  if (Number(owner['principalVersion']) < 1 || Number(owner['bindingVersion']) < 1 || Number(owner['generation']) < 1) {
@@ -826,17 +1136,35 @@ function sourceJobIntentFromStored(value) {
826
1136
  if (new Set(files.map(file => file.path)).size !== files.length)
827
1137
  throw new ControlPlaneStoreError('invalid-input', 'source job files are duplicated');
828
1138
  try {
829
- validateScopedPluginFiles(files);
1139
+ if (intent['mode'] === 'create' || intent['mode'] === 'revise-created')
1140
+ validateSourceCreationFiles(files);
1141
+ else
1142
+ validateScopedPluginFiles(files);
830
1143
  }
831
1144
  catch {
832
1145
  throw new ControlPlaneStoreError('invalid-input', 'source job files are invalid');
833
1146
  }
1147
+ const creation = intent['mode'] === 'create' ? creationBindingFromStored(intent['creation']) : undefined;
1148
+ const revision = intent['mode'] === 'revise-created' ? revisionBindingFromStored(intent['revision']) : undefined;
1149
+ if (revision !== undefined && !intent['name'].startsWith(revision.grant.namePrefix))
1150
+ throw new ControlPlaneStoreError('invalid-input', 'source job plugin name is outside its revision grant');
1151
+ if (creation !== undefined && !intent['name'].startsWith(creation.grant.namePrefix)) {
1152
+ throw new ControlPlaneStoreError('invalid-input', 'source job plugin name is outside its creation grant');
1153
+ }
834
1154
  try {
835
1155
  validateSourceBuildConfig(intent['build']);
836
1156
  }
837
1157
  catch {
838
1158
  throw new ControlPlaneStoreError('invalid-input', 'source job build configuration is invalid');
839
1159
  }
1160
+ if (Object.hasOwn(intent, 'baseline')) {
1161
+ try {
1162
+ validateSourceBaselineConfig(intent['baseline']);
1163
+ }
1164
+ catch {
1165
+ throw new ControlPlaneStoreError('invalid-input', 'source job baseline configuration is invalid');
1166
+ }
1167
+ }
840
1168
  return Object.freeze({ authority: Object.freeze({ id: authority['id'], digest: authority['digest'], expiresAt: Number(authority['expiresAt']), maxSubmissions: Number(authority['maxSubmissions']) }),
841
1169
  owner: Object.freeze({ receiptVersion: 2, authorityId: owner['authorityId'], authorityHash: owner['authorityHash'],
842
1170
  principalId: owner['principalId'], principalRecordId: owner['principalRecordId'], principalVersion: Number(owner['principalVersion']),
@@ -844,7 +1172,10 @@ function sourceJobIntentFromStored(value) {
844
1172
  ownerDigest: intent['ownerDigest'], trustDigest: intent['trustDigest'], repository: intent['repository'],
845
1173
  name: intent['name'], gapId: intent['gapId'], gapRevision: Number(intent['gapRevision']), gapDigest: intent['gapDigest'],
846
1174
  baseCommit: intent['baseCommit'], files: Object.freeze(files), ttlMs: Number(intent['ttlMs']), build: intent['build'],
847
- worktree: intent['worktree'], containerName: intent['containerName'] });
1175
+ worktree: intent['worktree'], containerName: intent['containerName'],
1176
+ ...(creation === undefined ? {} : { mode: 'create', creation }),
1177
+ ...(revision === undefined ? {} : { mode: 'revise-created', revision }),
1178
+ ...(Object.hasOwn(intent, 'baseline') ? { baseline: Object.freeze(intent['baseline']) } : {}) });
848
1179
  }
849
1180
  function exactInputRecord(value, label) {
850
1181
  if (typeof value !== 'object' || value === null || Array.isArray(value)) {
@@ -927,6 +1258,9 @@ function sourceJobFromRow(row) {
927
1258
  || !Number.isSafeInteger(row.created_at) || !Number.isSafeInteger(row.updated_at) || row.updated_at < row.created_at
928
1259
  || !['queued', 'running', 'prepared', 'failed', 'unknown'].includes(row.status)
929
1260
  || (row.definition_hash !== null && !DIGEST.test(row.definition_hash)) || (row.occurrence_id !== null && !KEY.test(row.occurrence_id))
1261
+ || (row.dispatch_at !== null && (!Number.isSafeInteger(row.dispatch_at) || row.dispatch_at < row.created_at))
1262
+ || (row.previous_definition_hash !== null && !DIGEST.test(row.previous_definition_hash))
1263
+ || (row.previous_definition_version !== null && (!Number.isSafeInteger(row.previous_definition_version) || row.previous_definition_version < 1))
930
1264
  || (row.plan_id !== null && !/^source-[a-f0-9-]{36}$/u.test(row.plan_id)) || (row.failure_code !== null && !KEY.test(row.failure_code))) {
931
1265
  throw new ControlPlaneStoreError('invalid-state', 'stored source job is corrupt');
932
1266
  }
@@ -936,11 +1270,17 @@ function sourceJobFromRow(row) {
936
1270
  || (row.status === 'prepared' && (row.definition_hash === null || row.occurrence_id === null || row.plan_id === null || row.failure_code !== null))
937
1271
  || (row.status === 'unknown' && (row.definition_hash === null || row.occurrence_id === null || row.plan_id !== null || row.failure_code === null))
938
1272
  || (row.status === 'failed' && (row.plan_id !== null || row.failure_code === null))
939
- || (!active && row.status !== 'prepared' && row.plan_id !== null))
1273
+ || (!active && row.status !== 'prepared' && row.plan_id !== null)
1274
+ || (row.previous_definition_hash === null) !== (row.previous_definition_version === null)
1275
+ || (row.previous_definition_hash !== null && (row.status !== 'queued' || row.definition_hash !== null || row.dispatch_at === null)))
940
1276
  throw new ControlPlaneStoreError('invalid-state', 'stored source job state is corrupt');
941
1277
  return Object.freeze({ id: row.id, automationId: row.automation_id, idempotencyKey: row.idempotency_key, intent, intentDigest: row.intent_digest,
942
1278
  status: row.status, revision: row.revision, createdAt: row.created_at, expiresAt: row.expires_at, updatedAt: row.updated_at,
943
- ...(row.definition_hash === null ? {} : { definitionHash: row.definition_hash }), ...(row.occurrence_id === null ? {} : { occurrenceId: row.occurrence_id }),
1279
+ ...(row.definition_hash === null ? {} : { definitionHash: row.definition_hash }),
1280
+ ...(row.dispatch_at === null ? {} : { dispatchAt: row.dispatch_at }),
1281
+ ...(row.previous_definition_hash === null ? {} : { previousDefinitionHash: row.previous_definition_hash }),
1282
+ ...(row.previous_definition_version === null ? {} : { previousDefinitionVersion: row.previous_definition_version }),
1283
+ ...(row.occurrence_id === null ? {} : { occurrenceId: row.occurrence_id }),
944
1284
  ...(row.plan_id === null ? {} : { planId: row.plan_id }), ...(row.failure_code === null ? {} : { failureCode: row.failure_code }) });
945
1285
  }
946
1286
  function readOwnerTaskFailureReference(database, gapId) {
@@ -1014,7 +1354,7 @@ export function readOwnerSourceAdoptionPlan(database, activationPlanId) {
1014
1354
  const row = database.prepare('SELECT * FROM activation_plans WHERE id = ?').get(activationPlanId);
1015
1355
  if (!link || !row)
1016
1356
  throw new ControlPlaneStoreError('not-found', 'owner source adoption binding absent');
1017
- const plan = activationFromRow(row), { plan: sourcePlan, source } = readOwnerPreparedSourcePlan(database, link.source_plan_id);
1357
+ const plan = effectiveActivationFromRow(database, row), { plan: sourcePlan, source } = readOwnerPreparedSourcePlan(database, link.source_plan_id);
1018
1358
  if (sourcePlan.status !== 'release-complete' || !sourcePlan.release || plan.gapId !== sourcePlan.gapId)
1019
1359
  throw new ControlPlaneStoreError('invalid-state', 'source adoption requires a completed owner release');
1020
1360
  const expected = adoptionBinding(sourcePlan, source, plan), released = readReleaseCandidate(database, sourcePlan);
@@ -1023,11 +1363,340 @@ export function readOwnerSourceAdoptionPlan(database, activationPlanId) {
1023
1363
  throw new ControlPlaneStoreError('invalid-state', 'source adoption binding changed');
1024
1364
  return { plan, sourcePlan, source, released };
1025
1365
  }
1366
+ function runtimeEpochFromRow(row) {
1367
+ const request = parseRuntimeEpochRequest(JSON.parse(row.request_json));
1368
+ if (request.operationId !== row.operation_id || request.plan.id !== row.plan_id || request.sequence !== row.sequence
1369
+ || request.runtimeIdentityDigest !== row.runtime_digest || controlPlaneDigest(request) !== row.request_digest
1370
+ || request.requestedAt !== row.created_at || (row.status === 'applied') !== (row.receipt_json !== null)) {
1371
+ throw new ControlPlaneStoreError('invalid-state', 'runtime epoch journal binding changed');
1372
+ }
1373
+ return { request, status: row.status, ...(row.receipt_json === null ? {} : { receipt: parseRuntimeEpochReceipt(JSON.parse(row.receipt_json)) }) };
1374
+ }
1375
+ /** Historical adoption remains immutable; only a currently watched deployment can acquire another runtime proof. */
1376
+ export function readCurrentRuntimeEpochDeployment(database, profilePath) {
1377
+ const unsettled = database.prepare(`SELECT 1 FROM activation_plans WHERE target_path=? AND status IN (
1378
+ 'staging','awaiting-reload','awaiting-readiness','awaiting-live-tasks','awaiting-effect-blocked-replay','awaiting-shadow',
1379
+ 'awaiting-canary','awaiting-soak','awaiting-health','commit-pending','rollback-pending') LIMIT 1`).get(profilePath);
1380
+ const latest = database.prepare(`SELECT checkpoint.plan_id,checkpoint.exposure_order,checkpoint.successful_order
1381
+ FROM activation_deployment_checkpoints checkpoint JOIN activation_plans plan ON plan.id=checkpoint.plan_id
1382
+ WHERE plan.target_path=? ORDER BY checkpoint.exposure_order DESC LIMIT 1`).get(profilePath);
1383
+ if (unsettled || !latest || latest.exposure_order !== latest.successful_order) {
1384
+ throw new ControlPlaneStoreError('invalid-state', 'runtime epoch requires the current successful deployment');
1385
+ }
1386
+ const adopted = readOwnerSourceAdoptionPlan(database, latest.plan_id), { plan } = adopted;
1387
+ const watch = database.prepare('SELECT state,activation_id,fence,last_host_generation FROM activation_watch WHERE plan_id=?').get(plan.id);
1388
+ if (plan.status !== 'activated' || !plan.activation || watch?.state !== 'watching'
1389
+ || watch.activation_id !== plan.activation.id || watch.fence !== plan.activation.fence) {
1390
+ throw new ControlPlaneStoreError('invalid-state', 'runtime epoch deployment is no longer watched');
1391
+ }
1392
+ const terms = plan.dossier.handoff;
1393
+ const handoffRow = database.prepare('SELECT * FROM adoption_handoffs WHERE plan_id=?').get(plan.id);
1394
+ if (!terms || !handoffRow || handoffRow.revoked_at !== null || handoffRow.plan_digest !== plan.digest
1395
+ || handoffRow.coordinator_id !== terms.coordinatorId)
1396
+ throw new ControlPlaneStoreError('conflict', 'runtime epoch handoff was revoked or changed');
1397
+ const handoff = { planId: plan.id, planDigest: plan.digest, coordinatorId: handoffRow.coordinator_id,
1398
+ createdAt: handoffRow.created_at, expiresAt: handoffRow.expires_at };
1399
+ const approvalRow = database.prepare('SELECT approval_receipt_json FROM activation_plans WHERE id=?').get(plan.id);
1400
+ const approvalReceipt = parseApprovalReceipt(JSON.parse(approvalRow.approval_receipt_json));
1401
+ if (!plan.approval || controlPlaneDigest(projectedApproval(approvalReceipt)) !== controlPlaneDigest(plan.approval)) {
1402
+ throw new ControlPlaneStoreError('invalid-state', 'runtime epoch historical approval changed');
1403
+ }
1404
+ const witness = hostInputWitnessFromRow(database, plan);
1405
+ if (!witness || witness.activationId !== plan.activation.id || witness.fence !== plan.activation.fence) {
1406
+ throw new ControlPlaneStoreError('invalid-state', 'runtime epoch deployment witness changed');
1407
+ }
1408
+ const readinessRow = database.prepare(`SELECT operation.* FROM host_attestation_operations operation
1409
+ JOIN host_attestations attestation ON attestation.plan_id=operation.plan_id AND attestation.phase=operation.phase
1410
+ AND attestation.receipt_digest=operation.receipt_digest
1411
+ WHERE operation.plan_id=? AND operation.phase='readiness' AND operation.status='applied'`).get(plan.id);
1412
+ if (!readinessRow)
1413
+ throw new ControlPlaneStoreError('invalid-state', 'runtime epoch lacks original applied readiness');
1414
+ const readiness = hostOperationFromRow(readinessRow);
1415
+ if (!readiness.receipt || readiness.receipt.outcome !== 'passed' || readiness.receipt.activationId !== plan.activation.id
1416
+ || readiness.receipt.fence !== plan.activation.fence || readiness.receipt.hostGeneration !== watch.last_host_generation) {
1417
+ throw new ControlPlaneStoreError('conflict', 'runtime epoch original readiness changed');
1418
+ }
1419
+ return { ...adopted, handoff, approvalReceipt, witness, readiness,
1420
+ maintenance: readHostMaintenanceContext(database, plan.id).records };
1421
+ }
1422
+ /** Read-only authority seam; current request admission never extends the historical activation authorization. */
1423
+ export function readOwnerRuntimeEpochContext(database, operationId) {
1424
+ const row = database.prepare('SELECT * FROM deployment_runtime_epochs WHERE operation_id=?').get(operationId);
1425
+ if (!row)
1426
+ throw new ControlPlaneStoreError('not-found', 'runtime epoch operation absent');
1427
+ const record = runtimeEpochFromRow(row), request = record.request;
1428
+ if (record.status !== 'claimed')
1429
+ throw new ControlPlaneStoreError('conflict', 'runtime epoch operation is not claimed');
1430
+ const context = readCurrentRuntimeEpochDeployment(database, request.profile.path), { plan, readiness } = context;
1431
+ const latest = database.prepare('SELECT max(sequence) AS sequence FROM deployment_runtime_epochs WHERE plan_id=?').get(plan.id);
1432
+ if (latest?.sequence !== request.sequence || request.plan.id !== plan.id || request.plan.digest !== plan.digest
1433
+ || request.installationId !== plan.installationId || controlPlaneDigest(request.ledger) !== controlPlaneDigest(plan.ledger)
1434
+ || request.profile.name !== plan.profile || request.activation.id !== plan.activation.id || request.activation.fence !== plan.activation.fence
1435
+ || controlPlaneDigest(request.predecessor) !== controlPlaneDigest({ operationId: readiness.operationId,
1436
+ receiptDigest: controlPlaneDigest(readiness.receipt), hostGeneration: readiness.receipt.hostGeneration })) {
1437
+ throw new ControlPlaneStoreError('conflict', 'runtime epoch request lost current deployment admission');
1438
+ }
1439
+ return { ...context, request, status: 'claimed', createdAt: row.created_at };
1440
+ }
1441
+ export function readOwnerHostAttestationContext(database, operationId) {
1442
+ if (database.prepare('PRAGMA user_version').get()?.user_version !== controlPlaneSchemaVersion
1443
+ || typeof operationId !== 'string' || !KEY.test(operationId)) {
1444
+ throw new ControlPlaneStoreError('invalid-state', 'Host authority database or operation identity is invalid');
1445
+ }
1446
+ const operationRow = database.prepare('SELECT * FROM host_attestation_operations WHERE operation_id=?')
1447
+ .get(operationId);
1448
+ if (!operationRow)
1449
+ throw new ControlPlaneStoreError('not-found', 'Host authority operation is absent');
1450
+ const parsed = hostOperationFromRow(operationRow);
1451
+ if (!isBoundHostRequest(parsed.request) || parsed.status !== 'pending' || parsed.receipt !== undefined
1452
+ || operationRow.completed_at !== null || operationRow.applied_at !== null) {
1453
+ throw new ControlPlaneStoreError('invalid-state', 'Host authority requires an uncompleted schema-2 operation');
1454
+ }
1455
+ const operation = parsed;
1456
+ const { plan, sourcePlan, source, released } = readOwnerSourceAdoptionPlan(database, operation.planId);
1457
+ const request = operation.request, activation = plan.activation, expected = expectedAttestation[plan.status];
1458
+ if (!activation || expected?.phase !== operation.phase || request.phase !== operation.phase
1459
+ || request.operationId !== operation.operationId || request.plan.id !== plan.id || request.plan.digest !== plan.digest
1460
+ || request.installationId !== plan.installationId || controlPlaneDigest(request.ledger) !== controlPlaneDigest(plan.ledger)
1461
+ || request.profile.name !== plan.profile || request.profile.path !== plan.target.profilePath
1462
+ || request.activation.id !== activation.id || request.activation.fence !== activation.fence
1463
+ || request.requestedAt !== operation.createdAt || request.receiptTtlMs < 1_000 || request.receiptTtlMs > 300_000) {
1464
+ throw new ControlPlaneStoreError('conflict', 'Host authority operation lost its current plan binding');
1465
+ }
1466
+ const dispatchRow = database.prepare('SELECT * FROM host_attestation_dispatches WHERE operation_id=?')
1467
+ .get(operationId);
1468
+ if (!dispatchRow || dispatchRow.status !== 'claimed' || dispatchRow.completed_at !== null
1469
+ || !Number.isSafeInteger(dispatchRow.claimed_at) || dispatchRow.claimed_at < operation.createdAt) {
1470
+ throw new ControlPlaneStoreError('conflict', 'Host authority operation was not durably claimed');
1471
+ }
1472
+ const handoffRow = database.prepare('SELECT * FROM adoption_handoffs WHERE plan_id=?').get(plan.id);
1473
+ const terms = plan.dossier.handoff;
1474
+ if (!handoffRow || !terms || handoffRow.plan_digest !== plan.digest || handoffRow.coordinator_id !== terms.coordinatorId
1475
+ || !Number.isSafeInteger(handoffRow.created_at) || handoffRow.created_at < plan.createdAt
1476
+ || handoffRow.expires_at !== Math.min(handoffRow.created_at + terms.maximumWindowMs, plan.expiresAt, plan.approval?.expiresAt ?? 0)) {
1477
+ throw new ControlPlaneStoreError('invalid-state', 'Host authority handoff is not bound to the owner plan');
1478
+ }
1479
+ validateAdoptionHandoffTerms(terms);
1480
+ const handoff = { planId: plan.id, planDigest: plan.digest, coordinatorId: terms.coordinatorId,
1481
+ createdAt: handoffRow.created_at, expiresAt: handoffRow.expires_at,
1482
+ ...(handoffRow.revoked_at === null ? {} : { revokedAt: handoffRow.revoked_at }) };
1483
+ const planRow = database.prepare('SELECT approval_receipt_json FROM activation_plans WHERE id=?').get(plan.id);
1484
+ let approvalReceipt;
1485
+ try {
1486
+ approvalReceipt = parseApprovalReceipt(JSON.parse(planRow.approval_receipt_json ?? 'null'));
1487
+ }
1488
+ catch {
1489
+ throw new ControlPlaneStoreError('invalid-state', 'Host authority approval receipt is corrupt');
1490
+ }
1491
+ if (!plan.approval || controlPlaneDigest(projectedApproval(approvalReceipt)) !== controlPlaneDigest(plan.approval)) {
1492
+ throw new ControlPlaneStoreError('invalid-state', 'Host authority approval projection changed');
1493
+ }
1494
+ const rollback = operation.phase === 'rollback';
1495
+ const now = Date.now();
1496
+ if (!rollback && (now >= plan.expiresAt || now >= plan.approval.expiresAt || now >= handoff.expiresAt
1497
+ || handoff.revokedAt !== undefined))
1498
+ throw new ControlPlaneStoreError('expired', 'Host authority forward authorization expired');
1499
+ const witness = hostInputWitnessFromRow(database, plan);
1500
+ if (!witness || witness.activationId !== activation.id || (!rollback && witness.fence !== activation.fence)) {
1501
+ throw new ControlPlaneStoreError('conflict', 'Host authority lacks exact deployment witness');
1502
+ }
1503
+ if (rollback) {
1504
+ if (!activation.hostRecoveryRequired || !activation.rollbackProfileRestored || activation.targetBaselineFiles === undefined
1505
+ || request.requirements.kind !== 'rollback'
1506
+ || controlPlaneDigest(request.requirements.baselineFiles) !== controlPlaneDigest(activation.targetBaselineFiles)
1507
+ || request.requirements.action !== (activation.targetOriginallyExisted ? 'restore' : 'stop')) {
1508
+ throw new ControlPlaneStoreError('conflict', 'Host rollback lacks a durable physical recovery obligation');
1509
+ }
1510
+ const checkpoint = database.prepare('SELECT exposure_order,successful_order FROM activation_deployment_checkpoints WHERE plan_id=?')
1511
+ .get(plan.id);
1512
+ const postActivated = ['post-activation-regressed', 'post-activation-retracted', 'live-qualification-invalidated']
1513
+ .includes(activation.failureCode ?? '');
1514
+ if (postActivated && (!checkpoint || checkpoint.exposure_order !== checkpoint.successful_order)) {
1515
+ throw new ControlPlaneStoreError('conflict', 'post-activation rollback lacks a successful deployment checkpoint');
1516
+ }
1517
+ if (postActivated) {
1518
+ const readinessRow = database.prepare(`SELECT o.* FROM host_attestation_operations o
1519
+ JOIN host_attestations a ON a.plan_id=o.plan_id AND a.phase=o.phase AND a.receipt_digest=o.receipt_digest
1520
+ WHERE o.plan_id=? AND o.phase='readiness' AND o.status='applied'`).get(plan.id);
1521
+ const readiness = readinessRow && hostOperationFromRow(readinessRow);
1522
+ if (!readiness?.receipt || readiness.receipt.outcome !== 'passed'
1523
+ || readiness.request.activation.id !== activation.id || readiness.request.activation.fence !== witness.fence
1524
+ || readiness.receipt.activationId !== activation.id || readiness.receipt.fence !== witness.fence) {
1525
+ throw new ControlPlaneStoreError('conflict', 'post-activation rollback lacks original applied readiness');
1526
+ }
1527
+ }
1528
+ const active = database.prepare(`SELECT 1 FROM activation_plans WHERE target_path=? AND id<>? AND status IN
1529
+ ('staging','awaiting-reload','awaiting-readiness','awaiting-live-tasks','awaiting-effect-blocked-replay','awaiting-shadow',
1530
+ 'awaiting-canary','awaiting-soak','awaiting-health','commit-pending','rollback-pending') LIMIT 1`)
1531
+ .get(plan.target.profilePath, plan.id);
1532
+ const superseded = checkpoint && database.prepare(`SELECT 1 FROM activation_deployment_checkpoints AS c
1533
+ JOIN activation_plans AS p ON p.id=c.plan_id WHERE p.target_path=? AND c.successful_order IS NOT NULL
1534
+ AND c.exposure_order>? LIMIT 1`).get(plan.target.profilePath, checkpoint.exposure_order);
1535
+ if (active || superseded)
1536
+ throw new ControlPlaneStoreError('conflict', 'Host rollback target has a newer owner');
1537
+ }
1538
+ const latestRow = database.prepare(`SELECT max(generation) AS generation FROM (
1539
+ SELECT max(a.host_generation) AS generation FROM host_attestations a JOIN activation_plans p ON p.id=a.plan_id WHERE p.installation_id=?
1540
+ UNION ALL SELECT max(w.last_host_generation) AS generation FROM activation_watch w JOIN activation_plans p ON p.id=w.plan_id WHERE p.installation_id=?)`)
1541
+ .get(plan.installationId, plan.installationId);
1542
+ const latest = latestRow.generation ?? 0;
1543
+ if ((operation.phase === 'reload' && (request.predecessor !== null || request.requirements.kind !== 'reload'
1544
+ || request.requirements.previousHostGeneration !== latest))
1545
+ || (rollback && (request.requirements.kind !== 'rollback' || request.requirements.previousHostGeneration !== latest))) {
1546
+ throw new ControlPlaneStoreError('conflict', 'Host authority generation changed after operation reservation');
1547
+ }
1548
+ if (operation.phase !== 'reload') {
1549
+ const predecessor = request.predecessor;
1550
+ const priorPhase = rollback ? undefined : predecessorPhase[operation.phase];
1551
+ const priorRows = database.prepare(`SELECT o.* FROM host_attestation_operations AS o
1552
+ JOIN host_attestations AS a ON a.plan_id=o.plan_id AND a.phase=o.phase AND a.receipt_digest=o.receipt_digest
1553
+ WHERE o.plan_id=? AND o.status='applied' ${priorPhase === undefined ? '' : 'AND o.phase=?'}
1554
+ ORDER BY o.applied_at DESC,
1555
+ CASE o.phase WHEN 'reload' THEN 1 WHEN 'readiness' THEN 2 WHEN 'effect-blocked-replay' THEN 3
1556
+ WHEN 'shadow' THEN 4 WHEN 'canary' THEN 5 WHEN 'soak' THEN 6 WHEN 'health' THEN 7 WHEN 'rollback' THEN 8 END DESC`)
1557
+ .all(...(priorPhase === undefined ? [plan.id] : [plan.id, priorPhase]));
1558
+ let actual = null;
1559
+ for (const priorRow of priorRows) {
1560
+ const prior = hostOperationFromRow(priorRow), receipt = prior.receipt;
1561
+ if (!receipt || priorRow.receipt_digest === null || prior.request.activation.id !== activation.id
1562
+ || receipt.activationId !== activation.id
1563
+ || (!rollback && (prior.request.activation.fence !== activation.fence || receipt.fence !== activation.fence))
1564
+ || (rollback && (prior.request.activation.fence >= activation.fence || receipt.fence >= activation.fence)))
1565
+ continue;
1566
+ actual = { operationId: prior.operationId, receiptId: receipt.receiptId, phase: prior.phase,
1567
+ receiptDigest: priorRow.receipt_digest, hostGeneration: receipt.hostGeneration };
1568
+ break;
1569
+ }
1570
+ if (controlPlaneDigest(actual) !== controlPlaneDigest(predecessor)) {
1571
+ throw new ControlPlaneStoreError('conflict', 'Host authority predecessor is no longer current');
1572
+ }
1573
+ if (predecessor !== null) {
1574
+ const prior = database.prepare(`SELECT o.*, a.host_generation AS applied_generation FROM host_attestation_operations o
1575
+ JOIN host_attestations a ON a.plan_id=o.plan_id AND a.phase=o.phase AND a.receipt_digest=o.receipt_digest
1576
+ WHERE o.operation_id=? AND o.plan_id=? AND o.status='applied'`).get(predecessor.operationId, plan.id);
1577
+ if (!prior)
1578
+ throw new ControlPlaneStoreError('conflict', 'Host authority predecessor is not applied');
1579
+ const predecessorOperation = hostOperationFromRow(prior);
1580
+ if (!predecessorOperation.receipt || predecessorOperation.receipt.receiptId !== predecessor.receiptId
1581
+ || predecessorOperation.phase !== predecessor.phase || prior.receipt_digest !== predecessor.receiptDigest
1582
+ || prior.applied_generation !== predecessor.hostGeneration
1583
+ || predecessorOperation.request.activation.id !== activation.id
1584
+ || (!rollback && (predecessor.phase !== predecessorPhase[operation.phase] || predecessor.hostGeneration !== latest
1585
+ || predecessorOperation.receipt.outcome !== 'passed'))
1586
+ || (rollback && predecessorOperation.request.activation.fence >= activation.fence && witness.fence < activation.fence)) {
1587
+ throw new ControlPlaneStoreError('conflict', 'Host authority predecessor binding changed');
1588
+ }
1589
+ }
1590
+ else if (!rollback)
1591
+ throw new ControlPlaneStoreError('conflict', 'Host authority normal phase lacks predecessor');
1592
+ }
1593
+ return { plan, sourcePlan, source, released, handoff, approvalReceipt, operation,
1594
+ dispatch: { status: 'claimed', claimedAt: dispatchRow.claimed_at }, witness };
1595
+ }
1596
+ function liveQualificationRecord(db, id) {
1597
+ const row = db.prepare('SELECT * FROM live_qualification_batches WHERE id=?').get(id);
1598
+ if (!row)
1599
+ return undefined;
1600
+ if (Buffer.byteLength(row.batch_json) > 262_144)
1601
+ throw new ControlPlaneStoreError('invalid-state', 'live qualification batch too large');
1602
+ const batch = JSON.parse(row.batch_json);
1603
+ assertLiveQualificationBatch(batch);
1604
+ const receipt = row.receipt_json === null ? undefined : parseLiveQualificationReceipt(JSON.parse(row.receipt_json));
1605
+ if (row.id !== batch.id || row.plan_id !== batch.planId || row.batch_digest !== controlPlaneDigest(batch)
1606
+ || (receipt && (row.receipt_digest !== controlPlaneDigest(receipt) || receipt.batchId !== batch.id || receipt.batchDigest !== batch.digest))
1607
+ || (row.state === 'applied' && !receipt))
1608
+ throw new ControlPlaneStoreError('invalid-state', 'live qualification journal changed');
1609
+ return { batch, state: row.state, ...(receipt ? { receipt } : {}) };
1610
+ }
1611
+ function liveQualificationReadiness(db, plan) {
1612
+ const row = db.prepare(`SELECT operation.* FROM host_attestation_operations operation
1613
+ JOIN host_attestations attestation ON attestation.plan_id=operation.plan_id AND attestation.phase=operation.phase
1614
+ AND attestation.receipt_digest=operation.receipt_digest
1615
+ WHERE operation.plan_id=? AND operation.phase='readiness' AND operation.status='applied'`).get(plan.id);
1616
+ if (!row)
1617
+ throw new ControlPlaneStoreError('invalid-state', 'live qualification lacks applied readiness');
1618
+ const operation = hostOperationFromRow(row), receipt = operation.receipt;
1619
+ const generation = db.prepare('SELECT MAX(host_generation) AS generation FROM host_attestations WHERE plan_id IN (SELECT id FROM activation_plans WHERE installation_id=?)').get(plan.installationId);
1620
+ if (!receipt || receipt.outcome !== 'passed' || operation.request.schemaVersion !== 2
1621
+ || receipt.planDigest !== plan.digest || receipt.activationId !== plan.activation?.id || receipt.fence !== plan.activation?.fence
1622
+ || receipt.hostGeneration !== generation.generation || operation.request.activation.fence !== receipt.fence) {
1623
+ throw new ControlPlaneStoreError('conflict', 'live qualification readiness generation changed');
1624
+ }
1625
+ return operation;
1626
+ }
1627
+ function liveQualificationWindow(db, plan) {
1628
+ const row = db.prepare('SELECT * FROM live_qualification_windows WHERE plan_id=?').get(plan.id);
1629
+ const terms = plan.dossier.liveQualification;
1630
+ if (!terms || !row || row.activation_id !== plan.activation?.id || row.fence !== plan.activation?.fence
1631
+ || row.deadline_at <= row.started_at || row.deadline_at > row.started_at + terms.maximumWindowMs
1632
+ || row.deadline_at > plan.expiresAt || row.deadline_at > (plan.approval?.expiresAt ?? 0)) {
1633
+ throw new ControlPlaneStoreError('invalid-state', 'live qualification window changed');
1634
+ }
1635
+ const readiness = liveQualificationReadiness(db, plan);
1636
+ return { startedAt: row.started_at, deadlineAt: row.deadline_at,
1637
+ readinessDigest: controlPlaneDigest(readiness.receipt), hostGeneration: readiness.receipt.hostGeneration };
1638
+ }
1639
+ /** Independent signers use this read-only seam, never a migrating Store. */
1640
+ export function readLiveQualificationContext(db, id) {
1641
+ if (Number(db.prepare('PRAGMA user_version').get()?.user_version) !== controlPlaneSchemaVersion)
1642
+ throw new ControlPlaneStoreError('invalid-state', 'live qualification schema differs');
1643
+ const record = liveQualificationRecord(db, id);
1644
+ if (!record)
1645
+ throw new ControlPlaneStoreError('not-found', 'live qualification batch absent');
1646
+ const { batch } = record;
1647
+ const { plan, source } = readOwnerSourceAdoptionPlan(db, batch.planId);
1648
+ const window = liveQualificationWindow(db, plan);
1649
+ if (!['awaiting-live-tasks', 'commit-pending', 'activated'].includes(plan.status)
1650
+ || !plan.dossier.liveQualification || batch.planDigest !== plan.digest || batch.installationId !== plan.installationId
1651
+ || batch.profilePath !== plan.target.profilePath || batch.activationId !== plan.activation?.id || batch.fence !== plan.activation?.fence
1652
+ || controlPlaneDigest(batch.terms) !== controlPlaneDigest(plan.dossier.liveQualification)
1653
+ || ['authorityId', 'authorityHash', 'principalId', 'principalRecordId', 'principalVersion', 'workspace', 'agentPreset'].some(key => batch.owner[key] !== source.owner[key])
1654
+ || batch.startedAt !== window.startedAt || batch.deadlineAt !== window.deadlineAt
1655
+ || batch.readinessDigest !== window.readinessDigest || batch.hostGeneration !== window.hostGeneration
1656
+ || db.prepare('SELECT 1 FROM live_qualification_invalidations WHERE plan_id=?').get(plan.id)) {
1657
+ throw new ControlPlaneStoreError('conflict', 'live qualification deployment or owner changed');
1658
+ }
1659
+ const competing = db.prepare(`SELECT 1 FROM activation_plans WHERE target_path=? AND id<>? AND status IN
1660
+ ('staging','awaiting-reload','awaiting-readiness','awaiting-live-tasks','awaiting-effect-blocked-replay','awaiting-shadow',
1661
+ 'awaiting-canary','awaiting-soak','awaiting-health','commit-pending','rollback-pending')`).get(plan.target.profilePath, plan.id);
1662
+ if (competing)
1663
+ throw new ControlPlaneStoreError('conflict', 'live qualification target superseded');
1664
+ if (plan.status === 'activated') {
1665
+ const latest = db.prepare(`SELECT checkpoint.plan_id FROM activation_deployment_checkpoints checkpoint
1666
+ JOIN activation_plans candidate ON candidate.id=checkpoint.plan_id WHERE candidate.target_path=? ORDER BY checkpoint.exposure_order DESC LIMIT 1`).get(plan.target.profilePath);
1667
+ if (latest?.plan_id !== plan.id)
1668
+ throw new ControlPlaneStoreError('conflict', 'live qualification deployment superseded');
1669
+ }
1670
+ const deployments = batch.votes.map(vote => {
1671
+ const row = db.prepare('SELECT record_json,record_digest,plan_id FROM foreground_deployments WHERE inbox_id=?').get(vote.inboxId);
1672
+ if (!row || row.plan_id !== plan.id || Buffer.byteLength(row.record_json) > 262_144)
1673
+ throw new ControlPlaneStoreError('invalid-state', 'live task witness absent');
1674
+ const deployment = JSON.parse(row.record_json);
1675
+ assertForegroundDeployment(deployment);
1676
+ if (controlPlaneDigest(deployment) !== row.record_digest || row.record_digest !== vote.deploymentDigest
1677
+ || deployment.state !== 'observed' || !deployment.execution || !deployment.execution.quiescent
1678
+ || deployment.execution.status !== 'succeeded' || deployment.execution.modelSelectionState !== 'frozen' || !deployment.execution.modelSelection
1679
+ || deployment.task.inboxId !== vote.inboxId || deployment.execution.completedAt !== vote.completedAt
1680
+ || deployment.task.dispatchedAt < window.startedAt || deployment.task.dispatchedAt >= window.deadlineAt
1681
+ || vote.completedAt >= window.deadlineAt || deployment.task.owner.principalRecordId !== batch.owner.principalRecordId
1682
+ || deployment.task.owner.principalVersion !== batch.owner.principalVersion || deployment.task.scope.workspace !== batch.owner.workspace
1683
+ || deployment.task.scope.preset !== batch.owner.agentPreset || deployment.readiness.planDigest !== plan.digest
1684
+ || deployment.readiness.planId !== plan.id || deployment.readiness.fence !== batch.fence
1685
+ || deployment.readiness.hostGeneration !== batch.hostGeneration || deployment.readiness.receiptDigest !== batch.readinessDigest
1686
+ || controlPlaneDigest(deployment.readiness.exact) !== controlPlaneDigest({ package: plan.candidate.package, version: plan.candidate.version, integrity: plan.candidate.integrity })) {
1687
+ throw new ControlPlaneStoreError('conflict', 'live task witness differs');
1688
+ }
1689
+ return deployment;
1690
+ });
1691
+ return { record, plan, source, deployments };
1692
+ }
1026
1693
  export class ControlPlaneStore {
1027
1694
  #database;
1028
1695
  #now;
1029
1696
  #ownerTaskFailureGapAdmission;
1030
1697
  #foregroundDeploymentAdmission;
1698
+ #liveQualificationAdmission;
1699
+ #withLiveQualificationFence;
1031
1700
  #withOwnerActivationFence;
1032
1701
  #adoptionCoordinatorId;
1033
1702
  constructor(options) {
@@ -1036,8 +1705,9 @@ export class ControlPlaneStore {
1036
1705
  }
1037
1706
  if (options.adoptionCoordinatorId && options.withOwnerActivationFence)
1038
1707
  throw new ControlPlaneStoreError('invalid-input', 'adoption coordinator cannot hold owner fence');
1039
- this.#database = openControlPlaneDatabase(options.path);
1708
+ this.#database = options.readOnly === true ? openControlPlaneDatabaseReadOnly(options.path) : openControlPlaneDatabase(options.path);
1040
1709
  this.#now = options.now ?? Date.now;
1710
+ this.#withLiveQualificationFence = options.withLiveQualificationFence;
1041
1711
  this.#withOwnerActivationFence = options.withOwnerActivationFence;
1042
1712
  this.#adoptionCoordinatorId = options.adoptionCoordinatorId;
1043
1713
  }
@@ -1177,7 +1847,7 @@ export class ControlPlaneStore {
1177
1847
  this.#assertAdoptionHandoffBinding(planId, this.#adoptionCoordinatorId);
1178
1848
  return callback();
1179
1849
  }
1180
- if (!['approved', 'staging', 'awaiting-reload', 'awaiting-readiness', 'awaiting-effect-blocked-replay',
1850
+ if (!['approved', 'staging', 'awaiting-reload', 'awaiting-readiness', 'awaiting-live-tasks', 'awaiting-effect-blocked-replay',
1181
1851
  'awaiting-shadow', 'awaiting-canary', 'awaiting-soak', 'awaiting-health'].includes(plan.status)) {
1182
1852
  throw new ControlPlaneStoreError('invalid-state', 'adoption coordinator cannot advance this activation state');
1183
1853
  }
@@ -1190,6 +1860,10 @@ export class ControlPlaneStore {
1190
1860
  const current = this.getPlan(planId);
1191
1861
  if (current.status === 'commit-pending' && current.dossier.handoff !== undefined)
1192
1862
  this.#assertAdoptionHandoff(planId);
1863
+ if (current.status === 'commit-pending' && current.dossier.liveQualification) {
1864
+ this.#assertLiveQualificationCommit(planId);
1865
+ return callback();
1866
+ }
1193
1867
  return callback();
1194
1868
  };
1195
1869
  if (this.#ownerTaskFailureGapAdmission === plan.gapId)
@@ -1240,9 +1914,17 @@ export class ControlPlaneStore {
1240
1914
  const requestBinding = { operation: 'create-activation-plan', gapId: input.gapId, candidate, catalog: input.catalog,
1241
1915
  matchedCapabilities, profile, target: input.target, installationId: input.installationId,
1242
1916
  ledger: input.ledger, executor: input.executor, ttlMs: input.ttlMs,
1243
- ...(input.sourcePlanId === undefined ? {} : { sourcePlanId: input.sourcePlanId }), ...(input.handoff === undefined ? {} : { handoff: input.handoff }) };
1917
+ ...(input.sourcePlanId === undefined ? {} : { sourcePlanId: input.sourcePlanId }), ...(input.handoff === undefined ? {} : { handoff: input.handoff }), ...(input.liveQualification === undefined ? {} : { liveQualification: input.liveQualification }),
1918
+ ...(input.hostDeploymentInputs === undefined ? {} : { hostDeploymentInputs: input.hostDeploymentInputs }) };
1244
1919
  this.#assertOwnerTaskFailureGapAdmission(input.gapId);
1245
1920
  const source = input.sourcePlanId === undefined ? undefined : readOwnerPreparedSourcePlan(this.#database, input.sourcePlanId);
1921
+ if (input.liveQualification !== undefined) {
1922
+ validateLiveQualificationTerms(input.liveQualification);
1923
+ if (!source || !input.handoff)
1924
+ throw new ControlPlaneStoreError('invalid-input', 'bounded-live adoption requires an exact owner release and external coordinator');
1925
+ }
1926
+ if (input.hostDeploymentInputs !== undefined)
1927
+ validateHostDeploymentInputs(input.hostDeploymentInputs);
1246
1928
  if (input.handoff !== undefined) {
1247
1929
  validateAdoptionHandoffTerms(input.handoff);
1248
1930
  if (!source)
@@ -1272,7 +1954,9 @@ export class ControlPlaneStore {
1272
1954
  const gapSnapshot = Object.freeze({ revision: gap.revision, inputDigest: gap.inputDigest, roi: gap.roi, capability: gap.capability });
1273
1955
  const dossier = Object.freeze({ catalogDigest: input.catalog.digest, catalogProvenance: input.catalog.provenance,
1274
1956
  matchedCapabilities: Object.freeze(matchedCapabilities), authorities: Object.freeze([...candidate.authorities]), packages,
1275
- ...(input.handoff === undefined ? {} : { handoff: Object.freeze({ ...input.handoff }) }) });
1957
+ ...(input.handoff === undefined ? {} : { handoff: Object.freeze({ ...input.handoff }) }),
1958
+ ...(input.liveQualification === undefined ? {} : { liveQualification: Object.freeze({ ...input.liveQualification }) }),
1959
+ ...(input.hostDeploymentInputs === undefined ? {} : { hostDeploymentInputs: Object.freeze([...input.hostDeploymentInputs]) }) });
1276
1960
  const now = this.#now();
1277
1961
  const id = `plugin-${randomUUID()}`;
1278
1962
  const expiresAt = now + input.ttlMs;
@@ -1320,7 +2004,11 @@ export class ControlPlaneStore {
1320
2004
  const row = this.#database.prepare('SELECT * FROM activation_plans WHERE id = ?').get(id);
1321
2005
  if (row === undefined)
1322
2006
  throw new ControlPlaneStoreError('not-found', 'activation plan not found');
1323
- return activationFromRow(row);
2007
+ return effectiveActivationFromRow(this.#database, row);
2008
+ }
2009
+ currentPlanExecutor(planId) {
2010
+ const context = readHostMaintenanceContext(this.#database, planId);
2011
+ return context.records.at(-1)?.after.executor ?? context.plan.executor;
1324
2012
  }
1325
2013
  findSourceAdoption(sourcePlanId) {
1326
2014
  const row = this.#database.prepare('SELECT activation_plan_id FROM source_adoptions WHERE source_plan_id = ?').get(sourcePlanId);
@@ -1431,11 +2119,22 @@ export class ControlPlaneStore {
1431
2119
  const intentDigest = controlPlaneDigest(intent);
1432
2120
  const now = this.#now();
1433
2121
  this.#assertOwnerTaskFailureGapAdmission(intent.gapId);
2122
+ if (intent.mode === 'create' && this.getOwnerTaskFailureReference(intent.gapId) !== undefined
2123
+ && intent.creation?.growthRun === undefined) {
2124
+ throw new ControlPlaneStoreError('invalid-input', 'task-backed creation requires a frozen source growth run');
2125
+ }
1434
2126
  if (intent.containerName !== `dsh-${input.id}` || basename(intent.worktree) !== `worktree-job-${input.id.slice('source-job-'.length)}`) {
1435
2127
  throw new ControlPlaneStoreError('invalid-input', 'source job resource identity is invalid');
1436
2128
  }
1437
2129
  if (now >= intent.authority.expiresAt)
1438
2130
  throw new ControlPlaneStoreError('expired', 'source job authority is expired');
2131
+ if (intent.creation !== undefined && now >= intent.creation.grant.expiresAt)
2132
+ throw new ControlPlaneStoreError('expired', 'source creation grant is expired');
2133
+ if (intent.revision !== undefined && (this.getOwnerTaskFailureReference(intent.gapId) === undefined || now >= intent.revision.grant.expiresAt))
2134
+ throw new ControlPlaneStoreError('expired', 'revision grant or owner task is unavailable');
2135
+ const growthRun = intent.creation?.growthRun ?? intent.revision?.growthRun;
2136
+ if (growthRun !== undefined && (now >= growthRun.generationDeadlineAt || now >= growthRun.expiresAt))
2137
+ throw new ControlPlaneStoreError('expired', 'source growth generation window is expired');
1439
2138
  this.#database.exec('BEGIN IMMEDIATE');
1440
2139
  try {
1441
2140
  const prior = this.#database.prepare('SELECT * FROM source_jobs WHERE authority_id = ? AND idempotency_key = ?')
@@ -1448,6 +2147,30 @@ export class ControlPlaneStore {
1448
2147
  this.#database.exec('COMMIT');
1449
2148
  return record;
1450
2149
  }
2150
+ if (intent.baseline !== undefined) {
2151
+ const rows = this.#database.prepare(`SELECT * FROM source_jobs WHERE json_extract(intent_json, '$.repository') = ?
2152
+ AND json_type(intent_json, '$.baseline') IS NOT NULL AND status IN ('queued', 'running', 'unknown', 'prepared')
2153
+ LIMIT 1001`).all(intent.repository);
2154
+ if (rows.length > 1000)
2155
+ throw new ControlPlaneStoreError('invalid-state', 'managed source job cohort exceeds bound');
2156
+ for (const row of rows) {
2157
+ const competing = sourceJobFromRow(row);
2158
+ if (competing.status !== 'prepared')
2159
+ throw new ControlPlaneStoreError('conflict', 'managed source job is already active for repository');
2160
+ const prepared = competing.planId === undefined ? undefined : this.getSourcePlan(competing.planId);
2161
+ // A revision rebuilds its exact retained parent in a new private tree;
2162
+ // it cannot advance that parent's source release or alter the baseline.
2163
+ if (intent.revision && prepared?.id === intent.revision.parent.planId && prepared.mode === 'prepared-create') {
2164
+ const certificate = this.getCreationVerification(prepared.id);
2165
+ if (certificate && controlPlaneDigest(certificate) === intent.revision.parent.certificateDigest
2166
+ && certificate.plan.artifactSha256 === intent.revision.parent.artifactSha256)
2167
+ continue;
2168
+ }
2169
+ if (prepared === undefined || !['release-complete', 'release-failed', 'expired', 'local-checks-failed'].includes(prepared.status)) {
2170
+ throw new ControlPlaneStoreError('conflict', 'managed source job is awaiting a terminal source release');
2171
+ }
2172
+ }
2173
+ }
1451
2174
  const authority = this.#database.prepare('SELECT * FROM source_job_authorities WHERE authority_id = ?').get(intent.authority.id);
1452
2175
  if (authority !== undefined && (authority.authority_digest !== intent.authority.digest || authority.expires_at !== intent.authority.expiresAt
1453
2176
  || authority.max_submissions !== intent.authority.maxSubmissions)) {
@@ -1461,6 +2184,36 @@ export class ControlPlaneStore {
1461
2184
  }
1462
2185
  this.#database.prepare('UPDATE source_job_authorities SET submissions = submissions + 1 WHERE authority_id = ? AND submissions < max_submissions')
1463
2186
  .run(intent.authority.id);
2187
+ if (intent.creation !== undefined) {
2188
+ const grant = intent.creation.grant, digest = controlPlaneDigest(grant);
2189
+ const previous = this.#database.prepare('SELECT * FROM source_creation_grants WHERE grant_id = ?').get(grant.id);
2190
+ if (previous !== undefined && (previous.grant_digest !== digest || previous.expires_at !== grant.expiresAt
2191
+ || previous.max_creates !== grant.maxCreates))
2192
+ throw new ControlPlaneStoreError('conflict', 'source creation grant definition is immutable');
2193
+ if (previous !== undefined && previous.creates >= previous.max_creates)
2194
+ throw new ControlPlaneStoreError('invalid-state', 'source creation grant quota is exhausted');
2195
+ if (previous === undefined)
2196
+ this.#database.prepare(`INSERT INTO source_creation_grants
2197
+ (grant_id, grant_digest, expires_at, max_creates, creates) VALUES (?, ?, ?, ?, 0)`)
2198
+ .run(grant.id, digest, grant.expiresAt, grant.maxCreates);
2199
+ const quota = this.#database.prepare('UPDATE source_creation_grants SET creates = creates + 1 WHERE grant_id = ? AND creates < max_creates').run(grant.id);
2200
+ if (Number(quota.changes) !== 1)
2201
+ throw new ControlPlaneStoreError('conflict', 'source creation grant quota changed');
2202
+ }
2203
+ if (intent.revision !== undefined) {
2204
+ const grant = intent.revision.grant, digest = controlPlaneDigest(grant);
2205
+ const previous = this.#database.prepare('SELECT * FROM source_revision_grants WHERE grant_id=?').get(grant.id);
2206
+ if (previous && (previous.grant_digest !== digest || previous.expires_at !== grant.expiresAt || previous.max_revisions !== grant.maxRevisions)) {
2207
+ throw new ControlPlaneStoreError('conflict', 'source revision grant definition is immutable');
2208
+ }
2209
+ if (previous && previous.revisions >= previous.max_revisions)
2210
+ throw new ControlPlaneStoreError('invalid-state', 'source revision grant quota is exhausted');
2211
+ if (!previous)
2212
+ this.#database.prepare('INSERT INTO source_revision_grants VALUES(?,?,?,?,0)').run(grant.id, digest, grant.expiresAt, grant.maxRevisions);
2213
+ const quota = this.#database.prepare('UPDATE source_revision_grants SET revisions=revisions+1 WHERE grant_id=? AND revisions<max_revisions').run(grant.id);
2214
+ if (Number(quota.changes) !== 1)
2215
+ throw new ControlPlaneStoreError('conflict', 'source revision grant quota changed');
2216
+ }
1464
2217
  this.#database.prepare(`INSERT INTO source_jobs (id, automation_id, authority_id, idempotency_key, intent_json, intent_digest,
1465
2218
  status, revision, created_at, expires_at, definition_hash, occurrence_id, plan_id, failure_code, updated_at)
1466
2219
  VALUES (?, ?, ?, ?, ?, ?, 'queued', 1, ?, ?, NULL, NULL, NULL, NULL, ?)`).run(input.id, input.automationId, intent.authority.id, idempotencyKey, JSON.stringify(intent), intentDigest, now, intent.authority.expiresAt, now);
@@ -1491,34 +2244,67 @@ export class ControlPlaneStore {
1491
2244
  created_at DESC, id DESC LIMIT ?`).all(limit).map(sourceJobFromRow);
1492
2245
  }
1493
2246
  /** Prepared owner continuations have a bounded recovery query, independent of job history. */
1494
- listPreparedSourceApprovalJobs(includeRelease = false, includeExecution = false, includeAdoption = false) {
2247
+ listPreparedSourceApprovalJobs(includeRelease = false, includeExecution = false, includeAdoption = false, includeCreation = false, includeCreationAdoption = false, includeRevision = false, includeRegression = false) {
1495
2248
  return this.#database.prepare(`SELECT j.* FROM source_jobs j JOIN source_plans p ON p.id = j.plan_id
1496
2249
  JOIN owner_task_failure_gaps g ON g.gap_id = p.gap_id
1497
2250
  LEFT JOIN source_adoptions a ON a.source_plan_id = p.id
1498
2251
  LEFT JOIN activation_plans ap ON ap.id = a.activation_plan_id
1499
- WHERE j.status = 'prepared' AND (p.status = 'pending-approval' OR (? = 1 AND p.status IN ('approved', 'ready-for-human-review'))
2252
+ WHERE j.status = 'prepared' AND ((p.mode = 'modify' AND (p.status = 'pending-approval' OR (? = 1 AND p.status IN ('approved', 'ready-for-human-review'))
1500
2253
  OR (? = 1 AND p.status IN ('awaiting-pr', 'awaiting-review', 'awaiting-merge', 'awaiting-build', 'awaiting-sign', 'awaiting-publish',
1501
2254
  'awaiting-registry-verify', 'awaiting-catalog-admission'))
1502
- OR (? = 1 AND p.status = 'release-complete' AND (ap.id IS NULL OR ap.status NOT IN ('activated', 'rolled-back', 'rejected')))) AND (p.expires_at > ? OR ap.status IN ('staging', 'awaiting-reload', 'awaiting-readiness', 'awaiting-effect-blocked-replay', 'awaiting-shadow', 'awaiting-canary', 'awaiting-soak', 'awaiting-health', 'commit-pending', 'rollback-pending'))
1503
- ORDER BY j.created_at, j.id LIMIT 1000`).all(includeRelease ? 1 : 0, includeExecution ? 1 : 0, includeAdoption ? 1 : 0, this.#now()).map(sourceJobFromRow);
2255
+ OR (? = 1 AND p.status = 'release-complete' AND (ap.id IS NULL OR ap.status NOT IN ('activated', 'rolled-back', 'rejected')))))
2256
+ OR (? = 1 AND p.mode = 'prepared-create' AND p.status = 'pending-approval'
2257
+ AND json_type(j.intent_json, '$.creation.growthRun.creationAcceptance') IS NOT NULL
2258
+ AND NOT EXISTS (SELECT 1 FROM source_creation_verifications v WHERE v.plan_id = p.id))
2259
+ OR (? = 1 AND p.mode = 'prepared-create' AND p.status = 'pending-approval'
2260
+ AND EXISTS (SELECT 1 FROM source_creation_verifications v WHERE v.plan_id = p.id AND v.status = 'verified'))
2261
+ OR (? = 1 AND p.mode = 'prepared-revise' AND p.status = 'pending-approval'
2262
+ AND json_type(j.intent_json, '$.revision.growthRun.revisionAcceptance') IS NOT NULL
2263
+ AND NOT EXISTS (SELECT 1 FROM source_revision_verifications v WHERE v.plan_id=p.id))
2264
+ OR (? = 1 AND p.mode = 'prepared-revise' AND p.status = 'pending-approval'
2265
+ AND json_type(j.intent_json, '$.revision.growthRun.revisionRegressionAcceptance') IS NOT NULL
2266
+ AND EXISTS (SELECT 1 FROM source_revision_verifications v WHERE v.plan_id=p.id AND v.status='verified')
2267
+ AND NOT EXISTS (SELECT 1 FROM source_revision_regressions v WHERE v.plan_id=p.id)))
2268
+ AND (p.expires_at > ? OR ap.status IN ('staging', 'awaiting-reload', 'awaiting-readiness', 'awaiting-live-tasks', 'awaiting-effect-blocked-replay', 'awaiting-shadow', 'awaiting-canary', 'awaiting-soak', 'awaiting-health', 'commit-pending', 'rollback-pending'))
2269
+ ORDER BY j.created_at, j.id LIMIT 1000`).all(includeRelease ? 1 : 0, includeExecution ? 1 : 0, includeAdoption ? 1 : 0, includeCreation ? 1 : 0, includeCreationAdoption ? 1 : 0, includeRevision ? 1 : 0, includeRegression ? 1 : 0, this.#now()).map(sourceJobFromRow);
1504
2270
  }
1505
2271
  bindSourceJobDefinition(input) {
1506
2272
  if (!Number.isSafeInteger(input.revision) || input.revision < 1 || !DIGEST.test(input.definitionHash))
1507
2273
  throw new ControlPlaneStoreError('invalid-input', 'source job definition binding is invalid');
1508
2274
  const now = this.#now();
1509
- const result = this.#database.prepare(`UPDATE source_jobs SET definition_hash = ?, revision = revision + 1, updated_at = ?
2275
+ const result = this.#database.prepare(`UPDATE source_jobs SET definition_hash = ?, previous_definition_hash = NULL,
2276
+ previous_definition_version = NULL, revision = revision + 1, updated_at = ?
1510
2277
  WHERE id = ? AND revision = ? AND status = 'queued' AND definition_hash IS NULL AND expires_at > ?`).run(input.definitionHash, now, input.id, input.revision, now);
1511
2278
  if (Number(result.changes) !== 1)
1512
2279
  throw new ControlPlaneStoreError('conflict', 'source job definition binding lost its queued CAS');
1513
2280
  return this.getSourceJob(input.id);
1514
2281
  }
2282
+ /** Persist the only safe rearm: same queued job, with no claimed occurrence. */
2283
+ rearmQueuedGrowthSourceJob(input) {
2284
+ if (!Number.isSafeInteger(input.revision) || input.revision < 1 || !DIGEST.test(input.priorDefinitionHash)
2285
+ || !Number.isSafeInteger(input.priorDefinitionVersion) || input.priorDefinitionVersion < 1
2286
+ || !Number.isSafeInteger(input.dispatchAt) || input.dispatchAt <= this.#now()) {
2287
+ throw new ControlPlaneStoreError('invalid-input', 'source growth rearm request is invalid');
2288
+ }
2289
+ const now = this.#now();
2290
+ const result = this.#database.prepare(`UPDATE source_jobs SET dispatch_at = ?, previous_definition_hash = definition_hash,
2291
+ previous_definition_version = ?,
2292
+ definition_hash = NULL, revision = revision + 1, updated_at = ? WHERE id = ? AND revision = ? AND status = 'queued'
2293
+ AND occurrence_id IS NULL AND definition_hash = ? AND expires_at > ? AND (json_type(intent_json, '$.creation.growthRun') IS NOT NULL OR json_type(intent_json, '$.revision.growthRun') IS NOT NULL)`).run(input.dispatchAt, input.priorDefinitionVersion, now, input.id, input.revision, input.priorDefinitionHash, now);
2294
+ if (Number(result.changes) !== 1)
2295
+ throw new ControlPlaneStoreError('conflict', 'source growth rearm lost its queued CAS');
2296
+ return this.getSourceJob(input.id);
2297
+ }
1515
2298
  claimSourceJob(input) {
1516
2299
  if (!Number.isSafeInteger(input.revision) || input.revision < 1 || !DIGEST.test(input.definitionHash) || !KEY.test(input.occurrenceId)) {
1517
2300
  throw new ControlPlaneStoreError('invalid-input', 'source job claim is invalid');
1518
2301
  }
1519
2302
  const now = this.#now();
1520
2303
  const result = this.#database.prepare(`UPDATE source_jobs SET status = 'running', occurrence_id = ?, revision = revision + 1, updated_at = ?
1521
- WHERE id = ? AND revision = ? AND status = 'queued' AND definition_hash = ? AND occurrence_id IS NULL AND expires_at > ?`).run(input.occurrenceId, now, input.id, input.revision, input.definitionHash, now);
2304
+ WHERE id = ? AND revision = ? AND status = 'queued' AND definition_hash = ? AND occurrence_id IS NULL AND expires_at > ?
2305
+ AND (json_type(intent_json, '$.creation') IS NULL OR json_extract(intent_json, '$.creation.grant.expiresAt') > ?)
2306
+ AND (json_type(intent_json, '$.revision') IS NULL OR json_extract(intent_json, '$.revision.grant.expiresAt') > ?)`)
2307
+ .run(input.occurrenceId, now, input.id, input.revision, input.definitionHash, now, now, now);
1522
2308
  if (Number(result.changes) !== 1)
1523
2309
  throw new ControlPlaneStoreError('conflict', 'source job claim lost its queued CAS');
1524
2310
  return this.getSourceJob(input.id);
@@ -1528,7 +2314,8 @@ export class ControlPlaneStore {
1528
2314
  throw new ControlPlaneStoreError('invalid-input', 'source job settlement is invalid');
1529
2315
  const permitted = input.status === 'unknown' ? "status = 'running'" : "status IN ('queued', 'running', 'unknown')";
1530
2316
  const now = this.#now();
1531
- const result = this.#database.prepare(`UPDATE source_jobs SET status = ?, failure_code = ?, revision = revision + 1, updated_at = ?
2317
+ const result = this.#database.prepare(`UPDATE source_jobs SET status = ?, failure_code = ?,
2318
+ previous_definition_hash = NULL, previous_definition_version = NULL, revision = revision + 1, updated_at = ?
1532
2319
  WHERE id = ? AND revision = ? AND ${permitted}`).run(input.status, input.failureCode, now, input.id, input.revision);
1533
2320
  if (Number(result.changes) !== 1)
1534
2321
  throw new ControlPlaneStoreError('conflict', 'source job settlement lost its state CAS');
@@ -1540,10 +2327,22 @@ export class ControlPlaneStore {
1540
2327
  revision = revision + 1, updated_at = ? WHERE status = 'running'`).run(now);
1541
2328
  return Number(result.changes);
1542
2329
  }
2330
+ /** Revoke claimed task-created jobs when their Host run producer disappears. */
2331
+ interruptGrowthSourceJobs() {
2332
+ const now = this.#now();
2333
+ const result = this.#database.prepare(`UPDATE source_jobs SET status = 'unknown', failure_code = 'source-growth-provider-disposed',
2334
+ revision = revision + 1, updated_at = ? WHERE status = 'running'
2335
+ AND (json_type(intent_json, '$.creation.growthRun') IS NOT NULL OR json_type(intent_json, '$.revision.growthRun') IS NOT NULL)`).run(now);
2336
+ return Number(result.changes);
2337
+ }
1543
2338
  createSourcePlan(input) {
1544
2339
  const mode = input.mode ?? 'create';
1545
- if (input.sourceJob !== undefined && mode !== 'modify')
1546
- throw new ControlPlaneStoreError('invalid-input', 'source jobs only complete checked modify plans');
2340
+ if (mode !== 'create' && mode !== 'modify' && mode !== 'prepared-create' && mode !== 'prepared-revise')
2341
+ throw new ControlPlaneStoreError('invalid-input', 'source plan mode is invalid');
2342
+ if (input.sourceJob !== undefined && mode === 'create')
2343
+ throw new ControlPlaneStoreError('invalid-input', 'source jobs only complete checked modify or prepared-create plans');
2344
+ if ((mode === 'prepared-create' || mode === 'prepared-revise') && input.sourceJob === undefined)
2345
+ throw new ControlPlaneStoreError('invalid-input', 'prepared creation requires a running source job');
1547
2346
  const key = bounded(input.idempotencyKey, 'idempotencyKey', 160);
1548
2347
  const name = bounded(input.name, 'name', 64);
1549
2348
  if (!KEY.test(key) || !PLUGIN_NAME.test(name) || !COMMIT.test(input.baseCommit) || !DIGEST.test(input.generatorDigest)
@@ -1552,10 +2351,20 @@ export class ControlPlaneStore {
1552
2351
  if (mode === 'modify' && input.generatorDigest !== MODIFY_GENERATOR_DIGEST) {
1553
2352
  throw new ControlPlaneStoreError('invalid-input', 'modify source plans must bind the modify generator constant');
1554
2353
  }
2354
+ const creation = input.creation === undefined ? undefined : creationBindingFromStored(input.creation);
2355
+ if ((mode === 'prepared-create') !== (creation !== undefined)
2356
+ || (creation !== undefined && (creation.generatorDigest !== input.generatorDigest || !name.startsWith(creation.grant.namePrefix)))) {
2357
+ throw new ControlPlaneStoreError('invalid-input', 'prepared source creation binding is invalid');
2358
+ }
2359
+ const revision = input.revision === undefined ? undefined : revisionBindingFromStored(input.revision);
2360
+ if ((mode === 'prepared-revise') !== (revision !== undefined)
2361
+ || (revision !== undefined && (revision.generatorDigest !== input.generatorDigest || !name.startsWith(revision.grant.namePrefix)))) {
2362
+ throw new ControlPlaneStoreError('invalid-input', 'prepared source revision binding is invalid');
2363
+ }
1555
2364
  let preparedEvidence;
1556
- if (mode === 'modify') {
2365
+ if (mode === 'modify' || mode === 'prepared-create' || mode === 'prepared-revise') {
1557
2366
  if (input.prepared === undefined) {
1558
- throw new ControlPlaneStoreError('invalid-input', 'modify source plan requires prepared check evidence');
2367
+ throw new ControlPlaneStoreError('invalid-input', 'prepared source plan requires check evidence');
1559
2368
  }
1560
2369
  if (!DIGEST.test(input.prepared.treeDigest) || !DIGEST.test(input.prepared.patchDigest)
1561
2370
  || !Number.isSafeInteger(input.prepared.checkedAt) || input.prepared.checkedAt < 0) {
@@ -1566,6 +2375,20 @@ export class ControlPlaneStore {
1566
2375
  else if (input.prepared !== undefined) {
1567
2376
  throw new ControlPlaneStoreError('invalid-input', 'create source plans cannot carry prepared evidence');
1568
2377
  }
2378
+ let preparedArtifact;
2379
+ if (input.preparedArtifact !== undefined) {
2380
+ if (mode !== 'prepared-create' && mode !== 'prepared-revise' || input.sourceJob === undefined || !Buffer.isBuffer(input.preparedArtifact)
2381
+ || preparedEvidence === undefined || input.preparedArtifact.length < 1
2382
+ || input.preparedArtifact.length > MAX_PREPARED_ARTIFACT_BYTES
2383
+ || input.preparedArtifact.length !== preparedEvidence.pack.sizeBytes
2384
+ || createHash('sha256').update(input.preparedArtifact).digest('hex') !== preparedEvidence.pack.sha256) {
2385
+ throw new ControlPlaneStoreError('invalid-input', 'prepared source artifact differs from checked package evidence');
2386
+ }
2387
+ if (this.getOwnerTaskFailureReference(input.gapId) === undefined) {
2388
+ throw new ControlPlaneStoreError('invalid-state', 'prepared source artifact requires an owner task failure gap');
2389
+ }
2390
+ preparedArtifact = Buffer.from(input.preparedArtifact);
2391
+ }
1569
2392
  positiveInteger(input.ttlMs, 'ttlMs');
1570
2393
  if (input.ttlMs < 60_000 || input.ttlMs > 86_400_000)
1571
2394
  throw new ControlPlaneStoreError('invalid-input', 'ttlMs is invalid');
@@ -1582,11 +2405,24 @@ export class ControlPlaneStore {
1582
2405
  this.#assertOwnerTaskFailureGapAdmission(input.gapId);
1583
2406
  const sourceJobBinding = input.sourceJob === undefined ? undefined : { jobId: input.sourceJob.jobId, jobRevision: input.sourceJob.jobRevision,
1584
2407
  occurrenceId: input.sourceJob.occurrenceId };
1585
- const inputDigest = controlPlaneDigest({ ...(mode === 'modify' ? { ...requestBinding, mode, prepared: input.prepared } : requestBinding),
1586
- ...(sourceJobBinding === undefined ? {} : { sourceJob: sourceJobBinding }) });
2408
+ const inputDigest = controlPlaneDigest({ ...(mode === 'create' ? requestBinding : { ...requestBinding, mode,
2409
+ ...(creation === undefined ? {} : { creation }), ...(revision === undefined ? {} : { revision }), prepared: input.prepared }),
2410
+ ...(sourceJobBinding === undefined ? {} : { sourceJob: sourceJobBinding }),
2411
+ ...(preparedArtifact === undefined ? {} : { preparedArtifactSha256: preparedEvidence.pack.sha256 }) });
1587
2412
  const prior = this.#sourcePlanReceiptByKey(key, 'create-source-plan', inputDigest);
1588
- if (prior !== undefined)
2413
+ if (prior !== undefined) {
2414
+ if (preparedArtifact !== undefined) {
2415
+ const reference = this.#database.prepare('SELECT pack_sha256 FROM source_prepared_artifact_refs WHERE plan_id = ?')
2416
+ .get(prior.result.id);
2417
+ const row = this.#database.prepare('SELECT * FROM source_prepared_artifacts WHERE pack_sha256 = ?')
2418
+ .get(preparedEvidence.pack.sha256);
2419
+ if (reference?.pack_sha256 !== preparedEvidence.pack.sha256 || row === undefined
2420
+ || !preparedArtifactBytesFromRow(row, preparedEvidence.pack).equals(preparedArtifact)) {
2421
+ throw new ControlPlaneStoreError('invalid-state', 'idempotent prepared source artifact is missing or changed');
2422
+ }
2423
+ }
1589
2424
  return prior;
2425
+ }
1590
2426
  const gap = this.getGap(input.gapId);
1591
2427
  if (gap.status !== 'open' || gap.candidateId !== undefined) {
1592
2428
  throw new ControlPlaneStoreError('invalid-state', 'only an unreserved open gap can create a source plan');
@@ -1598,8 +2434,8 @@ export class ControlPlaneStore {
1598
2434
  const immutable = { schemaVersion: 1, kind: 'source', id, gapId: gap.id, gapSnapshot,
1599
2435
  repository: input.repository, worktree: input.worktree, baseCommit: input.baseCommit, name,
1600
2436
  generatorDigest: input.generatorDigest, scope, createdAt: now, expiresAt };
1601
- const digest = controlPlaneDigest(mode === 'modify'
1602
- ? { ...immutable, mode, sourceCheck: { treeDigest: input.prepared.treeDigest, patchDigest: input.prepared.patchDigest,
2437
+ const digest = controlPlaneDigest(mode !== 'create'
2438
+ ? { ...immutable, mode, ...(creation === undefined ? {} : { creation }), ...(revision === undefined ? {} : { sourceRevision: revision }), sourceCheck: { treeDigest: input.prepared.treeDigest, patchDigest: input.prepared.patchDigest,
1603
2439
  checkedAt: input.prepared.checkedAt }, preparedEvidence }
1604
2440
  : immutable);
1605
2441
  this.#database.exec('BEGIN IMMEDIATE');
@@ -1615,11 +2451,42 @@ export class ControlPlaneStore {
1615
2451
  || sourceJob.occurrenceId !== sourceJobBinding.occurrenceId || sourceJob.intent.gapId !== gap.id
1616
2452
  || sourceJob.intent.gapRevision !== currentGap.revision || sourceJob.intent.gapDigest !== controlPlaneDigest(currentGap)
1617
2453
  || sourceJob.intent.baseCommit !== input.baseCommit || sourceJob.intent.name !== name
1618
- || sourceJob.intent.repository !== input.repository || sourceJob.intent.worktree !== input.worktree || sourceJob.intent.ttlMs !== input.ttlMs) {
2454
+ || sourceJob.intent.repository !== input.repository || sourceJob.intent.worktree !== input.worktree || sourceJob.intent.ttlMs !== input.ttlMs
2455
+ || (mode === 'prepared-create') !== (sourceJob.intent.mode === 'create')
2456
+ || (mode === 'prepared-create' && controlPlaneDigest(sourceJob.intent.creation) !== controlPlaneDigest(creation))
2457
+ || (mode === 'prepared-revise') !== (sourceJob.intent.mode === 'revise-created')
2458
+ || (mode === 'prepared-revise' && controlPlaneDigest(sourceJob.intent.revision) !== controlPlaneDigest(revision))) {
1619
2459
  throw new ControlPlaneStoreError('conflict', 'source job completion lost its immutable running binding');
1620
2460
  }
1621
- if (now >= sourceJob.expiresAt)
2461
+ if (now >= sourceJob.expiresAt || (creation !== undefined && now >= creation.grant.expiresAt) || (revision !== undefined && now >= revision.grant.expiresAt))
1622
2462
  throw new ControlPlaneStoreError('expired', 'source job authority is expired');
2463
+ if (mode === 'prepared-create' && this.getOwnerTaskFailureReference(input.gapId) !== undefined
2464
+ && (!creation?.growthRun || now >= creation.growthRun.expiresAt)) {
2465
+ throw new ControlPlaneStoreError('expired', 'task-backed creation growth run is absent or expired');
2466
+ }
2467
+ }
2468
+ if (revision !== undefined && (this.getOwnerTaskFailureReference(input.gapId) === undefined || now >= revision.growthRun.expiresAt)) {
2469
+ throw new ControlPlaneStoreError('expired', 'revision requires a current owner task and frozen growth run');
2470
+ }
2471
+ if (preparedArtifact !== undefined) {
2472
+ const pack = preparedEvidence.pack;
2473
+ const existing = this.#database.prepare('SELECT * FROM source_prepared_artifacts WHERE pack_sha256 = ?')
2474
+ .get(pack.sha256);
2475
+ if (existing !== undefined) {
2476
+ if (!preparedArtifactBytesFromRow(existing, pack).equals(preparedArtifact)) {
2477
+ throw new ControlPlaneStoreError('invalid-state', 'stored prepared source artifact differs from checked bytes');
2478
+ }
2479
+ }
2480
+ else {
2481
+ const totals = this.#database.prepare('SELECT count(*) AS count, coalesce(sum(size_bytes), 0) AS total FROM source_prepared_artifacts')
2482
+ .get();
2483
+ if (!Number.isSafeInteger(totals.count) || !Number.isSafeInteger(totals.total)
2484
+ || totals.count >= MAX_STORED_ARTIFACTS || totals.total > MAX_STORED_ARTIFACT_BYTES - preparedArtifact.length) {
2485
+ throw new ControlPlaneStoreError('invalid-state', 'prepared source artifact storage quota is exhausted');
2486
+ }
2487
+ this.#database.prepare('INSERT INTO source_prepared_artifacts (pack_sha256, size_bytes, bytes, created_at) VALUES (?, ?, ?, ?)')
2488
+ .run(pack.sha256, preparedArtifact.length, preparedArtifact, now);
2489
+ }
1623
2490
  }
1624
2491
  this.#database.prepare('INSERT INTO gap_plan_claims (gap_id, plan_id, plan_kind, claimed_at) VALUES (?, ?, ?, ?)').run(gap.id, id, 'source', now);
1625
2492
  // 'modify' rows carry their checked digests and frozen-build evidence from
@@ -1627,8 +2494,12 @@ export class ControlPlaneStore {
1627
2494
  // post-approval scaffold run finishes.
1628
2495
  this.#database.prepare(`INSERT INTO source_plans (id, plan_digest, gap_id, gap_snapshot_json, repository, worktree,
1629
2496
  base_commit, plugin_name, generator_digest, scope_json, mode, status, revision, created_at, expires_at,
1630
- approval_json, checked_tree_digest, checked_patch_digest, checked_at, prepared_evidence_json, updated_at)
1631
- 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), mode, now, expiresAt, input.prepared?.treeDigest ?? null, input.prepared?.patchDigest ?? null, input.prepared?.checkedAt ?? null, preparedEvidence === undefined ? null : JSON.stringify(preparedEvidence), now);
2497
+ approval_json, checked_tree_digest, checked_patch_digest, checked_at, prepared_evidence_json, creation_json, revision_json, updated_at)
2498
+ 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), mode, now, expiresAt, input.prepared?.treeDigest ?? null, input.prepared?.patchDigest ?? null, input.prepared?.checkedAt ?? null, preparedEvidence === undefined ? null : JSON.stringify(preparedEvidence), creation === undefined ? null : JSON.stringify(creation), revision === undefined ? null : JSON.stringify(revision), now);
2499
+ if (preparedArtifact !== undefined) {
2500
+ this.#database.prepare('INSERT INTO source_prepared_artifact_refs (plan_id, pack_sha256) VALUES (?, ?)')
2501
+ .run(id, preparedEvidence.pack.sha256);
2502
+ }
1632
2503
  const matched = this.#database.prepare(`UPDATE capability_gaps SET status = 'matched', revision = revision + 1, updated_at = ? WHERE id = ? AND revision = ?`).run(now, gap.id, gap.revision);
1633
2504
  if (Number(matched.changes) !== 1)
1634
2505
  throw new ControlPlaneStoreError('conflict', 'capability gap changed while source plan was created');
@@ -1658,6 +2529,735 @@ export class ControlPlaneStore {
1658
2529
  throw new ControlPlaneStoreError('not-found', 'source plan not found');
1659
2530
  return sourceFromRow(row);
1660
2531
  }
2532
+ /** Exact durable source job for a currently admitted prepared creation. */
2533
+ getPreparedSourceJob(planId) {
2534
+ const plan = this.getSourcePlan(planId);
2535
+ if (plan.mode !== 'prepared-create' || plan.status !== 'pending-approval'
2536
+ || plan.creation === undefined || plan.preparedEvidence === undefined
2537
+ || this.#now() >= plan.expiresAt || this.#now() >= plan.creation.grant.expiresAt) {
2538
+ throw new ControlPlaneStoreError('invalid-state', 'prepared creation requires a current pending plan');
2539
+ }
2540
+ const owner = this.getOwnerTaskFailureReference(plan.gapId);
2541
+ if (owner === undefined)
2542
+ throw new ControlPlaneStoreError('invalid-state', 'prepared creation requires an owner task failure gap');
2543
+ this.#assertOwnerTaskFailureGapAdmission(plan.gapId);
2544
+ return this.getRetainedPreparedCreation(planId).job;
2545
+ }
2546
+ /** Read-only exact historical binding for an already signed adoption. Caller must hold the current owner fence. */
2547
+ getRetainedPreparedCreation(planId) {
2548
+ const plan = this.getSourcePlan(planId);
2549
+ if (plan.mode !== 'prepared-create' || !['pending-approval', 'expired'].includes(plan.status)
2550
+ || plan.creation === undefined || plan.preparedEvidence === undefined || plan.sourceCheck === undefined) {
2551
+ throw new ControlPlaneStoreError('invalid-state', 'retained creation requires exact prepared evidence');
2552
+ }
2553
+ const owner = this.getOwnerTaskFailureReference(plan.gapId);
2554
+ if (owner === undefined)
2555
+ throw new ControlPlaneStoreError('invalid-state', 'retained creation requires an owner task failure gap');
2556
+ const row = this.#database.prepare('SELECT * FROM source_jobs WHERE plan_id = ?')
2557
+ .get(plan.id);
2558
+ if (row === undefined)
2559
+ throw new ControlPlaneStoreError('invalid-state', 'prepared creation has no source job');
2560
+ const job = sourceJobFromRow(row);
2561
+ const gap = this.getGap(plan.gapId);
2562
+ if (job.status !== 'prepared' || job.planId !== plan.id || job.intent.mode !== 'create'
2563
+ || job.intent.creation === undefined || job.intent.gapId !== plan.gapId
2564
+ || job.intent.gapRevision !== plan.gapSnapshot.revision
2565
+ || gap.inputDigest !== plan.gapSnapshot.inputDigest
2566
+ || job.intent.ownerDigest !== controlPlaneDigest(owner.owner)
2567
+ || controlPlaneDigest(job.intent.owner) !== job.intent.ownerDigest
2568
+ || controlPlaneDigest(job.intent.creation) !== controlPlaneDigest(plan.creation)
2569
+ || job.intent.creation.generatorDigest !== plan.generatorDigest
2570
+ || job.intent.repository !== plan.repository || job.intent.worktree !== plan.worktree
2571
+ || job.intent.baseCommit !== plan.baseCommit || job.intent.name !== plan.name
2572
+ || plan.expiresAt !== plan.createdAt + job.intent.ttlMs) {
2573
+ throw new ControlPlaneStoreError('invalid-state', 'prepared creation source job binding is corrupt');
2574
+ }
2575
+ return { plan, job, reference: owner };
2576
+ }
2577
+ /** Exact durable source job for a currently admitted prepared revision. */
2578
+ getPreparedRevisionJob(planId) {
2579
+ const plan = this.getSourcePlan(planId);
2580
+ if (plan.mode !== 'prepared-revise' || plan.status !== 'pending-approval'
2581
+ || plan.sourceRevision === undefined || plan.preparedEvidence === undefined
2582
+ || this.#now() >= plan.expiresAt || this.#now() >= plan.sourceRevision.grant.expiresAt) {
2583
+ throw new ControlPlaneStoreError('invalid-state', 'prepared revision requires a current pending plan');
2584
+ }
2585
+ const owner = this.getOwnerTaskFailureReference(plan.gapId);
2586
+ if (owner === undefined)
2587
+ throw new ControlPlaneStoreError('invalid-state', 'prepared revision requires an owner task failure gap');
2588
+ this.#assertOwnerTaskFailureGapAdmission(plan.gapId);
2589
+ return this.getRetainedPreparedRevision(planId).job;
2590
+ }
2591
+ /** Read-only exact historical binding for an already signed adoption. Caller must hold the current owner fence. */
2592
+ getRetainedPreparedRevision(planId) {
2593
+ const plan = this.getSourcePlan(planId);
2594
+ if (plan.mode !== 'prepared-revise' || !['pending-approval', 'expired'].includes(plan.status)
2595
+ || plan.sourceRevision === undefined || plan.preparedEvidence === undefined || plan.sourceCheck === undefined) {
2596
+ throw new ControlPlaneStoreError('invalid-state', 'retained revision requires exact prepared evidence');
2597
+ }
2598
+ const owner = this.getOwnerTaskFailureReference(plan.gapId);
2599
+ if (owner === undefined)
2600
+ throw new ControlPlaneStoreError('invalid-state', 'retained revision requires an owner task failure gap');
2601
+ const row = this.#database.prepare('SELECT * FROM source_jobs WHERE plan_id = ?')
2602
+ .get(plan.id);
2603
+ if (row === undefined)
2604
+ throw new ControlPlaneStoreError('invalid-state', 'prepared revision has no source job');
2605
+ const job = sourceJobFromRow(row);
2606
+ const gap = this.getGap(plan.gapId);
2607
+ if (job.status !== 'prepared' || job.planId !== plan.id || job.intent.mode !== 'revise-created'
2608
+ || job.intent.revision === undefined || job.intent.gapId !== plan.gapId
2609
+ || job.intent.gapRevision !== plan.gapSnapshot.revision
2610
+ || gap.inputDigest !== plan.gapSnapshot.inputDigest
2611
+ || job.intent.ownerDigest !== controlPlaneDigest(owner.owner)
2612
+ || controlPlaneDigest(job.intent.owner) !== job.intent.ownerDigest
2613
+ || controlPlaneDigest(job.intent.revision) !== controlPlaneDigest(plan.sourceRevision)
2614
+ || job.intent.revision.generatorDigest !== plan.generatorDigest
2615
+ || job.intent.repository !== plan.repository || job.intent.worktree !== plan.worktree
2616
+ || job.intent.baseCommit !== plan.baseCommit || job.intent.name !== plan.name
2617
+ || plan.expiresAt !== plan.createdAt + job.intent.ttlMs) {
2618
+ throw new ControlPlaneStoreError('invalid-state', 'prepared revision source job binding is corrupt');
2619
+ }
2620
+ return { plan, job, reference: owner };
2621
+ }
2622
+ /** Host-only package bytes for an owner-admitted, still-pending created plugin. */
2623
+ readPreparedSourceArtifact(planId) {
2624
+ const plan = this.getSourcePlan(planId);
2625
+ if (plan.mode !== 'prepared-create' && plan.mode !== 'prepared-revise' || plan.status !== 'pending-approval'
2626
+ || plan.preparedEvidence === undefined || this.#now() >= plan.expiresAt) {
2627
+ throw new ControlPlaneStoreError('invalid-state', 'prepared source artifact requires a current pending creation plan');
2628
+ }
2629
+ if (plan.mode === 'prepared-revise')
2630
+ this.getPreparedRevisionJob(planId);
2631
+ else
2632
+ this.getPreparedSourceJob(planId);
2633
+ const pack = plan.preparedEvidence.pack;
2634
+ const reference = this.#database.prepare('SELECT pack_sha256 FROM source_prepared_artifact_refs WHERE plan_id = ?')
2635
+ .get(plan.id);
2636
+ if (reference?.pack_sha256 !== pack.sha256)
2637
+ throw new ControlPlaneStoreError('invalid-state', 'prepared source artifact reference is missing or corrupt');
2638
+ const row = this.#database.prepare('SELECT * FROM source_prepared_artifacts WHERE pack_sha256 = ?')
2639
+ .get(pack.sha256);
2640
+ if (row === undefined)
2641
+ throw new ControlPlaneStoreError('invalid-state', 'prepared source artifact is missing');
2642
+ return preparedArtifactBytesFromRow(row, pack);
2643
+ }
2644
+ /** One immutable, independently signed result per exact prepared creation. */
2645
+ recordCreationVerification(certificate) {
2646
+ validatePluginCreationVerificationCertificate(certificate);
2647
+ const plan = this.getSourcePlan(certificate.plan.id);
2648
+ const job = this.getPreparedSourceJob(plan.id);
2649
+ const reference = this.getOwnerTaskFailureReference(plan.gapId);
2650
+ if (!reference || !plan.sourceCheck || !plan.preparedEvidence || !plan.creation?.growthRun
2651
+ || !job.intent.creation?.growthRun || plan.digest !== certificate.plan.digest || plan.name !== certificate.plan.name
2652
+ || plan.sourceCheck.treeDigest !== certificate.plan.sourceTreeDigest
2653
+ || plan.sourceCheck.patchDigest !== certificate.plan.sourcePatchDigest
2654
+ || plan.preparedEvidence.pack.sha256 !== certificate.plan.artifactSha256
2655
+ || plan.preparedEvidence.pack.sizeBytes !== certificate.plan.artifactBytes
2656
+ || plan.generatorDigest !== certificate.plan.generatorDigest
2657
+ || controlPlaneDigest(reference) !== certificate.source.referenceDigest
2658
+ || controlPlaneDigest(reference.owner) !== certificate.source.ownerDigest
2659
+ || sourceGrowthRunDigest(plan.creation.growthRun) !== certificate.source.growthRunDigest
2660
+ || sourceGrowthRunDigest(job.intent.creation.growthRun) !== certificate.source.growthRunDigest
2661
+ || controlPlaneDigest(plan.creation.growthRun.model) !== controlPlaneDigest(certificate.model)
2662
+ || certificate.expiresAt > Math.min(plan.expiresAt, plan.creation.grant.expiresAt, plan.creation.growthRun.expiresAt, certificate.authority.expiresAt)) {
2663
+ throw new ControlPlaneStoreError('invalid-input', 'creation verification certificate does not match exact prepared source');
2664
+ }
2665
+ this.readPreparedSourceArtifact(plan.id);
2666
+ const serialized = JSON.stringify(certificate), certificateDigest = controlPlaneDigest(certificate);
2667
+ this.#database.exec('BEGIN IMMEDIATE');
2668
+ try {
2669
+ const previous = this.#database.prepare('SELECT status,certificate_digest FROM source_creation_verifications WHERE plan_id=?')
2670
+ .get(plan.id);
2671
+ if (previous?.status === 'verified' && previous.certificate_digest === certificateDigest) { /* idempotent */ }
2672
+ else if (previous?.status !== 'claimed')
2673
+ throw new ControlPlaneStoreError('conflict', 'creation verification was not claimed or already settled');
2674
+ else
2675
+ this.#database.prepare(`UPDATE source_creation_verifications SET status='verified',certificate_json=?,certificate_digest=?,updated_at=?
2676
+ WHERE plan_id=? AND status='claimed'`).run(serialized, certificateDigest, this.#now(), plan.id);
2677
+ this.#database.exec('COMMIT');
2678
+ }
2679
+ catch (error) {
2680
+ this.#database.exec('ROLLBACK');
2681
+ throw error;
2682
+ }
2683
+ }
2684
+ getCreationVerification(planId) {
2685
+ const row = this.#database.prepare('SELECT status,certificate_json,certificate_digest FROM source_creation_verifications WHERE plan_id=?')
2686
+ .get(planId);
2687
+ if (!row || row.status !== 'verified')
2688
+ return undefined;
2689
+ let certificate;
2690
+ try {
2691
+ const parsed = JSON.parse(row.certificate_json);
2692
+ validatePluginCreationVerificationCertificate(parsed);
2693
+ certificate = parsed;
2694
+ }
2695
+ catch {
2696
+ throw new ControlPlaneStoreError('invalid-state', 'stored creation verification certificate is corrupt');
2697
+ }
2698
+ if (certificate.plan.id !== planId || controlPlaneDigest(certificate) !== row.certificate_digest) {
2699
+ throw new ControlPlaneStoreError('invalid-state', 'stored creation verification certificate digest changed');
2700
+ }
2701
+ return certificate;
2702
+ }
2703
+ getCreationVerificationStatus(planId) {
2704
+ const row = this.#database.prepare('SELECT status FROM source_creation_verifications WHERE plan_id=?')
2705
+ .get(planId);
2706
+ return row?.status;
2707
+ }
2708
+ /** Content-free diagnostic projection; caller must hold current owner/source fence. */
2709
+ inspectCreationVerificationRecord(planId) {
2710
+ const row = this.#database.prepare('SELECT status,reason,updated_at FROM source_creation_verifications WHERE plan_id=?')
2711
+ .get(planId);
2712
+ if (!row)
2713
+ return undefined;
2714
+ if (!['claimed', 'verified', 'unknown', 'rejected'].includes(row.status)
2715
+ || !Number.isSafeInteger(row.updated_at) || row.updated_at < 1
2716
+ || (row.reason !== null && !/^[a-z][a-z0-9-]{0,79}$/u.test(row.reason))) {
2717
+ throw new ControlPlaneStoreError('invalid-state', 'creation verification diagnostic record is corrupt');
2718
+ }
2719
+ return { status: row.status, ...(row.reason === null ? {} : { reason: row.reason }), updatedAt: row.updated_at };
2720
+ }
2721
+ claimCreationVerification(planId) {
2722
+ const job = this.getPreparedSourceJob(planId);
2723
+ const pinned = job.intent.creation?.growthRun?.creationAcceptance;
2724
+ if (!pinned || this.#now() >= pinned.expiresAt) {
2725
+ throw new ControlPlaneStoreError('invalid-state', 'creation verification requires a current pre-author policy binding');
2726
+ }
2727
+ this.#database.exec('BEGIN IMMEDIATE');
2728
+ try {
2729
+ const count = this.#database.prepare('SELECT count(*) AS n FROM source_creation_verifications').get();
2730
+ if (count.n >= 256)
2731
+ throw new ControlPlaneStoreError('conflict', 'creation verification record limit reached');
2732
+ const now = this.#now();
2733
+ this.#database.prepare(`INSERT INTO source_creation_verifications
2734
+ (plan_id,status,certificate_json,certificate_digest,reason,created_at,updated_at) VALUES (?,'claimed',NULL,NULL,NULL,?,?)`)
2735
+ .run(planId, now, now);
2736
+ this.#database.exec('COMMIT');
2737
+ }
2738
+ catch (error) {
2739
+ this.#database.exec('ROLLBACK');
2740
+ throw error;
2741
+ }
2742
+ }
2743
+ settleCreationVerification(planId, status, reason) {
2744
+ if (typeof reason !== 'string' || !/^[a-z][a-z0-9-]{0,79}$/u.test(reason)) {
2745
+ throw new ControlPlaneStoreError('invalid-input', 'creation verification reason is invalid');
2746
+ }
2747
+ const result = this.#database.prepare(`UPDATE source_creation_verifications SET status=?,reason=?,updated_at=?
2748
+ WHERE plan_id=? AND status='claimed'`).run(status, reason, this.#now(), planId);
2749
+ if (Number(result.changes) !== 1)
2750
+ throw new ControlPlaneStoreError('conflict', 'creation verification claim is not current');
2751
+ }
2752
+ /** One immutable, independently signed result per exact prepared revision. */
2753
+ recordRevisionVerification(certificate, source) {
2754
+ validatePluginRevisionVerificationCertificate(certificate);
2755
+ const plan = this.getSourcePlan(certificate.plan.id);
2756
+ const job = this.getPreparedRevisionJob(plan.id);
2757
+ const reference = this.getOwnerTaskFailureReference(plan.gapId);
2758
+ validateCreationCapabilitySource(source, certificate);
2759
+ if (!reference || controlPlaneDigest(plan.sourceRevision?.parent) !== controlPlaneDigest(certificate.parent) || !plan.sourceCheck || !plan.preparedEvidence || !plan.sourceRevision?.growthRun
2760
+ || !job.intent.revision?.growthRun || plan.digest !== certificate.plan.digest || plan.name !== certificate.plan.name
2761
+ || plan.sourceCheck.treeDigest !== certificate.plan.sourceTreeDigest
2762
+ || plan.sourceCheck.patchDigest !== certificate.plan.sourcePatchDigest
2763
+ || plan.preparedEvidence.pack.sha256 !== certificate.plan.artifactSha256
2764
+ || plan.preparedEvidence.pack.sizeBytes !== certificate.plan.artifactBytes
2765
+ || plan.generatorDigest !== certificate.plan.generatorDigest
2766
+ || controlPlaneDigest(reference) !== certificate.source.referenceDigest
2767
+ || controlPlaneDigest(reference.owner) !== certificate.source.ownerDigest
2768
+ || sourceGrowthRunDigest(plan.sourceRevision.growthRun) !== certificate.source.growthRunDigest
2769
+ || sourceGrowthRunDigest(job.intent.revision.growthRun) !== certificate.source.growthRunDigest
2770
+ || controlPlaneDigest(plan.sourceRevision.growthRun.model) !== controlPlaneDigest(certificate.model)
2771
+ || certificate.expiresAt > Math.min(plan.expiresAt, plan.sourceRevision.grant.expiresAt, plan.sourceRevision.growthRun.expiresAt, certificate.authority.expiresAt)) {
2772
+ throw new ControlPlaneStoreError('invalid-input', 'revision verification certificate does not match exact prepared source');
2773
+ }
2774
+ this.readPreparedSourceArtifact(plan.id);
2775
+ if (source.baseCommit !== plan.baseCommit || controlPlaneDigest(source.scope) !== controlPlaneDigest(plan.scope))
2776
+ throw new ControlPlaneStoreError('invalid-input', 'revision source archive base or scope changed');
2777
+ const sourceJson = JSON.stringify(source);
2778
+ if (Buffer.byteLength(sourceJson) > 2_097_152)
2779
+ throw new ControlPlaneStoreError('invalid-input', 'revision source archive exceeds bound');
2780
+ const serialized = JSON.stringify(certificate), certificateDigest = controlPlaneDigest(certificate);
2781
+ this.#database.exec('BEGIN IMMEDIATE');
2782
+ try {
2783
+ const previous = this.#database.prepare('SELECT status,certificate_digest FROM source_revision_verifications WHERE plan_id=?')
2784
+ .get(plan.id);
2785
+ if (previous?.status === 'verified' && previous.certificate_digest === certificateDigest) { /* idempotent */ }
2786
+ else if (previous?.status !== 'claimed')
2787
+ throw new ControlPlaneStoreError('conflict', 'revision verification was not claimed or already settled');
2788
+ else
2789
+ this.#database.prepare(`UPDATE source_revision_verifications SET status='verified',certificate_json=?,certificate_digest=?,updated_at=?
2790
+ WHERE plan_id=? AND status='claimed'`).run(serialized, certificateDigest, this.#now(), plan.id);
2791
+ const oldSource = this.#database.prepare('SELECT source_json FROM source_revision_sources WHERE plan_id=?').get(plan.id);
2792
+ if (oldSource && oldSource.source_json !== sourceJson)
2793
+ throw new ControlPlaneStoreError('conflict', 'revision source archive changed');
2794
+ if (!oldSource)
2795
+ this.#database.prepare('INSERT INTO source_revision_sources VALUES(?,?)').run(plan.id, sourceJson);
2796
+ this.#database.exec('COMMIT');
2797
+ }
2798
+ catch (error) {
2799
+ this.#database.exec('ROLLBACK');
2800
+ throw error;
2801
+ }
2802
+ }
2803
+ getRevisionVerification(planId) {
2804
+ const row = this.#database.prepare('SELECT status,certificate_json,certificate_digest FROM source_revision_verifications WHERE plan_id=?')
2805
+ .get(planId);
2806
+ if (!row || row.status !== 'verified')
2807
+ return undefined;
2808
+ let certificate;
2809
+ try {
2810
+ const parsed = JSON.parse(row.certificate_json);
2811
+ validatePluginRevisionVerificationCertificate(parsed);
2812
+ certificate = parsed;
2813
+ }
2814
+ catch {
2815
+ throw new ControlPlaneStoreError('invalid-state', 'stored revision verification certificate is corrupt');
2816
+ }
2817
+ if (certificate.plan.id !== planId || controlPlaneDigest(certificate) !== row.certificate_digest) {
2818
+ throw new ControlPlaneStoreError('invalid-state', 'stored revision verification certificate digest changed');
2819
+ }
2820
+ return certificate;
2821
+ }
2822
+ getRevisionVerificationStatus(planId) {
2823
+ const row = this.#database.prepare('SELECT status FROM source_revision_verifications WHERE plan_id=?')
2824
+ .get(planId);
2825
+ return row?.status;
2826
+ }
2827
+ /** Content-free diagnostic projection; caller must hold current owner/source fence. */
2828
+ inspectRevisionVerificationRecord(planId) {
2829
+ const row = this.#database.prepare('SELECT status,reason,updated_at FROM source_revision_verifications WHERE plan_id=?')
2830
+ .get(planId);
2831
+ if (!row)
2832
+ return undefined;
2833
+ if (!['claimed', 'verified', 'unknown', 'rejected'].includes(row.status)
2834
+ || !Number.isSafeInteger(row.updated_at) || row.updated_at < 1
2835
+ || (row.reason !== null && !/^[a-z][a-z0-9-]{0,79}$/u.test(row.reason))) {
2836
+ throw new ControlPlaneStoreError('invalid-state', 'revision verification diagnostic record is corrupt');
2837
+ }
2838
+ return { status: row.status, ...(row.reason === null ? {} : { reason: row.reason }), updatedAt: row.updated_at };
2839
+ }
2840
+ claimRevisionVerification(planId) {
2841
+ const job = this.getPreparedRevisionJob(planId);
2842
+ const pinned = job.intent.revision?.growthRun?.revisionAcceptance;
2843
+ if (!pinned || this.#now() >= pinned.expiresAt) {
2844
+ throw new ControlPlaneStoreError('invalid-state', 'revision verification requires a current pre-author policy binding');
2845
+ }
2846
+ this.#database.exec('BEGIN IMMEDIATE');
2847
+ try {
2848
+ const count = this.#database.prepare('SELECT count(*) AS n FROM source_revision_verifications').get();
2849
+ if (count.n >= 256)
2850
+ throw new ControlPlaneStoreError('conflict', 'revision verification record limit reached');
2851
+ const now = this.#now();
2852
+ this.#database.prepare(`INSERT INTO source_revision_verifications
2853
+ (plan_id,status,certificate_json,certificate_digest,reason,created_at,updated_at) VALUES (?,'claimed',NULL,NULL,NULL,?,?)`)
2854
+ .run(planId, now, now);
2855
+ this.#database.exec('COMMIT');
2856
+ }
2857
+ catch (error) {
2858
+ this.#database.exec('ROLLBACK');
2859
+ throw error;
2860
+ }
2861
+ }
2862
+ settleRevisionVerification(planId, status, reason) {
2863
+ if (typeof reason !== 'string' || !/^[a-z][a-z0-9-]{0,79}$/u.test(reason)) {
2864
+ throw new ControlPlaneStoreError('invalid-input', 'revision verification reason is invalid');
2865
+ }
2866
+ const result = this.#database.prepare(`UPDATE source_revision_verifications SET status=?,reason=?,updated_at=?
2867
+ WHERE plan_id=? AND status='claimed'`).run(status, reason, this.#now(), planId);
2868
+ if (Number(result.changes) !== 1)
2869
+ throw new ControlPlaneStoreError('conflict', 'revision verification claim is not current');
2870
+ }
2871
+ /** Purpose-separated paired held-out proof; never changes plan or adoption state. */
2872
+ recordRevisionRegression(certificate) {
2873
+ validatePluginRevisionRegressionCertificate(certificate);
2874
+ const plan = this.getSourcePlan(certificate.plan.id);
2875
+ const job = this.getPreparedRevisionJob(plan.id);
2876
+ const candidate = this.getRevisionVerification(plan.id);
2877
+ const parent = this.getCreationVerification(certificate.parent.planId);
2878
+ const source = this.readRevisionSource(plan.id);
2879
+ const reference = this.getOwnerTaskFailureReference(plan.gapId);
2880
+ const run = plan.sourceRevision?.growthRun;
2881
+ const previousJobs = new Set([...(parent?.observations ?? []), ...(candidate?.observations ?? [])].map(item => item.jobId));
2882
+ if (certificate.verifiedAt > this.#now() || certificate.expiresAt <= this.#now()
2883
+ || !candidate || !parent || !source || !reference || !run?.revisionRegressionAcceptance
2884
+ || !job.intent.revision?.growthRun.revisionRegressionAcceptance
2885
+ || controlPlaneDigest(certificate.plan) !== controlPlaneDigest(candidate.plan)
2886
+ || controlPlaneDigest(certificate.source) !== controlPlaneDigest(candidate.source)
2887
+ || controlPlaneDigest(certificate.parent) !== controlPlaneDigest(candidate.parent)
2888
+ || certificate.candidateVerificationDigest !== controlPlaneDigest(candidate)
2889
+ || certificate.sourceDigest !== source.digest
2890
+ || certificate.contractDigest !== parent.contractDigest
2891
+ || certificate.schemaDigest !== candidate.schemaDigest
2892
+ || controlPlaneDigest(certificate.environment) !== controlPlaneDigest(candidate.environment)
2893
+ || controlPlaneDigest(certificate.environment) !== controlPlaneDigest(parent.environment)
2894
+ || controlPlaneDigest(certificate.model) !== controlPlaneDigest(run.model)
2895
+ || controlPlaneDigest(certificate.authority) !== controlPlaneDigest(run.revisionRegressionAcceptance)
2896
+ || controlPlaneDigest(certificate.authority) !== controlPlaneDigest(job.intent.revision.growthRun.revisionRegressionAcceptance)
2897
+ || certificate.source.referenceDigest !== controlPlaneDigest(reference)
2898
+ || certificate.source.ownerDigest !== controlPlaneDigest(reference.owner)
2899
+ || certificate.source.growthRunDigest !== sourceGrowthRunDigest(run)
2900
+ || sourceGrowthRunDigest(run) !== sourceGrowthRunDigest(job.intent.revision.growthRun)
2901
+ || certificate.observations.length !== parent.observations.length
2902
+ || certificate.observations.some((item, index) => item.caseId !== parent.observations[index]?.caseId
2903
+ || previousJobs.has(item.parent.jobId) || previousJobs.has(item.candidate.jobId))
2904
+ || certificate.expiresAt > Math.min(candidate.expiresAt, plan.expiresAt, run.expiresAt, plan.sourceRevision.grant.expiresAt, certificate.authority.expiresAt)) {
2905
+ throw new ControlPlaneStoreError('invalid-input', 'regression certificate does not match exact parent and candidate');
2906
+ }
2907
+ this.readPreparedSourceArtifact(plan.id);
2908
+ const serialized = JSON.stringify(certificate), digest = controlPlaneDigest(certificate);
2909
+ this.#database.exec('BEGIN IMMEDIATE');
2910
+ try {
2911
+ const old = this.#database.prepare('SELECT status,certificate_digest FROM source_revision_regressions WHERE plan_id=?')
2912
+ .get(plan.id);
2913
+ if (old?.status === 'verified' && old.certificate_digest === digest) { /* immutable idempotent result */ }
2914
+ else if (old?.status !== 'claimed')
2915
+ throw new ControlPlaneStoreError('conflict', 'regression was not claimed or is settled');
2916
+ else
2917
+ this.#database.prepare(`UPDATE source_revision_regressions SET status='verified',certificate_json=?,certificate_digest=?,updated_at=?
2918
+ WHERE plan_id=? AND status='claimed'`).run(serialized, digest, this.#now(), plan.id);
2919
+ this.#database.exec('COMMIT');
2920
+ }
2921
+ catch (error) {
2922
+ this.#database.exec('ROLLBACK');
2923
+ throw error;
2924
+ }
2925
+ }
2926
+ getRevisionRegression(planId) {
2927
+ const row = this.#database.prepare('SELECT status,certificate_json,certificate_digest FROM source_revision_regressions WHERE plan_id=?')
2928
+ .get(planId);
2929
+ if (!row || row.status !== 'verified')
2930
+ return undefined;
2931
+ let certificate;
2932
+ try {
2933
+ const parsed = JSON.parse(row.certificate_json);
2934
+ validatePluginRevisionRegressionCertificate(parsed);
2935
+ certificate = parsed;
2936
+ }
2937
+ catch {
2938
+ throw new ControlPlaneStoreError('invalid-state', 'stored revision regression certificate is corrupt');
2939
+ }
2940
+ if (certificate.plan.id !== planId || controlPlaneDigest(certificate) !== row.certificate_digest) {
2941
+ throw new ControlPlaneStoreError('invalid-state', 'stored revision regression certificate digest changed');
2942
+ }
2943
+ return certificate;
2944
+ }
2945
+ getRevisionRegressionStatus(planId) {
2946
+ const row = this.#database.prepare('SELECT status FROM source_revision_regressions WHERE plan_id=?')
2947
+ .get(planId);
2948
+ return row?.status;
2949
+ }
2950
+ /** Content-free diagnostic projection; caller must hold current owner/source fence. */
2951
+ inspectRevisionRegressionRecord(planId) {
2952
+ const row = this.#database.prepare('SELECT status,reason,updated_at FROM source_revision_regressions WHERE plan_id=?')
2953
+ .get(planId);
2954
+ if (!row)
2955
+ return undefined;
2956
+ if (!['claimed', 'verified', 'unknown', 'rejected'].includes(row.status)
2957
+ || !Number.isSafeInteger(row.updated_at) || row.updated_at < 1
2958
+ || (row.reason !== null && !/^[a-z][a-z0-9-]{0,79}$/u.test(row.reason))) {
2959
+ throw new ControlPlaneStoreError('invalid-state', 'revision regression diagnostic record is corrupt');
2960
+ }
2961
+ return { status: row.status, ...(row.reason === null ? {} : { reason: row.reason }), updatedAt: row.updated_at };
2962
+ }
2963
+ claimRevisionRegression(planId) {
2964
+ const job = this.getPreparedRevisionJob(planId);
2965
+ const pinned = job.intent.revision?.growthRun?.revisionRegressionAcceptance;
2966
+ if (!pinned || this.#now() >= pinned.expiresAt) {
2967
+ throw new ControlPlaneStoreError('invalid-state', 'revision regression requires a current pre-author policy binding');
2968
+ }
2969
+ this.#database.exec('BEGIN IMMEDIATE');
2970
+ try {
2971
+ const count = this.#database.prepare('SELECT count(*) AS n FROM source_revision_regressions').get();
2972
+ if (count.n >= 256)
2973
+ throw new ControlPlaneStoreError('conflict', 'revision regression record limit reached');
2974
+ const now = this.#now();
2975
+ this.#database.prepare(`INSERT INTO source_revision_regressions
2976
+ (plan_id,status,certificate_json,certificate_digest,reason,created_at,updated_at) VALUES (?,'claimed',NULL,NULL,NULL,?,?)`)
2977
+ .run(planId, now, now);
2978
+ this.#database.exec('COMMIT');
2979
+ }
2980
+ catch (error) {
2981
+ this.#database.exec('ROLLBACK');
2982
+ throw error;
2983
+ }
2984
+ }
2985
+ settleRevisionRegression(planId, status, reason) {
2986
+ if (typeof reason !== 'string' || !/^[a-z][a-z0-9-]{0,79}$/u.test(reason)) {
2987
+ throw new ControlPlaneStoreError('invalid-input', 'revision regression reason is invalid');
2988
+ }
2989
+ const result = this.#database.prepare(`UPDATE source_revision_regressions SET status=?,reason=?,updated_at=?
2990
+ WHERE plan_id=? AND status='claimed'`).run(status, reason, this.#now(), planId);
2991
+ if (Number(result.changes) !== 1)
2992
+ throw new ControlPlaneStoreError('conflict', 'revision regression claim is not current');
2993
+ }
2994
+ readRevisionSource(planId) {
2995
+ const certificate = this.getRevisionVerification(planId);
2996
+ if (!certificate)
2997
+ return undefined;
2998
+ const row = this.#database.prepare('SELECT source_json FROM source_revision_sources WHERE plan_id=?').get(planId);
2999
+ if (!row || Buffer.byteLength(row.source_json) > 2_097_152)
3000
+ throw new ControlPlaneStoreError('invalid-state', 'revision source archive is absent or oversized');
3001
+ const source = JSON.parse(row.source_json);
3002
+ validateCreationCapabilitySource(source, certificate);
3003
+ const plan = this.getSourcePlan(planId);
3004
+ if (source.baseCommit !== plan.baseCommit || controlPlaneDigest(source.scope) !== controlPlaneDigest(plan.scope))
3005
+ throw new ControlPlaneStoreError('invalid-state', 'revision source archive scope changed');
3006
+ return source;
3007
+ }
3008
+ /** Reclaim only orphaned bytes or bytes referenced solely by expired plans. */
3009
+ deleteExpiredPreparedSourceArtifacts(now) {
3010
+ if (!Number.isSafeInteger(now) || now < 0)
3011
+ throw new ControlPlaneStoreError('invalid-input', 'artifact expiry time is invalid');
3012
+ this.#database.exec('BEGIN IMMEDIATE');
3013
+ try {
3014
+ const references = this.#database.prepare('SELECT plan_id, pack_sha256 FROM source_prepared_artifact_refs')
3015
+ .all();
3016
+ for (const reference of references) {
3017
+ const plan = this.getSourcePlan(reference.plan_id);
3018
+ if (plan.mode !== 'prepared-create' && plan.mode !== 'prepared-revise' || plan.preparedEvidence?.pack.sha256 !== reference.pack_sha256) {
3019
+ throw new ControlPlaneStoreError('invalid-state', 'prepared source artifact reference is corrupt');
3020
+ }
3021
+ if (plan.status === 'expired' && plan.expiresAt < now) {
3022
+ this.#database.prepare('DELETE FROM source_prepared_artifact_refs WHERE plan_id = ?').run(plan.id);
3023
+ }
3024
+ }
3025
+ const artifacts = this.#database.prepare(`SELECT a.pack_sha256 FROM source_prepared_artifacts a
3026
+ WHERE NOT EXISTS (SELECT 1 FROM source_prepared_artifact_refs r WHERE r.pack_sha256 = a.pack_sha256)`)
3027
+ .all();
3028
+ let deleted = 0;
3029
+ for (const artifact of artifacts) {
3030
+ const result = this.#database.prepare('DELETE FROM source_prepared_artifacts WHERE pack_sha256 = ?')
3031
+ .run(artifact.pack_sha256);
3032
+ deleted += Number(result.changes);
3033
+ }
3034
+ this.#database.exec('COMMIT');
3035
+ return deleted;
3036
+ }
3037
+ catch (error) {
3038
+ this.#database.exec('ROLLBACK');
3039
+ throw error;
3040
+ }
3041
+ }
3042
+ /** Durable, structurally bound applied merge history; receipt signatures are checked by the baseline resolver. */
3043
+ getSourceBaselineHistory(repository) {
3044
+ if (!isAbsolute(repository) || resolve(repository) !== repository)
3045
+ throw new ControlPlaneStoreError('invalid-input', 'source repository path is invalid');
3046
+ // A SAVEPOINT provides one SQLite read snapshot, and also works inside the
3047
+ // start-release BEGIN IMMEDIATE writer without nesting another BEGIN.
3048
+ this.#database.exec('SAVEPOINT source_baseline_history');
3049
+ try {
3050
+ const history = this.#readSourceBaselineHistory(repository);
3051
+ this.#database.exec('RELEASE source_baseline_history');
3052
+ return history;
3053
+ }
3054
+ catch (error) {
3055
+ this.#database.exec('ROLLBACK TO source_baseline_history');
3056
+ this.#database.exec('RELEASE source_baseline_history');
3057
+ throw error;
3058
+ }
3059
+ }
3060
+ /** Must run inside an open read transaction/SAVEPOINT so the caller sees one snapshot. */
3061
+ #readSourceBaselineHistory(repository) {
3062
+ const rows = this.#database.prepare(`SELECT * FROM source_plans WHERE repository = ?
3063
+ AND (release_id IS NOT NULL OR status IN ('release-complete','release-failed'))
3064
+ ORDER BY created_at, id LIMIT 1001`).all(repository);
3065
+ if (rows.length > 1000)
3066
+ throw new ControlPlaneStoreError('invalid-state', 'source release history exceeds bound');
3067
+ const history = [];
3068
+ for (const row of rows) {
3069
+ const plan = sourceFromRow(row);
3070
+ if (plan.release === undefined)
3071
+ throw new ControlPlaneStoreError('invalid-state', 'started source release lacks release identity');
3072
+ if (plan.status !== 'release-complete' && plan.status !== 'release-failed') {
3073
+ throw new ControlPlaneStoreError('conflict', 'source repository has an unfinished release');
3074
+ }
3075
+ const merges = this.#database.prepare(`SELECT * FROM source_release_operations WHERE plan_id = ? AND phase = 'merge'
3076
+ AND status = 'applied' LIMIT 2`).all(plan.id);
3077
+ if (plan.status === 'release-failed') {
3078
+ if (merges.length !== 0)
3079
+ throw new ControlPlaneStoreError('conflict', 'failed source release has unresolved applied remote merge');
3080
+ continue;
3081
+ }
3082
+ if (merges.length !== 1)
3083
+ throw new ControlPlaneStoreError('invalid-state', 'completed source release lacks one applied merge');
3084
+ const operation = sourceReleaseOperationFromRow(merges[0]);
3085
+ const receipt = operation.receipt;
3086
+ if (operation.status !== 'applied' || operation.phase !== 'merge' || operation.request.phase !== 'merge' || operation.planId !== plan.id
3087
+ || operation.request.plan.digest !== plan.digest || operation.request.plan.revision > plan.revision
3088
+ || operation.request.release.id !== plan.release.id || operation.request.release.fence !== plan.release.fence
3089
+ || receipt?.outcome !== 'passed' || receipt.evidence.kind !== 'merge'
3090
+ || receipt.planDigest !== plan.digest || receipt.evidence.targetBranch !== plan.releaseAuthorization?.releasePolicy.targetBranch
3091
+ || receipt.evidence.reviewedHeadCommit !== operation.request.input.headCommit
3092
+ || receipt.evidence.reviewEvidenceDigest !== operation.request.input.reviewEvidenceDigest
3093
+ || receipt.evidence.reviewId !== operation.request.input.reviewId
3094
+ || receipt.evidence.prId !== operation.request.input.prId) {
3095
+ throw new ControlPlaneStoreError('invalid-state', 'completed source release merge is not bound to current release');
3096
+ }
3097
+ history.push({ plan, operation });
3098
+ }
3099
+ return history;
3100
+ }
3101
+ /** Release edges and maintenance edges in one SQLite snapshot, for preflight consumers that must compare both. */
3102
+ readSourceMaintenanceState(repository) {
3103
+ if (!isAbsolute(repository) || resolve(repository) !== repository)
3104
+ throw new ControlPlaneStoreError('invalid-input', 'source repository path is invalid');
3105
+ this.#database.exec('SAVEPOINT source_maintenance_state');
3106
+ try {
3107
+ const unsettledJob = this.#database.prepare(`SELECT id FROM source_jobs WHERE json_extract(intent_json,'$.repository')=?
3108
+ AND status IN ('queued','running','unknown') LIMIT 1`).get(repository);
3109
+ const unsettledPlan = this.#database.prepare(`SELECT id FROM source_plans WHERE repository=?
3110
+ AND status NOT IN ('expired','local-checks-failed','release-failed','release-complete') LIMIT 1`).get(repository);
3111
+ if (unsettledJob || unsettledPlan)
3112
+ throw new ControlPlaneStoreError('conflict', 'source maintenance has unsettled source work');
3113
+ const history = this.#readSourceBaselineHistory(repository);
3114
+ const records = this.getSourceMaintenanceRecords(repository);
3115
+ this.#database.exec('RELEASE source_maintenance_state');
3116
+ return { history, records };
3117
+ }
3118
+ catch (error) {
3119
+ this.#database.exec('ROLLBACK TO source_maintenance_state');
3120
+ this.#database.exec('RELEASE source_maintenance_state');
3121
+ throw error;
3122
+ }
3123
+ }
3124
+ getSourceMaintenanceRecords(repository) {
3125
+ if (!isAbsolute(repository) || resolve(repository) !== repository)
3126
+ throw new ControlPlaneStoreError('invalid-input', 'source repository path is invalid');
3127
+ const rows = this.#database.prepare('SELECT sequence,host_plan_id,record_json,record_digest FROM source_maintenance WHERE repository=? ORDER BY sequence LIMIT 257')
3128
+ .all(repository);
3129
+ if (rows.length > 256)
3130
+ throw new ControlPlaneStoreError('invalid-state', 'source maintenance history exceeds bound');
3131
+ return rows.map((row, index) => {
3132
+ const record = parseSourceMaintenanceRecord(JSON.parse(row.record_json));
3133
+ if (record.repository !== repository || record.sequence !== index + 1 || record.sequence !== row.sequence
3134
+ || record.host?.planId !== (row.host_plan_id ?? undefined)
3135
+ || sourceMaintenanceDigest(record) !== row.record_digest) {
3136
+ throw new ControlPlaneStoreError('invalid-state', 'source maintenance stored record changed');
3137
+ }
3138
+ return record;
3139
+ });
3140
+ }
3141
+ /** Offline owner transaction only. No service/model-facing signing path exists. */
3142
+ async appendSourceMaintenance(recordInput, input) {
3143
+ const record = parseSourceMaintenanceRecord(recordInput), { trust, baseline } = input;
3144
+ validateSourceBaselineConfig(baseline);
3145
+ const key = resolveTrustKey(trust, 'host-attestation', record.authority, record.keyId);
3146
+ if (record.installationId !== trust.installationId || controlPlaneDigest(record.ledger) !== controlPlaneDigest(trust.ledger)
3147
+ || record.repository !== recordInput.repository || controlPlaneDigest(record.baseline) !== controlPlaneDigest(baseline)
3148
+ || !sameSourceMaintenancePublicKey(record.publicKeyPem, key.publicKeyPem) || record.issuedAt > this.#now()) {
3149
+ throw new ControlPlaneStoreError('invalid-input', 'source maintenance owner binding changed');
3150
+ }
3151
+ const verifiedHistory = this.getSourceBaselineHistory(record.repository);
3152
+ const verifiedMaintenance = this.getSourceMaintenanceRecords(record.repository);
3153
+ const authorityDigest = controlPlaneDigest({ trust, baseline });
3154
+ await verifySourceBaselineHistory({ repository: record.repository, config: baseline, trust,
3155
+ history: verifiedHistory, maintenance: verifiedMaintenance, signal: new AbortController().signal });
3156
+ const verifiedSnapshot = controlPlaneDigest({ history: verifiedHistory, maintenance: verifiedMaintenance });
3157
+ this.#database.exec('SAVEPOINT append_source_maintenance');
3158
+ try {
3159
+ this.#database.exec('UPDATE source_maintenance SET sequence=sequence WHERE 0');
3160
+ const records = this.getSourceMaintenanceRecords(record.repository);
3161
+ const history = this.getSourceBaselineHistory(record.repository);
3162
+ if (controlPlaneDigest({ trust, baseline }) !== authorityDigest) {
3163
+ throw new ControlPlaneStoreError('conflict', 'source maintenance trust or baseline changed before append');
3164
+ }
3165
+ if (controlPlaneDigest({ history, maintenance: records }) !== verifiedSnapshot) {
3166
+ throw new ControlPlaneStoreError('conflict', 'source maintenance signed history changed before append');
3167
+ }
3168
+ const priorTransaction = records.find(item => item.transactionId === record.transactionId);
3169
+ if (priorTransaction) {
3170
+ if (sourceMaintenanceDigest(priorTransaction) !== sourceMaintenanceDigest(record)) {
3171
+ throw new ControlPlaneStoreError('conflict', 'source maintenance transaction changed');
3172
+ }
3173
+ this.#database.exec('RELEASE append_source_maintenance');
3174
+ return;
3175
+ }
3176
+ verifySourceMaintenanceRecords([...records, record], { installationId: trust.installationId,
3177
+ ledger: trust.ledger, repository: record.repository, baseline, hostIdentity: key });
3178
+ const unsettledJob = this.#database.prepare(`SELECT id FROM source_jobs WHERE json_extract(intent_json,'$.repository')=?
3179
+ AND status IN ('queued','running','unknown') LIMIT 1`).get(record.repository);
3180
+ const unsettledPlan = this.#database.prepare(`SELECT id FROM source_plans WHERE repository=?
3181
+ AND status NOT IN ('expired','local-checks-failed','release-failed','release-complete') LIMIT 1`).get(record.repository);
3182
+ if (unsettledJob || unsettledPlan)
3183
+ throw new ControlPlaneStoreError('conflict', 'source maintenance has unsettled source work');
3184
+ const activationState = this.readSourceMaintenanceActivationState(trust.dshHome);
3185
+ if (activationState.kind === 'unsettled') {
3186
+ throw new ControlPlaneStoreError('conflict', 'source maintenance activation is unsettled');
3187
+ }
3188
+ if (activationState.kind === 'watched') {
3189
+ const context = readCurrentRuntimeEpochDeployment(this.#database, this.getPlan(activationState.planId).target.profilePath);
3190
+ const { plan, readiness } = context, receipt = readiness.receipt;
3191
+ if (!record.host || record.host.planId !== plan.id || record.host.planDigest !== plan.digest
3192
+ || record.host.readinessOperationId !== readiness.operationId || !receipt
3193
+ || record.host.readinessReceiptDigest !== hostMaintenanceDigest(receipt)
3194
+ || receipt.authority !== record.authority || receipt.keyId !== record.keyId) {
3195
+ throw new ControlPlaneStoreError('conflict', 'source maintenance watched Host anchor changed');
3196
+ }
3197
+ const { signature, ...unsigned } = receipt;
3198
+ if (!verify(null, Buffer.from(hostMaintenanceCanonical(unsigned)), createPublicKey(key.publicKeyPem), Buffer.from(signature, 'base64'))) {
3199
+ throw new ControlPlaneStoreError('conflict', 'source maintenance original Host receipt signature changed');
3200
+ }
3201
+ }
3202
+ else if (record.host !== null)
3203
+ throw new ControlPlaneStoreError('conflict', 'source maintenance has no watched Host anchor');
3204
+ const current = sourceBaselineChain(baseline, history, records).at(-1);
3205
+ if (record.previousTip !== current)
3206
+ throw new ControlPlaneStoreError('conflict', 'source maintenance previous tip is stale');
3207
+ sourceBaselineChain(baseline, history, [...records, record]);
3208
+ assertSourceMaintenanceGit(record, input.physicalHome ?? trust.dshHome, trust.dshHome);
3209
+ this.#database.prepare('INSERT INTO source_maintenance VALUES (?,?,?,?,?,?)').run(record.repository, record.sequence, record.transactionId, record.host?.planId ?? null, JSON.stringify(record), sourceMaintenanceDigest(record));
3210
+ this.#database.exec('RELEASE append_source_maintenance');
3211
+ }
3212
+ catch (error) {
3213
+ this.#database.exec('ROLLBACK TO append_source_maintenance; RELEASE append_source_maintenance');
3214
+ throw error;
3215
+ }
3216
+ }
3217
+ /** Seed a previously owner-signed pre-adoption sidecar into a new ledger. */
3218
+ async importSourceMaintenanceRecords(recordInputs, input) {
3219
+ if (recordInputs.length === 0 || recordInputs.length > 256)
3220
+ throw new ControlPlaneStoreError('invalid-input', 'source maintenance import is empty or unbounded');
3221
+ const { trust, baseline } = input;
3222
+ validateSourceBaselineConfig(baseline);
3223
+ const records = recordInputs.map(parseSourceMaintenanceRecord), first = records[0];
3224
+ const key = resolveTrustKey(trust, 'host-attestation', first.authority, first.keyId);
3225
+ verifySourceMaintenanceRecords(records, { installationId: trust.installationId, ledger: trust.ledger,
3226
+ repository: first.repository, baseline, hostIdentity: key });
3227
+ if (records.some(record => record.host !== null))
3228
+ throw new ControlPlaneStoreError('invalid-input', 'pre-adoption source history carries a deployment anchor');
3229
+ sourceBaselineChain(baseline, [], records);
3230
+ const authorityDigest = controlPlaneDigest({ trust, baseline, records });
3231
+ this.#database.exec('SAVEPOINT import_source_maintenance');
3232
+ try {
3233
+ this.#database.exec('UPDATE source_maintenance SET sequence=sequence WHERE 0');
3234
+ if (controlPlaneDigest({ trust, baseline, records }) !== authorityDigest)
3235
+ throw new ControlPlaneStoreError('conflict', 'source import authority changed');
3236
+ const present = this.getSourceMaintenanceRecords(first.repository);
3237
+ if (present.length > records.length || present.some((record, index) => sourceMaintenanceDigest(record) !== sourceMaintenanceDigest(records[index]))) {
3238
+ throw new ControlPlaneStoreError('conflict', 'source import differs from existing ledger history');
3239
+ }
3240
+ if (this.getSourceBaselineHistory(first.repository).length)
3241
+ throw new ControlPlaneStoreError('conflict', 'source import cannot cross a published release');
3242
+ const anyJob = this.#database.prepare(`SELECT id FROM source_jobs WHERE json_extract(intent_json,'$.repository')=? LIMIT 1`)
3243
+ .get(first.repository);
3244
+ const anyPlan = this.#database.prepare('SELECT id FROM source_plans WHERE repository=? LIMIT 1').get(first.repository);
3245
+ const anyActivation = this.#database.prepare('SELECT id FROM activation_plans WHERE dsh_home=? LIMIT 1').get(trust.dshHome);
3246
+ if (anyJob || anyPlan || anyActivation)
3247
+ throw new ControlPlaneStoreError('conflict', 'source import requires an empty owner ledger');
3248
+ for (let index = 0; index < records.length; index++) {
3249
+ assertSourceMaintenanceGit(records[index], input.physicalHome ?? trust.dshHome, trust.dshHome, index === records.length - 1);
3250
+ }
3251
+ for (const record of records.slice(present.length)) {
3252
+ this.#database.prepare('INSERT INTO source_maintenance VALUES (?,?,?,?,?,?)').run(record.repository, record.sequence, record.transactionId, null, JSON.stringify(record), sourceMaintenanceDigest(record));
3253
+ }
3254
+ this.#database.exec('RELEASE import_source_maintenance');
3255
+ }
3256
+ catch (error) {
3257
+ this.#database.exec('ROLLBACK TO import_source_maintenance; RELEASE import_source_maintenance');
3258
+ throw error;
3259
+ }
3260
+ }
1661
3261
  /**
1662
3262
  * Enumerate 'modify' plans for isolated-worktree garbage collection. Only
1663
3263
  * modify plans own control-plane-created worktrees under the state root;
@@ -1671,14 +3271,24 @@ export class ControlPlaneStore {
1671
3271
  .all(filter.expiredBefore);
1672
3272
  return rows.map(sourceFromRow);
1673
3273
  }
3274
+ /** Isolated worktrees owned by the background prepared flows, including owner-granted creation. */
3275
+ listPreparedSourcePlans(filter) {
3276
+ const rows = filter?.expiredBefore === undefined
3277
+ ? this.#database.prepare(`SELECT * FROM source_plans WHERE mode IN ('modify', 'prepared-create', 'prepared-revise') ORDER BY created_at, id`)
3278
+ .all()
3279
+ : this.#database.prepare(`SELECT * FROM source_plans WHERE mode IN ('modify', 'prepared-create', 'prepared-revise') AND expires_at < ? ORDER BY created_at, id`)
3280
+ .all(filter.expiredBefore);
3281
+ return rows.map(sourceFromRow);
3282
+ }
1674
3283
  /** Retire an expired prepared plan before removing its worktree. The CAS
1675
3284
  * fences owner approval/review, and releases only this plan's gap claim. */
1676
3285
  expirePreparedSourcePlan(input) {
1677
3286
  if (!Number.isSafeInteger(input.now) || input.now < 0)
1678
3287
  throw new ControlPlaneStoreError('invalid-input', 'expiry time is invalid');
1679
3288
  const plan = this.getSourcePlan(input.planId);
1680
- if (plan.mode !== 'modify')
1681
- throw new ControlPlaneStoreError('invalid-state', 'only prepared modify plans may expire here');
3289
+ if (plan.mode !== 'modify' && plan.mode !== 'prepared-create' && plan.mode !== 'prepared-revise') {
3290
+ throw new ControlPlaneStoreError('invalid-state', 'only prepared source plans may expire here');
3291
+ }
1682
3292
  const expiryDigest = controlPlaneDigest({ planId: plan.id, planDigest: plan.digest, expiresAt: plan.expiresAt });
1683
3293
  if (plan.status === 'expired') {
1684
3294
  const receipt = this.#sourcePlanReceipt(`source-expired:${plan.id}`, 'source-expired', expiryDigest, plan.id);
@@ -1689,7 +3299,8 @@ export class ControlPlaneStore {
1689
3299
  this.#database.exec('BEGIN IMMEDIATE');
1690
3300
  try {
1691
3301
  const retired = this.#database.prepare(`UPDATE source_plans SET status = 'expired', revision = revision + 1, updated_at = ?
1692
- WHERE id = ? AND revision = ? AND mode = 'modify' AND status IN ('pending-approval', 'approved') AND expires_at < ?`)
3302
+ WHERE id = ? AND revision = ? AND ((mode = 'modify' AND status IN ('pending-approval', 'approved'))
3303
+ OR (mode IN ('prepared-create', 'prepared-revise') AND status = 'pending-approval')) AND expires_at < ?`)
1693
3304
  .run(input.now, plan.id, input.expectedRevision, input.now);
1694
3305
  if (Number(retired.changes) !== 1)
1695
3306
  throw new ControlPlaneStoreError('conflict', 'prepared plan changed or is not expired');
@@ -1714,6 +3325,9 @@ export class ControlPlaneStore {
1714
3325
  return this.#approvePlan('activation', input);
1715
3326
  }
1716
3327
  async approveSource(input) {
3328
+ if (['prepared-create', 'prepared-revise'].includes(this.getSourcePlan(input.planId).mode)) {
3329
+ throw new ControlPlaneStoreError('invalid-state', 'prepared creation requires its own owner creation authority');
3330
+ }
1717
3331
  return this.#approvePlan('source', input);
1718
3332
  }
1719
3333
  async #approvePlan(kind, input) {
@@ -1794,7 +3408,7 @@ export class ControlPlaneStore {
1794
3408
  const row = this.#database.prepare('SELECT * FROM activation_plans WHERE id = ?').get(input.planId);
1795
3409
  if (row === undefined)
1796
3410
  throw new ControlPlaneStoreError('not-found', 'activation plan not found');
1797
- const plan = activationFromRow(row);
3411
+ const plan = effectiveActivationFromRow(this.#database, row);
1798
3412
  if (plan.revision !== input.expectedRevision)
1799
3413
  throw new ControlPlaneStoreError('conflict', 'activation plan revision conflict');
1800
3414
  if (row.approval_receipt_json === null || plan.approval === undefined) {
@@ -1817,7 +3431,7 @@ export class ControlPlaneStore {
1817
3431
  if (now > plan.expiresAt)
1818
3432
  throw new ControlPlaneStoreError('expired', 'activation plan expired before its first claim');
1819
3433
  const targetOwner = this.#database.prepare(`SELECT id FROM activation_plans WHERE target_path = ? AND id <> ? AND status IN (
1820
- 'staging', 'awaiting-reload', 'awaiting-readiness', 'awaiting-effect-blocked-replay', 'awaiting-shadow',
3434
+ 'staging', 'awaiting-reload', 'awaiting-readiness', 'awaiting-live-tasks', 'awaiting-effect-blocked-replay', 'awaiting-shadow',
1821
3435
  'awaiting-canary', 'awaiting-soak', 'awaiting-health', 'commit-pending', 'rollback-pending') LIMIT 1`).get(plan.target.profilePath, plan.id);
1822
3436
  if (targetOwner !== undefined)
1823
3437
  throw new ControlPlaneStoreError('conflict', 'target profile already has an active activation');
@@ -1830,7 +3444,7 @@ export class ControlPlaneStore {
1830
3444
  const activationId = row.activation_id ?? `activation-${randomUUID()}`;
1831
3445
  const status = plan.status === 'approved' ? 'staging' : plan.status;
1832
3446
  const result = this.#database.prepare(`UPDATE activation_plans SET status = ?, revision = revision + 1, activation_id = ?,
1833
- 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);
3447
+ activation_fence = activation_fence + ?, activation_lease_until = ?, updated_at = ? WHERE id = ? AND revision = ?`).run(status, activationId, plan.status === 'commit-pending' && plan.dossier.liveQualification ? 0 : 1, now + input.leaseMs, now, plan.id, input.expectedRevision);
1834
3448
  if (Number(result.changes) !== 1)
1835
3449
  throw new ControlPlaneStoreError('conflict', 'activation plan changed while being claimed');
1836
3450
  const claimed = this.getPlan(plan.id);
@@ -1863,7 +3477,7 @@ export class ControlPlaneStore {
1863
3477
  const row = this.#database.prepare('SELECT * FROM activation_plans WHERE id = ?').get(input.planId);
1864
3478
  if (row === undefined)
1865
3479
  throw new ControlPlaneStoreError('not-found', 'activation plan not found');
1866
- const plan = activationFromRow(row);
3480
+ const plan = effectiveActivationFromRow(this.#database, row);
1867
3481
  if (plan.revision !== input.expectedRevision || plan.activation?.fence !== input.fence || plan.status !== 'staging'
1868
3482
  || Number(row.activation_lease_until ?? 0) < now)
1869
3483
  throw new ControlPlaneStoreError('conflict', 'activation lost its claim before recording the target baseline');
@@ -1905,7 +3519,7 @@ export class ControlPlaneStore {
1905
3519
  const row = this.#database.prepare('SELECT * FROM activation_plans WHERE id = ?').get(input.planId);
1906
3520
  if (row === undefined)
1907
3521
  throw new ControlPlaneStoreError('not-found', 'activation plan not found');
1908
- const plan = activationFromRow(row);
3522
+ const plan = effectiveActivationFromRow(this.#database, row);
1909
3523
  if (plan.revision !== input.expectedRevision || plan.activation?.fence !== input.fence || plan.status !== 'commit-pending'
1910
3524
  || Number(row.activation_lease_until ?? 0) < now) {
1911
3525
  throw new ControlPlaneStoreError('conflict', 'activation lost its claim before recording the installed baseline');
@@ -1961,7 +3575,8 @@ export class ControlPlaneStore {
1961
3575
  catch {
1962
3576
  throw new ControlPlaneStoreError('invalid-state', 'stored installed activation baseline is corrupt');
1963
3577
  }
1964
- return activationCoreFiles(value, plan.target.profilePath, 'stored installed activation baseline');
3578
+ const original = activationCoreFiles(value, plan.target.profilePath, 'stored installed activation baseline');
3579
+ return readHostMaintenanceContext(this.#database, plan.id).records.at(-1)?.after.profileFiles ?? original;
1965
3580
  }
1966
3581
  /** Move a signed closed deployment watch into the existing physical rollback lifecycle. */
1967
3582
  beginPostActivationRollback(input) {
@@ -1971,7 +3586,7 @@ export class ControlPlaneStore {
1971
3586
  const row = this.#database.prepare('SELECT * FROM activation_plans WHERE id = ?').get(input.planId);
1972
3587
  if (row === undefined)
1973
3588
  throw new ControlPlaneStoreError('not-found', 'activation plan not found');
1974
- const plan = activationFromRow(row);
3589
+ const plan = effectiveActivationFromRow(this.#database, row);
1975
3590
  if (plan.revision !== input.expectedRevision)
1976
3591
  throw new ControlPlaneStoreError('conflict', 'activation plan revision conflict');
1977
3592
  // A restart or retry must retain the original rollback identity and never
@@ -1997,7 +3612,7 @@ export class ControlPlaneStore {
1997
3612
  }
1998
3613
  this.getActivationInstalledBaseline(plan.id);
1999
3614
  const active = this.#database.prepare(`SELECT id FROM activation_plans WHERE target_path = ? AND id <> ? AND status IN (
2000
- 'staging', 'awaiting-reload', 'awaiting-readiness', 'awaiting-effect-blocked-replay', 'awaiting-shadow',
3615
+ 'staging', 'awaiting-reload', 'awaiting-readiness', 'awaiting-live-tasks', 'awaiting-effect-blocked-replay', 'awaiting-shadow',
2001
3616
  'awaiting-canary', 'awaiting-soak', 'awaiting-health', 'commit-pending', 'rollback-pending') LIMIT 1`)
2002
3617
  .get(plan.target.profilePath, plan.id);
2003
3618
  if (active !== undefined)
@@ -2038,9 +3653,65 @@ export class ControlPlaneStore {
2038
3653
  ORDER BY older.exposure_order`).all(current.target.profilePath, current.id, checkpoint.exposure_order);
2039
3654
  return rows.map(activationFromRow);
2040
3655
  }
3656
+ /** Freeze exact physical pins before a signed deployment becomes Host-visible. */
3657
+ recordActivationHostInputWitness(input) {
3658
+ return this.#withActivationWrite(input.planId, () => {
3659
+ const plan = this.getPlan(input.planId);
3660
+ const row = this.#database.prepare('SELECT activation_lease_until FROM activation_plans WHERE id=?').get(plan.id);
3661
+ const inputs = plan.dossier.hostDeploymentInputs;
3662
+ if (!inputs)
3663
+ throw new ControlPlaneStoreError('invalid-state', 'plan has no signed Host deployment inputs');
3664
+ if (plan.status !== 'staging' || plan.revision !== input.expectedRevision || plan.activation?.fence !== input.fence
3665
+ || (row.activation_lease_until ?? 0) < this.#now() || plan.activation.hostRecoveryRequired
3666
+ || plan.activation.targetOriginallyExisted === undefined || plan.activation.targetBaselineFiles === undefined) {
3667
+ throw new ControlPlaneStoreError('conflict', 'Host input witness lost its staging claim or target baseline');
3668
+ }
3669
+ let profileFiles;
3670
+ let deploymentFiles;
3671
+ let baselineDeploymentFiles;
3672
+ try {
3673
+ profileFiles = activationCoreFiles(input.profileFiles, plan.target.profilePath, 'Host input witness core files');
3674
+ if (profileFiles.some(file => file.sha256 === null))
3675
+ throw new Error('missing core file');
3676
+ deploymentFiles = hostInputPins(input.deploymentFiles, inputs, plan.target.profilePath, 'Host input witness deployment files');
3677
+ baselineDeploymentFiles = plan.activation.targetOriginallyExisted
3678
+ ? hostInputPins(input.baselineDeploymentFiles, inputs, plan.target.profilePath, 'Host input witness baseline files')
3679
+ : input.baselineDeploymentFiles.length === 0 ? [] : (() => { throw new Error('unexpected baseline files'); })();
3680
+ }
3681
+ catch {
3682
+ throw new ControlPlaneStoreError('invalid-input', 'Host input witness pins do not match the signed profile deployment');
3683
+ }
3684
+ const existing = hostInputWitnessFromRow(this.#database, plan);
3685
+ if (existing) {
3686
+ if (controlPlaneDigest(existing.profileFiles) !== controlPlaneDigest(profileFiles)
3687
+ || controlPlaneDigest(existing.deploymentFiles) !== controlPlaneDigest(deploymentFiles)
3688
+ || controlPlaneDigest(existing.baselineDeploymentFiles) !== controlPlaneDigest(baselineDeploymentFiles)) {
3689
+ throw new ControlPlaneStoreError('conflict', 'Host input witness is immutable');
3690
+ }
3691
+ return plan;
3692
+ }
3693
+ const unsigned = { schemaVersion: 1, kind: 'dsh-host-input-witness', planId: plan.id,
3694
+ planDigest: plan.digest, activationId: plan.activation.id, fence: input.fence, createdAt: this.#now(),
3695
+ inputs, profileFiles, deploymentFiles, baselineDeploymentFiles };
3696
+ const witness = { ...unsigned, digest: controlPlaneDigest(unsigned) };
3697
+ this.#database.prepare('INSERT INTO activation_host_input_witnesses (plan_id,witness_json,witness_digest) VALUES (?,?,?)')
3698
+ .run(plan.id, JSON.stringify(witness), controlPlaneDigest(witness));
3699
+ return plan;
3700
+ });
3701
+ }
3702
+ getActivationHostInputWitness(planId) {
3703
+ return hostInputWitnessFromRow(this.#database, this.getPlan(planId));
3704
+ }
2041
3705
  /** Freeze recovery obligation before the profile can become Host-visible. */
2042
3706
  markActivationHostExposure(input) {
2043
3707
  return this.#withActivationWrite(input.planId, () => {
3708
+ const before = this.getPlan(input.planId);
3709
+ if (before.dossier.hostDeploymentInputs) {
3710
+ const witness = hostInputWitnessFromRow(this.#database, before);
3711
+ if (!witness || witness.fence !== input.fence || before.revision !== input.expectedRevision) {
3712
+ throw new ControlPlaneStoreError('conflict', 'Host exposure requires an exact staged input witness');
3713
+ }
3714
+ }
2044
3715
  const now = this.#now();
2045
3716
  const result = this.#database.prepare(`UPDATE activation_plans SET host_recovery_required = 1, updated_at = ?
2046
3717
  WHERE id = ? AND revision = ? AND activation_fence = ? AND status = 'staging'
@@ -2048,7 +3719,19 @@ export class ControlPlaneStore {
2048
3719
  AND activation_lease_until >= ?`).run(now, input.planId, input.expectedRevision, input.fence, now);
2049
3720
  if (Number(result.changes) !== 1)
2050
3721
  throw new ControlPlaneStoreError('conflict', 'Host exposure lost its claim or original baseline');
2051
- return this.getPlan(input.planId);
3722
+ const plan = this.getPlan(input.planId);
3723
+ if (plan.dossier.liveQualification) {
3724
+ const handoff = this.getAdoptionHandoff(plan.id);
3725
+ const deadline = Math.min(now + plan.dossier.liveQualification.maximumWindowMs, plan.expiresAt, plan.approval.expiresAt, handoff?.expiresAt ?? 0);
3726
+ if (deadline <= now)
3727
+ throw new ControlPlaneStoreError('expired', 'live qualification authorization expired before exposure');
3728
+ this.#database.prepare(`INSERT INTO live_qualification_windows (plan_id,activation_id,fence,started_at,deadline_at)
3729
+ VALUES (?,?,?,?,?) ON CONFLICT(plan_id) DO NOTHING`).run(plan.id, plan.activation.id, plan.activation.fence, now, deadline);
3730
+ const prior = this.#database.prepare('SELECT activation_id,fence FROM live_qualification_windows WHERE plan_id=?').get(plan.id);
3731
+ if (prior?.activation_id !== plan.activation.id || prior.fence !== plan.activation.fence)
3732
+ throw new ControlPlaneStoreError('conflict', 'live qualification exposure cannot be replayed');
3733
+ }
3734
+ return plan;
2052
3735
  });
2053
3736
  }
2054
3737
  markRollbackProfileRestored(input) {
@@ -2082,7 +3765,7 @@ export class ControlPlaneStore {
2082
3765
  const row = this.#database.prepare('SELECT * FROM activation_plans WHERE id = ?').get(input.planId);
2083
3766
  if (row === undefined)
2084
3767
  throw new ControlPlaneStoreError('not-found', 'activation plan not found');
2085
- const plan = activationFromRow(row);
3768
+ const plan = effectiveActivationFromRow(this.#database, row);
2086
3769
  if (plan.revision !== input.expectedRevision || plan.activation?.fence !== input.fence || plan.status !== input.status
2087
3770
  || (requireLiveLease && Number(row.activation_lease_until ?? 0) < this.#now()))
2088
3771
  throw new ControlPlaneStoreError('conflict', 'activation no longer owns the filesystem fence');
@@ -2131,6 +3814,177 @@ export class ControlPlaneStore {
2131
3814
  throw new ControlPlaneStoreError('conflict', 'activation no longer owns the exact revision/fence/status');
2132
3815
  return plan;
2133
3816
  }
3817
+ currentRuntimeEpochDeployment(profilePath) {
3818
+ return readCurrentRuntimeEpochDeployment(this.#database, profilePath);
3819
+ }
3820
+ /**
3821
+ * Classify activation state for a Home in one read snapshot, using the exact
3822
+ * watch/plan queries that gate {@link appendSourceMaintenance}. Preflight
3823
+ * producers use it to distinguish a genuinely unanchored ledger (`none`) from
3824
+ * a watched deployment that must bind its readiness receipt (`watched`) and
3825
+ * from any conflict that must abort rather than silently degrade (`unsettled`).
3826
+ */
3827
+ readSourceMaintenanceActivationState(dshHome) {
3828
+ if (!isAbsolute(dshHome) || resolve(dshHome) !== dshHome) {
3829
+ throw new ControlPlaneStoreError('invalid-input', 'source maintenance Home path is invalid');
3830
+ }
3831
+ this.#database.exec('SAVEPOINT source_maintenance_activation_state');
3832
+ try {
3833
+ const watching = this.#database.prepare(`SELECT plan.id FROM activation_plans plan JOIN activation_watch watch ON watch.plan_id=plan.id
3834
+ WHERE plan.dsh_home=? AND watch.state='watching' LIMIT 2`).all(dshHome);
3835
+ const active = this.#database.prepare(`SELECT id FROM activation_plans WHERE dsh_home=?
3836
+ AND status NOT IN ('rolled-back','activated') LIMIT 1`).get(dshHome);
3837
+ const activated = this.#database.prepare(`SELECT plan.id,plan.target_path,watch.state,
3838
+ checkpoint.exposure_order,checkpoint.successful_order FROM activation_plans plan
3839
+ LEFT JOIN activation_watch watch ON watch.plan_id=plan.id
3840
+ LEFT JOIN activation_deployment_checkpoints checkpoint ON checkpoint.plan_id=plan.id
3841
+ WHERE plan.dsh_home=? AND plan.status='activated' LIMIT 1001`).all(dshHome);
3842
+ if (activated.length > 1000)
3843
+ throw new ControlPlaneStoreError('invalid-state', 'source maintenance activation history exceeds bound');
3844
+ let result = watching.length > 1 || active || watching.length === 0 && activated.length !== 0
3845
+ || watching.length === 1 && !activated.some(row => row.id === watching[0].id)
3846
+ ? { kind: 'unsettled' }
3847
+ : watching.length === 1
3848
+ ? { kind: 'watched', planId: watching[0].id }
3849
+ : { kind: 'none' };
3850
+ if (result.kind === 'watched') {
3851
+ const plan = this.getPlan(result.planId), watch = this.getActivationWatch(result.planId);
3852
+ const assertBinding = (candidate, boundWatch) => {
3853
+ if (boundWatch.activationId !== candidate.activation?.id || boundWatch.fence !== candidate.activation.fence
3854
+ || boundWatch.exact.package !== candidate.candidate.package || boundWatch.exact.version !== candidate.candidate.version
3855
+ || boundWatch.exact.integrity !== candidate.candidate.integrity) {
3856
+ throw new ControlPlaneStoreError('invalid-state', 'source maintenance activation watch binding is corrupt');
3857
+ }
3858
+ };
3859
+ assertBinding(plan, watch);
3860
+ const current = activated.find(row => row.id === plan.id);
3861
+ const latest = this.#database.prepare(`SELECT checkpoint.plan_id FROM activation_deployment_checkpoints checkpoint
3862
+ JOIN activation_plans plan ON plan.id=checkpoint.plan_id WHERE plan.target_path=?
3863
+ ORDER BY checkpoint.exposure_order DESC LIMIT 1`).get(plan.target.profilePath);
3864
+ if (current.exposure_order === null || current.successful_order !== current.exposure_order || latest?.plan_id !== plan.id) {
3865
+ result = { kind: 'unsettled' };
3866
+ }
3867
+ else
3868
+ for (const older of activated) {
3869
+ if (older.id === plan.id)
3870
+ continue;
3871
+ // Closed watches retain activated status after a successor takes over.
3872
+ // Only a proved older success on this same profile is retired history.
3873
+ if (older.target_path !== plan.target.profilePath || !['closed-regressed', 'closed-retracted'].includes(older.state ?? '')
3874
+ || older.exposure_order === null || older.successful_order !== older.exposure_order
3875
+ || older.exposure_order >= current.exposure_order) {
3876
+ result = { kind: 'unsettled' };
3877
+ break;
3878
+ }
3879
+ assertBinding(this.getPlan(older.id), this.getActivationWatch(older.id));
3880
+ }
3881
+ }
3882
+ this.#database.exec('RELEASE source_maintenance_activation_state');
3883
+ return result;
3884
+ }
3885
+ catch (error) {
3886
+ this.#database.exec('ROLLBACK TO source_maintenance_activation_state; RELEASE source_maintenance_activation_state');
3887
+ throw error;
3888
+ }
3889
+ }
3890
+ prepareRuntimeEpoch(input) {
3891
+ const runtimeDigest = runtimeIdentityDigest(input.runtime);
3892
+ if (input.runtime.observedAt > this.#now() || input.runtime.observedAt < this.#now() - 30_000
3893
+ || input.runtime.entries.some(entry => !entry.active))
3894
+ throw new ControlPlaneStoreError('invalid-input', 'runtime epoch needs a fresh active runtime');
3895
+ const current = readCurrentRuntimeEpochDeployment(this.#database, input.runtime.profilePath);
3896
+ return this.#withActivationWrite(current.plan.id, () => {
3897
+ const context = readCurrentRuntimeEpochDeployment(this.#database, input.runtime.profilePath), { plan, readiness } = context;
3898
+ const previous = this.#database.prepare(`SELECT * FROM deployment_runtime_epochs WHERE plan_id=? ORDER BY sequence DESC LIMIT 1`)
3899
+ .get(plan.id);
3900
+ if (previous) {
3901
+ const record = runtimeEpochFromRow(previous);
3902
+ if (record.request.runtimeIdentityDigest === runtimeDigest && controlPlaneDigest(record.request.issuer) === controlPlaneDigest(input.issuer)
3903
+ && record.status !== 'stale'
3904
+ && (record.status === 'applied' || this.#now() < record.request.requestedAt + record.request.receiptTtlMs)) {
3905
+ return record;
3906
+ }
3907
+ // An expired read-only observation may be replaced, never relabelled or replayed as a new proof.
3908
+ this.#database.prepare("UPDATE deployment_runtime_epochs SET status='stale' WHERE plan_id=? AND status IN ('pending','claimed')").run(plan.id);
3909
+ }
3910
+ const sequence = Number(this.#database.prepare('SELECT max(sequence) AS sequence FROM deployment_runtime_epochs WHERE plan_id=?').get(plan.id)?.sequence ?? 0) + 1;
3911
+ const request = parseRuntimeEpochRequest({ schemaVersion: 1, kind: 'dsh-runtime-epoch-request', operationId: `runtime-epoch-${randomUUID()}`,
3912
+ requestedAt: this.#now(), receiptTtlMs: input.receiptTtlMs, installationId: plan.installationId, ledger: plan.ledger,
3913
+ plan: { id: plan.id, digest: plan.digest }, activation: { id: plan.activation.id, fence: plan.activation.fence },
3914
+ profile: { name: plan.profile, path: plan.target.profilePath }, issuer: input.issuer, predecessor: {
3915
+ operationId: readiness.operationId, receiptDigest: controlPlaneDigest(readiness.receipt), hostGeneration: readiness.receipt.hostGeneration
3916
+ },
3917
+ sequence, runtimeIdentityDigest: runtimeDigest });
3918
+ this.#database.prepare(`INSERT INTO deployment_runtime_epochs VALUES (?,?,?,?,?,?,'pending',NULL,?)`).run(request.operationId, plan.id, sequence, runtimeDigest, JSON.stringify(request), controlPlaneDigest(request), request.requestedAt);
3919
+ return { request, status: 'pending' };
3920
+ });
3921
+ }
3922
+ getRuntimeEpoch(operationId) {
3923
+ const row = this.#database.prepare('SELECT * FROM deployment_runtime_epochs WHERE operation_id=?').get(operationId);
3924
+ if (!row)
3925
+ throw new ControlPlaneStoreError('not-found', 'runtime epoch absent');
3926
+ return runtimeEpochFromRow(row);
3927
+ }
3928
+ latestAppliedRuntimeEpoch(planId) {
3929
+ const row = this.#database.prepare(`SELECT * FROM deployment_runtime_epochs WHERE plan_id=? ORDER BY sequence DESC LIMIT 1`)
3930
+ .get(planId);
3931
+ if (row && row.status !== 'applied')
3932
+ throw new ControlPlaneStoreError('conflict', 'current runtime epoch is not independently verified');
3933
+ return row && runtimeEpochFromRow(row);
3934
+ }
3935
+ pendingRuntimeEpochs(coordinatorId) {
3936
+ return this.#database.prepare(`SELECT epoch.* FROM deployment_runtime_epochs epoch
3937
+ JOIN adoption_handoffs handoff ON handoff.plan_id=epoch.plan_id
3938
+ JOIN activation_plans plan ON plan.id=epoch.plan_id
3939
+ WHERE handoff.coordinator_id=? AND handoff.revoked_at IS NULL AND plan.status='activated'
3940
+ AND epoch.status IN ('pending','claimed') AND epoch.created_at + json_extract(epoch.request_json,'$.receiptTtlMs') > ?
3941
+ AND epoch.sequence=(SELECT max(other.sequence) FROM deployment_runtime_epochs other WHERE other.plan_id=epoch.plan_id)
3942
+ ORDER BY epoch.created_at,epoch.operation_id LIMIT 100`).all(coordinatorId, this.#now()).map(runtimeEpochFromRow);
3943
+ }
3944
+ claimRuntimeEpoch(operationId, coordinatorId) {
3945
+ this.#database.exec('BEGIN IMMEDIATE');
3946
+ try {
3947
+ const record = this.getRuntimeEpoch(operationId);
3948
+ if (!['pending', 'claimed'].includes(record.status) || this.#now() >= record.request.requestedAt + record.request.receiptTtlMs) {
3949
+ throw new ControlPlaneStoreError('expired', 'runtime epoch request is not live');
3950
+ }
3951
+ this.#database.prepare("UPDATE deployment_runtime_epochs SET status='claimed' WHERE operation_id=?").run(operationId);
3952
+ const context = readOwnerRuntimeEpochContext(this.#database, operationId);
3953
+ if (context.handoff.coordinatorId !== coordinatorId || (this.#adoptionCoordinatorId !== undefined && this.#adoptionCoordinatorId !== coordinatorId)) {
3954
+ throw new ControlPlaneStoreError('conflict', 'runtime epoch coordinator changed');
3955
+ }
3956
+ this.#database.exec('COMMIT');
3957
+ return { ...record, status: 'claimed' };
3958
+ }
3959
+ catch (error) {
3960
+ this.#database.exec('ROLLBACK');
3961
+ throw error;
3962
+ }
3963
+ }
3964
+ applyRuntimeEpoch(receiptInput, trust) {
3965
+ const receipt = parseRuntimeEpochReceipt(receiptInput);
3966
+ this.#database.exec('BEGIN IMMEDIATE');
3967
+ try {
3968
+ const context = readOwnerRuntimeEpochContext(this.#database, receipt.operationId);
3969
+ if (this.#now() >= context.request.requestedAt + context.request.receiptTtlMs)
3970
+ throw new ControlPlaneStoreError('expired', 'runtime epoch request expired before application');
3971
+ verifyRuntimeEpochReceipt(receipt, context.request, trust, this.#now());
3972
+ if (receipt.outcome !== 'passed' || receipt.evidence.failures !== 0)
3973
+ throw new ControlPlaneStoreError('conflict', 'failed runtime epoch cannot be applied');
3974
+ if (context.request.ledger.id !== trust.ledger.id || context.request.ledger.path !== trust.ledger.path) {
3975
+ throw new ControlPlaneStoreError('conflict', 'runtime epoch trust ledger changed');
3976
+ }
3977
+ this.#database.prepare("UPDATE deployment_runtime_epochs SET status='applied',receipt_json=? WHERE operation_id=? AND status='claimed'")
3978
+ .run(JSON.stringify(receipt), receipt.operationId);
3979
+ const result = this.getRuntimeEpoch(receipt.operationId);
3980
+ this.#database.exec('COMMIT');
3981
+ return result;
3982
+ }
3983
+ catch (error) {
3984
+ this.#database.exec('ROLLBACK');
3985
+ throw error;
3986
+ }
3987
+ }
2134
3988
  prepareHostAttestationOperation(input) {
2135
3989
  return this.#withActivationSource(input.planId, () => {
2136
3990
  const now = this.#now();
@@ -2283,25 +4137,194 @@ export class ControlPlaneStore {
2283
4137
  };
2284
4138
  return input.requireCurrentSource ? this.#withActivationSource(plan.id, commit) : commit();
2285
4139
  }
4140
+ listLiveQualificationDeployments(planId, limit = 1001) {
4141
+ if (!Number.isSafeInteger(limit) || limit < 1 || limit > 1001)
4142
+ throw new ControlPlaneStoreError('invalid-input', 'invalid live task limit');
4143
+ const window = this.getLiveQualificationWindow(planId);
4144
+ const committed = this.#database.prepare('SELECT started_at FROM activation_watch WHERE plan_id=?').get(planId);
4145
+ const cutoff = Math.min(window.deadlineAt, committed ? Number(committed.started_at) : window.deadlineAt);
4146
+ return this.#database.prepare(`SELECT inbox_id FROM foreground_deployments WHERE plan_id=?
4147
+ AND json_extract(record_json,'$.task.dispatchedAt')>=? AND json_extract(record_json,'$.task.dispatchedAt')<?
4148
+ ORDER BY inbox_id LIMIT ?`).all(planId, window.startedAt, cutoff, limit)
4149
+ .map(row => this.getForegroundDeployment(row.inbox_id));
4150
+ }
4151
+ listLiveQualificationPlans(profilePath, limit = 100) {
4152
+ if (!Number.isSafeInteger(limit) || limit < 1 || limit > 1000)
4153
+ throw new ControlPlaneStoreError('invalid-input', 'invalid qualification limit');
4154
+ return this.#database.prepare(`SELECT plan.* FROM activation_plans plan WHERE plan.target_path=? AND plan.status IN
4155
+ ('awaiting-live-tasks','commit-pending','activated') AND json_type(plan.dossier_json,'$.liveQualification')='object'
4156
+ AND (plan.status<>'activated' OR (plan.id=(SELECT checkpoint.plan_id FROM activation_deployment_checkpoints checkpoint
4157
+ JOIN activation_plans newer ON newer.id=checkpoint.plan_id WHERE newer.target_path=plan.target_path ORDER BY checkpoint.exposure_order DESC LIMIT 1)
4158
+ AND NOT EXISTS (SELECT 1 FROM activation_plans active WHERE active.target_path=plan.target_path AND active.id<>plan.id
4159
+ AND active.status IN ('staging','awaiting-reload','awaiting-readiness','awaiting-live-tasks','awaiting-effect-blocked-replay',
4160
+ 'awaiting-shadow','awaiting-canary','awaiting-soak','awaiting-health','commit-pending','rollback-pending'))))
4161
+ ORDER BY plan.created_at DESC,plan.id LIMIT ?`).all(profilePath, limit).map(activationFromRow);
4162
+ }
4163
+ getLiveQualificationDeadline(planId) {
4164
+ const plan = this.getPlan(planId);
4165
+ if (!plan.dossier.liveQualification)
4166
+ return undefined;
4167
+ const window = this.#database.prepare('SELECT deadline_at FROM live_qualification_windows WHERE plan_id=?').get(planId);
4168
+ return window ? Number(window.deadline_at) : undefined;
4169
+ }
4170
+ getLiveQualificationWindow(planId) { return liveQualificationWindow(this.#database, this.getPlan(planId)); }
4171
+ getLiveQualificationReadiness(planId) { return liveQualificationReadiness(this.#database, this.getPlan(planId)); }
4172
+ getLiveQualification(id) { return liveQualificationRecord(this.#database, id); }
4173
+ listLiveQualifications(planId, limit = 100) {
4174
+ if (!Number.isSafeInteger(limit) || limit < 1 || limit > 1000)
4175
+ throw new ControlPlaneStoreError('invalid-input', 'invalid qualification record limit');
4176
+ return this.#database.prepare('SELECT id FROM live_qualification_batches WHERE plan_id=? ORDER BY created_at DESC,id LIMIT ?').all(planId, limit).map(row => this.getLiveQualification(row.id));
4177
+ }
4178
+ assertCurrentLiveQualification(id) {
4179
+ const { record, plan } = readLiveQualificationContext(this.#database, id);
4180
+ if (record.state === 'stale' || (plan.status !== 'activated' && (this.#now() >= record.batch.deadlineAt || this.#now() >= record.batch.expiresAt))) {
4181
+ throw new ControlPlaneStoreError('expired', 'live qualification expired or became stale');
4182
+ }
4183
+ }
4184
+ putLiveQualification(batch) {
4185
+ assertLiveQualificationBatch(batch);
4186
+ this.#database.exec('BEGIN IMMEDIATE');
4187
+ try {
4188
+ const plan = this.getPlan(batch.planId);
4189
+ if (plan.status !== 'awaiting-live-tasks' || this.#now() >= batch.deadlineAt || this.#now() >= batch.expiresAt)
4190
+ throw new ControlPlaneStoreError('expired', 'live qualification admission ended');
4191
+ const prior = this.getLiveQualification(batch.id);
4192
+ if (prior) {
4193
+ if (controlPlaneDigest(prior.batch) !== controlPlaneDigest(batch) || prior.state === 'stale')
4194
+ throw new ControlPlaneStoreError('conflict', 'live qualification identity reused');
4195
+ }
4196
+ else
4197
+ this.#database.prepare(`INSERT INTO live_qualification_batches
4198
+ (id,lane,plan_id,batch_json,batch_digest,state,created_at) VALUES (?,?,?,?,?,'pending',?)`).run(batch.id, batch.lane, batch.planId, JSON.stringify(batch), controlPlaneDigest(batch), this.#now());
4199
+ readLiveQualificationContext(this.#database, batch.id);
4200
+ this.#database.exec('COMMIT');
4201
+ }
4202
+ catch (error) {
4203
+ this.#database.exec('ROLLBACK');
4204
+ throw error;
4205
+ }
4206
+ }
4207
+ staleLiveQualification(id) {
4208
+ this.#database.prepare("UPDATE live_qualification_batches SET state='stale' WHERE id=? AND state IN ('pending','signed')").run(id);
4209
+ }
4210
+ async applyLiveQualification(input) {
4211
+ const record = this.getLiveQualification(input.receipt.batchId);
4212
+ if (!record)
4213
+ throw new ControlPlaneStoreError('not-found', 'live qualification batch absent');
4214
+ const receipt = verifyLiveQualificationReceipt(input.receipt, record.batch, input.publicKeyPem, this.#now());
4215
+ return input.withSourceFence(() => {
4216
+ this.#database.exec('BEGIN IMMEDIATE');
4217
+ try {
4218
+ const current = this.getLiveQualification(record.batch.id);
4219
+ if (current.state === 'applied') {
4220
+ if (controlPlaneDigest(current.receipt) !== controlPlaneDigest(receipt))
4221
+ throw new ControlPlaneStoreError('conflict', 'qualification receipt changed');
4222
+ this.#database.exec('COMMIT');
4223
+ return this.getPlan(record.batch.planId);
4224
+ }
4225
+ this.assertCurrentLiveQualification(record.batch.id);
4226
+ const plan = this.getPlan(record.batch.planId);
4227
+ if (plan.status !== 'awaiting-live-tasks' || this.#adoptionCoordinatorId !== undefined)
4228
+ throw new ControlPlaneStoreError('invalid-state', 'only target feedback runtime may qualify this deployment');
4229
+ const updated = this.#database.prepare(`UPDATE activation_plans SET status=?,revision=revision+1,updated_at=?,failure_code=?,activation_lease_until=NULL
4230
+ WHERE id=? AND revision=? AND activation_fence=? AND status='awaiting-live-tasks'`).run(receipt.disposition === 'qualified' ? 'commit-pending' : 'rollback-pending', this.#now(), receipt.disposition === 'qualified' ? null : 'live-qualification-failed', plan.id, plan.revision, record.batch.fence);
4231
+ if (Number(updated.changes) !== 1)
4232
+ throw new ControlPlaneStoreError('conflict', 'qualification lost plan CAS');
4233
+ this.#database.prepare("UPDATE live_qualification_batches SET state='applied',receipt_json=?,receipt_digest=? WHERE id=? AND state IN ('pending','signed')")
4234
+ .run(JSON.stringify(receipt), controlPlaneDigest(receipt), record.batch.id);
4235
+ this.#database.exec('COMMIT');
4236
+ return this.getPlan(plan.id);
4237
+ }
4238
+ catch (error) {
4239
+ this.#database.exec('ROLLBACK');
4240
+ throw error;
4241
+ }
4242
+ });
4243
+ }
4244
+ /** Negative-only Host seam. The signed terms permit conservative removal of unconfirmable qualification. */
4245
+ invalidateLiveQualification(input) {
4246
+ const reason = bounded(input.reason, 'qualification invalidation reason', 160);
4247
+ this.#database.exec('BEGIN IMMEDIATE');
4248
+ try {
4249
+ const plan = this.getPlan(input.planId), record = this.getLiveQualification(input.batchId);
4250
+ if (!record || record.batch.planId !== plan.id || record.state !== 'applied' || record.receipt?.disposition !== 'qualified'
4251
+ || record.batch.planDigest !== plan.digest || record.batch.activationId !== plan.activation?.id || !plan.dossier.liveQualification) {
4252
+ throw new ControlPlaneStoreError('conflict', 'qualification invalidation lacks exact accepted evidence');
4253
+ }
4254
+ if (plan.status === 'rollback-pending' || plan.status === 'rolled-back') {
4255
+ this.#database.exec('COMMIT');
4256
+ return plan;
4257
+ }
4258
+ if (!['commit-pending', 'activated'].includes(plan.status) || record.batch.fence !== plan.activation.fence)
4259
+ throw new ControlPlaneStoreError('conflict', 'qualification invalidation no longer targets current deployment');
4260
+ this.assertNoClaimedHostAttestation(plan.id);
4261
+ if (plan.status === 'activated') {
4262
+ const latest = this.#database.prepare(`SELECT checkpoint.plan_id,checkpoint.successful_order,checkpoint.exposure_order FROM activation_deployment_checkpoints checkpoint
4263
+ JOIN activation_plans candidate ON candidate.id=checkpoint.plan_id WHERE candidate.target_path=? ORDER BY checkpoint.exposure_order DESC LIMIT 1`).get(plan.target.profilePath);
4264
+ if (!latest || latest.plan_id !== plan.id || latest.successful_order !== latest.exposure_order || !plan.activation?.hostRecoveryRequired
4265
+ || !this.getActivationInstalledBaseline(plan.id))
4266
+ throw new ControlPlaneStoreError('conflict', 'qualification invalidation cannot restore a superseded deployment');
4267
+ }
4268
+ const competing = this.#database.prepare(`SELECT 1 FROM activation_plans WHERE target_path=? AND id<>? AND status IN
4269
+ ('staging','awaiting-reload','awaiting-readiness','awaiting-live-tasks','awaiting-effect-blocked-replay','awaiting-shadow','awaiting-canary','awaiting-soak','awaiting-health','commit-pending','rollback-pending')`).get(plan.target.profilePath, plan.id);
4270
+ if (competing)
4271
+ throw new ControlPlaneStoreError('conflict', 'another deployment owns the profile');
4272
+ const now = this.#now();
4273
+ this.#database.prepare(`INSERT INTO live_qualification_invalidations
4274
+ (plan_id,batch_id,batch_digest,receipt_digest,activation_id,fence,reason,invalidated_at) VALUES (?,?,?,?,?,?,?,?)`).run(plan.id, record.batch.id, record.batch.digest, controlPlaneDigest(record.receipt), record.batch.activationId, record.batch.fence, reason, now);
4275
+ // A live commit worker is fenced by revision immediately; its final source guard must fail.
4276
+ this.#database.prepare(`UPDATE activation_plans SET status='rollback-pending',revision=revision+1,
4277
+ activation_lease_until=NULL,rollback_profile_restored=0,failure_code='live-qualification-invalidated',updated_at=?
4278
+ WHERE id=? AND revision=? AND activation_fence=?`).run(now, plan.id, plan.revision, plan.activation.fence);
4279
+ this.#database.exec('COMMIT');
4280
+ return this.getPlan(plan.id);
4281
+ }
4282
+ catch (error) {
4283
+ this.#database.exec('ROLLBACK');
4284
+ throw error;
4285
+ }
4286
+ }
4287
+ #assertLiveQualificationCommit(planId) {
4288
+ const plan = this.getPlan(planId);
4289
+ if (!plan.dossier.liveQualification)
4290
+ return;
4291
+ if (!this.#withLiveQualificationFence)
4292
+ throw new ControlPlaneStoreError('invalid-state', 'target canonical qualification fence unavailable');
4293
+ const record = this.listLiveQualifications(planId).find(item => item.state === 'applied' && item.receipt?.disposition === 'qualified');
4294
+ if (!record || this.#now() >= record.batch.deadlineAt || this.#now() >= record.receipt.expiresAt)
4295
+ throw new ControlPlaneStoreError('expired', 'accepted live qualification is absent or expired');
4296
+ this.assertCurrentLiveQualification(record.batch.id);
4297
+ }
2286
4298
  /** Same synchronous transaction owns current-profile selection and capture. */
2287
4299
  withForegroundDeployment(task, profilePath, callback) {
2288
4300
  assertForegroundTask(task);
2289
4301
  this.#database.exec('BEGIN IMMEDIATE');
2290
4302
  try {
2291
4303
  const active = this.#database.prepare(`SELECT id FROM activation_plans WHERE target_path = ? AND status IN (
2292
- 'staging', 'awaiting-reload', 'awaiting-readiness', 'awaiting-effect-blocked-replay', 'awaiting-shadow',
4304
+ 'staging', 'awaiting-reload', 'awaiting-readiness', 'awaiting-live-tasks', 'awaiting-effect-blocked-replay', 'awaiting-shadow',
2293
4305
  'awaiting-canary', 'awaiting-soak', 'awaiting-health', 'commit-pending', 'rollback-pending') LIMIT 1`).get(profilePath);
2294
- if (active)
2295
- throw new ControlPlaneStoreError('conflict', 'profile has an unsettled activation');
2296
- const latest = this.#database.prepare(`SELECT checkpoint.plan_id, checkpoint.exposure_order, checkpoint.successful_order
2297
- FROM activation_deployment_checkpoints checkpoint JOIN activation_plans plan ON plan.id = checkpoint.plan_id
2298
- WHERE plan.target_path = ? ORDER BY checkpoint.exposure_order DESC LIMIT 1`).get(profilePath);
2299
- if (!latest || latest.exposure_order !== latest.successful_order)
2300
- throw new ControlPlaneStoreError('invalid-state', 'profile has no current successful deployment');
2301
- const plan = this.getPlan(latest.plan_id), watch = this.getActivationWatch(plan.id);
2302
- if (plan.status !== 'activated' || !plan.activation || watch.state !== 'watching'
2303
- || watch.activationId !== plan.activation.id || watch.fence !== plan.activation.fence || watch.startedAt > task.dispatchedAt) {
2304
- throw new ControlPlaneStoreError('invalid-state', 'task did not begin under a watched deployment');
4306
+ let plan;
4307
+ if (active) {
4308
+ plan = this.getPlan(String(active.id));
4309
+ if (!plan.dossier.liveQualification || !['awaiting-live-tasks', 'commit-pending'].includes(plan.status))
4310
+ throw new ControlPlaneStoreError('conflict', 'profile has an unsettled activation');
4311
+ const window = this.getLiveQualificationWindow(plan.id);
4312
+ if (task.dispatchedAt < window.startedAt || task.dispatchedAt >= window.deadlineAt || this.#now() >= window.deadlineAt) {
4313
+ throw new ControlPlaneStoreError('expired', 'task is outside the live qualification window');
4314
+ }
4315
+ }
4316
+ else {
4317
+ const latest = this.#database.prepare(`SELECT checkpoint.plan_id, checkpoint.exposure_order, checkpoint.successful_order
4318
+ FROM activation_deployment_checkpoints checkpoint JOIN activation_plans plan ON plan.id = checkpoint.plan_id
4319
+ WHERE plan.target_path = ? ORDER BY checkpoint.exposure_order DESC LIMIT 1`).get(profilePath);
4320
+ if (!latest || latest.exposure_order !== latest.successful_order)
4321
+ throw new ControlPlaneStoreError('invalid-state', 'profile has no current successful deployment');
4322
+ plan = this.getPlan(latest.plan_id);
4323
+ const watch = this.getActivationWatch(plan.id);
4324
+ if (plan.status !== 'activated' || !plan.activation || watch.state !== 'watching'
4325
+ || watch.activationId !== plan.activation.id || watch.fence !== plan.activation.fence || watch.startedAt > task.dispatchedAt) {
4326
+ throw new ControlPlaneStoreError('invalid-state', 'task did not begin under a watched deployment');
4327
+ }
2305
4328
  }
2306
4329
  const source = readOwnerSourceAdoptionPlan(this.#database, plan.id).source;
2307
4330
  const owner = source.owner;
@@ -2497,19 +4520,26 @@ export class ControlPlaneStore {
2497
4520
  }
2498
4521
  #isPostActivationRollback(plan) {
2499
4522
  if (plan.status !== 'rollback-pending' || plan.activation === undefined || plan.activation.failureCode === undefined
2500
- || !['post-activation-regressed', 'post-activation-retracted'].includes(plan.activation.failureCode))
4523
+ || !['post-activation-regressed', 'post-activation-retracted', 'live-qualification-invalidated'].includes(plan.activation.failureCode))
2501
4524
  return false;
2502
4525
  const checkpoint = this.#database.prepare('SELECT successful_order, exposure_order FROM activation_deployment_checkpoints WHERE plan_id = ?')
2503
4526
  .get(plan.id);
2504
4527
  return checkpoint !== undefined && checkpoint.successful_order === checkpoint.exposure_order;
2505
4528
  }
2506
4529
  #hasPostActivationRollbackProvenance(plan) {
2507
- if (plan.activation === undefined || !['post-activation-regressed', 'post-activation-retracted'].includes(plan.activation.failureCode ?? ''))
4530
+ if (plan.activation === undefined || !['post-activation-regressed', 'post-activation-retracted', 'live-qualification-invalidated'].includes(plan.activation.failureCode ?? ''))
2508
4531
  return false;
2509
4532
  const checkpoint = this.#database.prepare('SELECT successful_order, exposure_order FROM activation_deployment_checkpoints WHERE plan_id = ?')
2510
4533
  .get(plan.id);
2511
4534
  if (checkpoint === undefined || checkpoint.successful_order !== checkpoint.exposure_order)
2512
4535
  return false;
4536
+ if (plan.activation.failureCode === 'live-qualification-invalidated') {
4537
+ const invalidation = this.#database.prepare('SELECT * FROM live_qualification_invalidations WHERE plan_id=?').get(plan.id);
4538
+ const accepted = invalidation && this.getLiveQualification(invalidation.batch_id);
4539
+ return !!invalidation && accepted?.state === 'applied' && accepted.receipt?.disposition === 'qualified'
4540
+ && invalidation.batch_digest === accepted.batch.digest && invalidation.receipt_digest === controlPlaneDigest(accepted.receipt)
4541
+ && invalidation.activation_id === plan.activation.id && invalidation.fence === accepted.batch.fence && invalidation.fence <= plan.activation.fence;
4542
+ }
2513
4543
  const watch = this.#database.prepare('SELECT activation_id, fence, state, close_signature_digest FROM activation_watch WHERE plan_id = ?').get(plan.id);
2514
4544
  return watch !== undefined && (watch.state === 'closed-regressed' || watch.state === 'closed-retracted')
2515
4545
  && watch.close_signature_digest !== null && watch.activation_id === plan.activation.id && watch.fence <= plan.activation.fence;
@@ -2614,7 +4644,7 @@ export class ControlPlaneStore {
2614
4644
  AND NOT EXISTS (SELECT 1 FROM host_attestation_operations operation JOIN host_attestation_dispatches dispatch
2615
4645
  ON dispatch.operation_id = operation.operation_id WHERE operation.plan_id = activation_plans.id
2616
4646
  AND dispatch.status = 'claimed')
2617
- AND (status IN ('awaiting-reload', 'awaiting-readiness', 'awaiting-effect-blocked-replay', 'awaiting-shadow',
4647
+ AND (status IN ('awaiting-reload', 'awaiting-readiness', 'awaiting-live-tasks', 'awaiting-effect-blocked-replay', 'awaiting-shadow',
2618
4648
  'awaiting-canary', 'awaiting-soak', 'awaiting-health')
2619
4649
  OR (status IN ('staging', 'commit-pending') AND COALESCE(activation_lease_until, 0) < ?))`).run(code, this.#now(), input.planId, input.expectedRevision, input.fence, this.#now());
2620
4650
  if (Number(result.changes) !== 1)
@@ -2623,6 +4653,18 @@ export class ControlPlaneStore {
2623
4653
  }, true);
2624
4654
  }
2625
4655
  advanceActivation(input) {
4656
+ if (input.to === 'activated' && this.getPlan(input.planId).dossier.liveQualification && this.#liveQualificationAdmission !== input.planId) {
4657
+ this.#assertLiveQualificationCommit(input.planId);
4658
+ return this.#withLiveQualificationFence(input.planId, () => {
4659
+ this.#liveQualificationAdmission = input.planId;
4660
+ try {
4661
+ return this.withOwnerTaskFailureGapAdmission(this.getPlan(input.planId).gapId, () => this.advanceActivation(input));
4662
+ }
4663
+ finally {
4664
+ this.#liveQualificationAdmission = undefined;
4665
+ }
4666
+ });
4667
+ }
2626
4668
  return this.#withActivationSource(input.planId, () => {
2627
4669
  const allowed = { staging: ['awaiting-reload', 'rollback-pending'],
2628
4670
  'rollback-pending': ['rolled-back'], 'commit-pending': ['activated', 'rollback-pending'] };
@@ -2675,7 +4717,7 @@ export class ControlPlaneStore {
2675
4717
  if (replay !== undefined) {
2676
4718
  const transition = Object.values(expectedAttestation).find(item => item.phase === input.receipt.phase);
2677
4719
  const expectedStatus = input.receipt.outcome === 'passed' && transition !== undefined
2678
- ? transition.next : 'rollback-pending';
4720
+ ? (input.receipt.phase === 'readiness' && replay.result.dossier.liveQualification ? 'awaiting-live-tasks' : transition.next) : 'rollback-pending';
2679
4721
  const expectedFailure = input.receipt.outcome === 'passed' ? undefined : 'host-attestation-failed';
2680
4722
  if (replay.result.status !== expectedStatus || replay.result.revision !== input.expectedRevision + 1
2681
4723
  || replay.result.activation?.id !== input.receipt.activationId || replay.result.activation.fence !== input.expectedFence
@@ -2702,7 +4744,7 @@ export class ControlPlaneStore {
2702
4744
  throw new ControlPlaneStoreError('conflict', 'non-transition Host receipt changed generation from its predecessor');
2703
4745
  }
2704
4746
  const now = this.#now();
2705
- const nextStatus = verified.outcome === 'passed' ? expected.next : 'rollback-pending';
4747
+ const nextStatus = verified.outcome === 'passed' ? (verified.phase === 'readiness' && plan.dossier.liveQualification ? 'awaiting-live-tasks' : expected.next) : 'rollback-pending';
2706
4748
  return this.#withActivationSource(input.planId, () => {
2707
4749
  this.#database.exec('BEGIN IMMEDIATE');
2708
4750
  try {
@@ -2938,6 +4980,8 @@ export class ControlPlaneStore {
2938
4980
  ON CONFLICT(plan_id) DO NOTHING`).run(plan.id, plan.candidate.package, plan.candidate.version, plan.candidate.integrity, activationId, fence, now, now);
2939
4981
  if (Number(result.changes) !== 1)
2940
4982
  throw new ControlPlaneStoreError('invalid-state', 'activated plan has no post-activation watch');
4983
+ if (plan.dossier.liveQualification)
4984
+ this.#database.prepare('UPDATE activation_watch SET last_host_generation=? WHERE plan_id=?').run(this.getLiveQualificationWindow(plan.id).hostGeneration, plan.id);
2941
4985
  }
2942
4986
  }
2943
4987
  #reopenGapAfterActivation(plan, now) {
@@ -3085,9 +5129,64 @@ export class ControlPlaneStore {
3085
5129
  const now = this.#now();
3086
5130
  if (now > plan.expiresAt || now > verified.expiresAt)
3087
5131
  throw new ControlPlaneStoreError('expired', 'source release authorization is no longer applicable');
5132
+ const prelinked = this.#database.prepare('SELECT * FROM source_jobs WHERE plan_id=? LIMIT 2')
5133
+ .all(plan.id);
5134
+ const prebaseline = prelinked.length === 1 ? sourceJobFromRow(prelinked[0]).intent.baseline : undefined;
5135
+ const premaintenance = prebaseline ? this.getSourceMaintenanceRecords(plan.repository) : [];
5136
+ let lineageDigest;
5137
+ if (prebaseline && premaintenance.length) {
5138
+ if (!input.trust)
5139
+ throw new ControlPlaneStoreError('invalid-input', 'managed source maintenance requires current trust');
5140
+ const prehistory = this.getSourceBaselineHistory(plan.repository);
5141
+ await verifySourceBaselineHistory({ repository: plan.repository, config: prebaseline, trust: input.trust,
5142
+ history: prehistory, maintenance: premaintenance, signal: new AbortController().signal });
5143
+ lineageDigest = controlPlaneDigest({ history: prehistory, maintenance: premaintenance, trust: input.trust, baseline: prebaseline });
5144
+ }
3088
5145
  return withCurrentSource(plan.gapId, () => {
3089
5146
  this.#database.exec('BEGIN IMMEDIATE');
3090
5147
  try {
5148
+ const linkedRows = this.#database.prepare('SELECT * FROM source_jobs WHERE plan_id = ? LIMIT 2')
5149
+ .all(plan.id);
5150
+ if (linkedRows.length > 1)
5151
+ throw new ControlPlaneStoreError('invalid-state', 'source plan has multiple source jobs');
5152
+ const linked = linkedRows[0] === undefined ? undefined : sourceJobFromRow(linkedRows[0]);
5153
+ const managedRows = this.#database.prepare(`SELECT * FROM source_jobs WHERE json_extract(intent_json, '$.repository') = ?
5154
+ AND json_type(intent_json, '$.baseline') IS NOT NULL AND status IN ('queued', 'running', 'unknown', 'prepared')
5155
+ LIMIT 1001`).all(plan.repository);
5156
+ if (managedRows.length > 1000)
5157
+ throw new ControlPlaneStoreError('invalid-state', 'managed source job cohort exceeds bound');
5158
+ for (const row of managedRows) {
5159
+ const managed = sourceJobFromRow(row);
5160
+ if (managed.planId === plan.id)
5161
+ continue;
5162
+ if (managed.status !== 'prepared' || managed.planId === undefined
5163
+ || !['release-complete', 'release-failed', 'expired', 'local-checks-failed'].includes(this.getSourcePlan(managed.planId).status)) {
5164
+ throw new ControlPlaneStoreError('conflict', 'managed source job owns this repository release');
5165
+ }
5166
+ }
5167
+ if (linked?.intent.baseline !== undefined) {
5168
+ if (linked.status !== 'prepared' || linked.intent.repository !== plan.repository
5169
+ || linked.intent.baseCommit !== plan.baseCommit) {
5170
+ throw new ControlPlaneStoreError('invalid-state', 'managed source release is not bound to its prepared job');
5171
+ }
5172
+ const active = this.#database.prepare(`SELECT id FROM source_plans WHERE repository = ? AND id <> ?
5173
+ AND release_id IS NOT NULL AND status NOT IN ('release-complete', 'release-failed') LIMIT 1`)
5174
+ .get(plan.repository, plan.id);
5175
+ if (active !== undefined)
5176
+ throw new ControlPlaneStoreError('conflict', 'source repository has another active release');
5177
+ const history = this.getSourceBaselineHistory(plan.repository);
5178
+ const maintenance = this.getSourceMaintenanceRecords(plan.repository);
5179
+ if (lineageDigest !== undefined && controlPlaneDigest({ history, maintenance, trust: input.trust,
5180
+ baseline: linked.intent.baseline }) !== lineageDigest) {
5181
+ throw new ControlPlaneStoreError('conflict', 'managed source signed lineage changed before release');
5182
+ }
5183
+ if (maintenance.length && lineageDigest === undefined) {
5184
+ throw new ControlPlaneStoreError('conflict', 'managed source maintenance was not preverified');
5185
+ }
5186
+ const expectedCommit = sourceBaselineChain(linked.intent.baseline, history, maintenance).at(-1);
5187
+ if (plan.baseCommit !== expectedCommit)
5188
+ throw new ControlPlaneStoreError('conflict', 'managed source release base commit is stale');
5189
+ }
3091
5190
  const releaseId = `release-${randomUUID()}`;
3092
5191
  const result = this.#database.prepare(`UPDATE source_plans SET status = 'awaiting-pr', revision = revision + 1,
3093
5192
  release_authorization_json = ?, release_authorization_digest = ?, release_id = ?, release_fence = release_fence + 1,
@@ -3785,7 +5884,7 @@ export class ControlPlaneStore {
3785
5884
  const row = this.#database.prepare(`SELECT
3786
5885
  (SELECT count(*) FROM capability_gaps WHERE status = 'open') AS gaps,
3787
5886
  (SELECT count(*) FROM activation_plans WHERE status = 'approved') + (SELECT count(*) FROM source_plans WHERE status = 'approved') AS ready_plans,
3788
- (SELECT count(*) FROM activation_plans WHERE status IN ('staging', 'awaiting-reload', 'awaiting-readiness',
5887
+ (SELECT count(*) FROM activation_plans WHERE status IN ('staging', 'awaiting-reload', 'awaiting-readiness', 'awaiting-live-tasks',
3789
5888
  'awaiting-effect-blocked-replay', 'awaiting-shadow', 'awaiting-canary', 'awaiting-soak', 'awaiting-health', 'commit-pending')) AS active_activations,
3790
5889
  (SELECT count(*) FROM activation_plans WHERE status = 'rolled-back') +
3791
5890
  (SELECT count(*) FROM source_plans WHERE status IN ('local-checks-failed', 'release-failed')) AS failed,
@@ -3826,15 +5925,32 @@ export class ControlPlaneStore {
3826
5925
  const replayBinding = { operation: 'create-source-plan', gapId: snapshot.gapId, repository: snapshot.repository,
3827
5926
  worktree: snapshot.worktree, baseCommit: snapshot.baseCommit, name: snapshot.name, generatorDigest: snapshot.generatorDigest,
3828
5927
  scope: snapshot.scope, ttlMs: snapshot.expiresAt - snapshot.createdAt };
5928
+ const linkedRow = this.#database.prepare('SELECT * FROM source_jobs WHERE plan_id = ?').get(snapshot.id);
5929
+ const linked = linkedRow === undefined ? undefined : sourceJobFromRow(linkedRow);
5930
+ const sourceJob = linked === undefined ? undefined : { jobId: linked.id, jobRevision: linked.revision - 1,
5931
+ occurrenceId: linked.occurrenceId };
5932
+ const replayInput = { ...(snapshot.mode === 'create' ? replayBinding : { ...replayBinding,
5933
+ mode: snapshot.mode, ...(snapshot.creation === undefined ? {} : { creation: snapshot.creation }),
5934
+ ...(snapshot.sourceRevision === undefined ? {} : { revision: snapshot.sourceRevision }),
5935
+ prepared: { treeDigest: snapshot.sourceCheck.treeDigest, patchDigest: snapshot.sourceCheck.patchDigest,
5936
+ checkedAt: snapshot.sourceCheck.checkedAt, evidence: snapshot.preparedEvidence } }),
5937
+ ...(sourceJob === undefined ? {} : { sourceJob }) };
5938
+ const replayDigest = controlPlaneDigest(replayInput);
5939
+ const artifactReplayDigest = (snapshot.mode === 'prepared-create' || snapshot.mode === 'prepared-revise') && snapshot.preparedEvidence !== undefined
5940
+ ? controlPlaneDigest({ ...replayInput, preparedArtifactSha256: snapshot.preparedEvidence.pack.sha256 }) : undefined;
3829
5941
  if (snapshot.digest !== authoritative.digest || snapshot.gapId !== authoritative.gapId
3830
5942
  || snapshot.revision !== 1 || snapshot.status !== 'pending-approval' || snapshot.createdAt !== receipt.createdAt
3831
5943
  || snapshot.approval !== undefined || snapshot.releaseAuthorization !== undefined
3832
- || snapshot.release !== undefined || controlPlaneDigest(snapshot.mode === 'modify' ? { ...replayBinding, mode: snapshot.mode,
3833
- prepared: { treeDigest: snapshot.sourceCheck.treeDigest, patchDigest: snapshot.sourceCheck.patchDigest,
3834
- checkedAt: snapshot.sourceCheck.checkedAt, evidence: snapshot.preparedEvidence } } : replayBinding) !== inputDigest
5944
+ || snapshot.release !== undefined || (replayDigest !== inputDigest && artifactReplayDigest !== inputDigest)
3835
5945
  || (snapshot.mode === 'create' && (snapshot.sourceCheck !== undefined || snapshot.preparedEvidence !== undefined))
3836
5946
  || (snapshot.mode === 'modify' && (snapshot.sourceCheck === undefined || snapshot.preparedEvidence === undefined
3837
- || controlPlaneDigest(snapshot.sourceCheck) !== controlPlaneDigest(authoritative.sourceCheck)))) {
5947
+ || controlPlaneDigest(snapshot.sourceCheck) !== controlPlaneDigest(authoritative.sourceCheck)))
5948
+ || (snapshot.mode === 'prepared-create' && (snapshot.sourceCheck === undefined || snapshot.preparedEvidence === undefined
5949
+ || snapshot.creation === undefined || linked?.intent.mode !== 'create'
5950
+ || controlPlaneDigest(snapshot.creation) !== controlPlaneDigest(authoritative.creation)))
5951
+ || (snapshot.mode === 'prepared-revise' && (snapshot.sourceCheck === undefined || snapshot.preparedEvidence === undefined
5952
+ || snapshot.sourceRevision === undefined || linked?.intent.mode !== 'revise-created'
5953
+ || controlPlaneDigest(snapshot.sourceRevision) !== controlPlaneDigest(authoritative.sourceRevision)))) {
3838
5954
  throw new ControlPlaneStoreError('invalid-state', 'stored source operation receipt is not bound to authoritative state');
3839
5955
  }
3840
5956
  return { ...receipt, result: snapshot };
@@ -3867,7 +5983,7 @@ export class ControlPlaneStore {
3867
5983
  catalog: { digest: snapshot.dossier.catalogDigest, provenance: snapshot.dossier.catalogProvenance },
3868
5984
  matchedCapabilities: snapshot.dossier.matchedCapabilities, profile: snapshot.profile, target: snapshot.target,
3869
5985
  installationId: snapshot.installationId, ledger: snapshot.ledger, executor: snapshot.executor,
3870
- ttlMs: snapshot.expiresAt - snapshot.createdAt, ...(adoption ? { sourcePlanId: adoption.source_plan_id } : {}), ...(snapshot.dossier.handoff === undefined ? {} : { handoff: snapshot.dossier.handoff }) };
5986
+ ttlMs: snapshot.expiresAt - snapshot.createdAt, ...(adoption ? { sourcePlanId: adoption.source_plan_id } : {}), ...(snapshot.dossier.handoff === undefined ? {} : { handoff: snapshot.dossier.handoff }), ...(snapshot.dossier.liveQualification === undefined ? {} : { liveQualification: snapshot.dossier.liveQualification }) };
3871
5987
  if (snapshot.digest !== authoritative.digest || snapshot.gapId !== authoritative.gapId || snapshot.revision !== 1
3872
5988
  || snapshot.status !== 'pending-approval' || snapshot.createdAt !== receipt.createdAt
3873
5989
  || snapshot.approval !== undefined || snapshot.activation !== undefined || controlPlaneDigest(replayBinding) !== inputDigest) {