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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (185) hide show
  1. package/README.md +187 -15
  2. package/bin/dsh-live-qualification-authority.js +15 -0
  3. package/bin/dsh-local-release-adapter.js +331 -9
  4. package/bin/dsh-systemd-host-attestor.js +359 -9
  5. package/bin/dsh-systemd-host-authority.js +12 -0
  6. package/lib/adoption-coordinator.d.ts +3 -1
  7. package/lib/adoption-coordinator.d.ts.map +1 -1
  8. package/lib/adoption-coordinator.js +24 -9
  9. package/lib/adoption-coordinator.js.map +1 -1
  10. package/lib/cli.d.ts.map +1 -1
  11. package/lib/cli.js +33 -14
  12. package/lib/cli.js.map +1 -1
  13. package/lib/creation-capability-feedback.d.ts +23 -0
  14. package/lib/creation-capability-feedback.d.ts.map +1 -0
  15. package/lib/creation-capability-feedback.js +132 -0
  16. package/lib/creation-capability-feedback.js.map +1 -0
  17. package/lib/creation-capability-journal.d.ts +66 -0
  18. package/lib/creation-capability-journal.d.ts.map +1 -0
  19. package/lib/creation-capability-journal.js +775 -0
  20. package/lib/creation-capability-journal.js.map +1 -0
  21. package/lib/creation-capability-runtime.d.ts +63 -0
  22. package/lib/creation-capability-runtime.d.ts.map +1 -0
  23. package/lib/creation-capability-runtime.js +578 -0
  24. package/lib/creation-capability-runtime.js.map +1 -0
  25. package/lib/creation-capability-source.d.ts +37 -0
  26. package/lib/creation-capability-source.d.ts.map +1 -0
  27. package/lib/creation-capability-source.js +328 -0
  28. package/lib/creation-capability-source.js.map +1 -0
  29. package/lib/creation-capability-types.d.ts +265 -0
  30. package/lib/creation-capability-types.d.ts.map +1 -0
  31. package/lib/creation-capability-types.js +2 -0
  32. package/lib/creation-capability-types.js.map +1 -0
  33. package/lib/deployment-readiness.d.ts +16 -0
  34. package/lib/deployment-readiness.d.ts.map +1 -1
  35. package/lib/deployment-readiness.js +48 -1
  36. package/lib/deployment-readiness.js.map +1 -1
  37. package/lib/effect-blocked-replay.js +2 -2
  38. package/lib/effect-blocked-replay.js.map +1 -1
  39. package/lib/foreground-deployment-runtime.d.ts +3 -0
  40. package/lib/foreground-deployment-runtime.d.ts.map +1 -1
  41. package/lib/foreground-deployment-runtime.js +55 -13
  42. package/lib/foreground-deployment-runtime.js.map +1 -1
  43. package/lib/foreground-deployment.d.ts.map +1 -1
  44. package/lib/foreground-deployment.js +5 -0
  45. package/lib/foreground-deployment.js.map +1 -1
  46. package/lib/host-attestor.d.ts +2 -0
  47. package/lib/host-attestor.d.ts.map +1 -1
  48. package/lib/host-attestor.js +8 -1
  49. package/lib/host-attestor.js.map +1 -1
  50. package/lib/host-inputs.d.ts +13 -0
  51. package/lib/host-inputs.d.ts.map +1 -0
  52. package/lib/host-inputs.js +162 -0
  53. package/lib/host-inputs.js.map +1 -0
  54. package/lib/host-maintenance.d.ts +61 -0
  55. package/lib/host-maintenance.d.ts.map +1 -0
  56. package/lib/host-maintenance.js +158 -0
  57. package/lib/host-maintenance.js.map +1 -0
  58. package/lib/host-readiness.d.ts +5 -0
  59. package/lib/host-readiness.d.ts.map +1 -0
  60. package/lib/host-readiness.js +22 -0
  61. package/lib/host-readiness.js.map +1 -0
  62. package/lib/index.d.ts +16 -0
  63. package/lib/index.d.ts.map +1 -1
  64. package/lib/index.js +7 -0
  65. package/lib/index.js.map +1 -1
  66. package/lib/live-qualification-authority.d.ts +32 -0
  67. package/lib/live-qualification-authority.d.ts.map +1 -0
  68. package/lib/live-qualification-authority.js +280 -0
  69. package/lib/live-qualification-authority.js.map +1 -0
  70. package/lib/live-qualification-client.d.ts +4 -0
  71. package/lib/live-qualification-client.d.ts.map +1 -0
  72. package/lib/live-qualification-client.js +22 -0
  73. package/lib/live-qualification-client.js.map +1 -0
  74. package/lib/live-qualification-runtime.d.ts +78 -0
  75. package/lib/live-qualification-runtime.d.ts.map +1 -0
  76. package/lib/live-qualification-runtime.js +367 -0
  77. package/lib/live-qualification-runtime.js.map +1 -0
  78. package/lib/live-qualification.d.ts +73 -0
  79. package/lib/live-qualification.d.ts.map +1 -0
  80. package/lib/live-qualification.js +164 -0
  81. package/lib/live-qualification.js.map +1 -0
  82. package/lib/native-source-build-worker.d.ts +4 -0
  83. package/lib/native-source-build-worker.d.ts.map +1 -0
  84. package/lib/native-source-build-worker.js +87 -0
  85. package/lib/native-source-build-worker.js.map +1 -0
  86. package/lib/native-source-build.d.ts +36 -0
  87. package/lib/native-source-build.d.ts.map +1 -0
  88. package/lib/native-source-build.js +143 -0
  89. package/lib/native-source-build.js.map +1 -0
  90. package/lib/native-source-pack.d.ts +11 -0
  91. package/lib/native-source-pack.d.ts.map +1 -0
  92. package/lib/native-source-pack.js +83 -0
  93. package/lib/native-source-pack.js.map +1 -0
  94. package/lib/owner-task-gaps.d.ts +2 -0
  95. package/lib/owner-task-gaps.d.ts.map +1 -1
  96. package/lib/owner-task-gaps.js +9 -1
  97. package/lib/owner-task-gaps.js.map +1 -1
  98. package/lib/runtime-epoch.d.ts +69 -0
  99. package/lib/runtime-epoch.d.ts.map +1 -0
  100. package/lib/runtime-epoch.js +139 -0
  101. package/lib/runtime-epoch.js.map +1 -0
  102. package/lib/runtime-observer-protocol.d.ts +3 -3
  103. package/lib/runtime-observer-protocol.js +5 -5
  104. package/lib/runtime-observer-protocol.js.map +1 -1
  105. package/lib/runtime-observer.d.ts.map +1 -1
  106. package/lib/runtime-observer.js +42 -6
  107. package/lib/runtime-observer.js.map +1 -1
  108. package/lib/service.d.ts +248 -4
  109. package/lib/service.d.ts.map +1 -1
  110. package/lib/service.js +1431 -64
  111. package/lib/service.js.map +1 -1
  112. package/lib/source-adoption-authority.d.ts +3 -0
  113. package/lib/source-adoption-authority.d.ts.map +1 -1
  114. package/lib/source-adoption-authority.js +13 -3
  115. package/lib/source-adoption-authority.js.map +1 -1
  116. package/lib/source-adoption-runner.d.ts +3 -0
  117. package/lib/source-adoption-runner.d.ts.map +1 -1
  118. package/lib/source-adoption-runner.js +16 -4
  119. package/lib/source-adoption-runner.js.map +1 -1
  120. package/lib/source-approval-authority.d.ts.map +1 -1
  121. package/lib/source-approval-authority.js +1 -0
  122. package/lib/source-approval-authority.js.map +1 -1
  123. package/lib/source-baseline.d.ts +37 -0
  124. package/lib/source-baseline.d.ts.map +1 -0
  125. package/lib/source-baseline.js +250 -0
  126. package/lib/source-baseline.js.map +1 -0
  127. package/lib/source-build.d.ts +43 -9
  128. package/lib/source-build.d.ts.map +1 -1
  129. package/lib/source-build.js +670 -57
  130. package/lib/source-build.js.map +1 -1
  131. package/lib/source-context.d.ts +18 -0
  132. package/lib/source-context.d.ts.map +1 -1
  133. package/lib/source-context.js +85 -11
  134. package/lib/source-context.js.map +1 -1
  135. package/lib/source-creation.d.ts +106 -0
  136. package/lib/source-creation.d.ts.map +1 -0
  137. package/lib/source-creation.js +664 -0
  138. package/lib/source-creation.js.map +1 -0
  139. package/lib/source-job-types.d.ts +28 -1
  140. package/lib/source-job-types.d.ts.map +1 -1
  141. package/lib/source-jobs.d.ts +33 -3
  142. package/lib/source-jobs.d.ts.map +1 -1
  143. package/lib/source-jobs.js +420 -43
  144. package/lib/source-jobs.js.map +1 -1
  145. package/lib/source-maintenance.d.ts +66 -0
  146. package/lib/source-maintenance.d.ts.map +1 -0
  147. package/lib/source-maintenance.js +177 -0
  148. package/lib/source-maintenance.js.map +1 -0
  149. package/lib/source-revision.d.ts +57 -0
  150. package/lib/source-revision.d.ts.map +1 -0
  151. package/lib/source-revision.js +175 -0
  152. package/lib/source-revision.js.map +1 -0
  153. package/lib/source-workspace.d.ts +19 -14
  154. package/lib/source-workspace.d.ts.map +1 -1
  155. package/lib/source-workspace.js +90 -17
  156. package/lib/source-workspace.js.map +1 -1
  157. package/lib/sqlite.d.ts +13 -3
  158. package/lib/sqlite.d.ts.map +1 -1
  159. package/lib/sqlite.js +362 -1
  160. package/lib/sqlite.js.map +1 -1
  161. package/lib/store.d.ts +258 -3
  162. package/lib/store.d.ts.map +1 -1
  163. package/lib/store.js +2257 -96
  164. package/lib/store.js.map +1 -1
  165. package/lib/systemd-host-authority.d.ts +68 -0
  166. package/lib/systemd-host-authority.d.ts.map +1 -0
  167. package/lib/systemd-host-authority.js +424 -0
  168. package/lib/systemd-host-authority.js.map +1 -0
  169. package/lib/task-observation-evidence.d.ts +32 -0
  170. package/lib/task-observation-evidence.d.ts.map +1 -0
  171. package/lib/task-observation-evidence.js +62 -0
  172. package/lib/task-observation-evidence.js.map +1 -0
  173. package/lib/task-observation-runtime.d.ts +2 -3
  174. package/lib/task-observation-runtime.d.ts.map +1 -1
  175. package/lib/task-observation-runtime.js +7 -39
  176. package/lib/task-observation-runtime.js.map +1 -1
  177. package/lib/task-observation-store.js +1 -1
  178. package/lib/trust.d.ts.map +1 -1
  179. package/lib/trust.js +0 -0
  180. package/lib/trust.js.map +1 -1
  181. package/lib/types.d.ts +57 -7
  182. package/lib/types.d.ts.map +1 -1
  183. package/lib/version.d.ts +1 -1
  184. package/lib/version.js +1 -1
  185. package/package.json +22 -14
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);
@@ -373,6 +643,29 @@ function activationFromRow(row) {
373
643
  }
374
644
  function preparedEvidenceFromStored(value) {
375
645
  const item = objectRecord(value, 'stored source prepared evidence');
646
+ if (item['schemaVersion'] === 2) {
647
+ exactKeys(item, ['schemaVersion', 'kind', 'boundary', 'runtimeDigest', 'fixedCompilerSha256', 'checks', 'pack', 'preparedAt'], 'stored native source prepared evidence');
648
+ const pack = objectRecord(item['pack'], 'stored native source pack');
649
+ exactKeys(pack, ['name', 'version', 'sizeBytes', 'sha256'], 'stored native source pack');
650
+ const checks = item['checks'];
651
+ if (item['kind'] !== 'dsh-source-prepared-evidence' || item['boundary'] !== 'macos-seatbelt-single-process-v1'
652
+ || !DIGEST.test(String(item['runtimeDigest'])) || !DIGEST.test(String(item['fixedCompilerSha256']))
653
+ || !Array.isArray(checks) || checks.length !== 3 || checks[0] !== 'semantic-typecheck'
654
+ || checks[1] !== 'js-declaration-emit' || checks[2] !== 'host-safe-pack'
655
+ || typeof pack['name'] !== 'string' || pack['name'] === '' || pack['name'].length > 214
656
+ || typeof pack['version'] !== 'string' || !/^[A-Za-z0-9][A-Za-z0-9._+=-]{0,63}$/u.test(pack['version'])
657
+ || !Number.isSafeInteger(pack['sizeBytes']) || Number(pack['sizeBytes']) < 1 || Number(pack['sizeBytes']) > 512 * 1024
658
+ || !DIGEST.test(String(pack['sha256']))
659
+ || !Number.isSafeInteger(item['preparedAt']) || Number(item['preparedAt']) < 0) {
660
+ throw new ControlPlaneStoreError('invalid-state', 'stored native source prepared evidence is corrupt');
661
+ }
662
+ return Object.freeze({ schemaVersion: 2, kind: 'dsh-source-prepared-evidence',
663
+ boundary: 'macos-seatbelt-single-process-v1', runtimeDigest: String(item['runtimeDigest']),
664
+ fixedCompilerSha256: String(item['fixedCompilerSha256']),
665
+ checks: Object.freeze(['semantic-typecheck', 'js-declaration-emit', 'host-safe-pack']),
666
+ pack: Object.freeze({ name: String(pack['name']), version: String(pack['version']),
667
+ sizeBytes: Number(pack['sizeBytes']), sha256: String(pack['sha256']) }), preparedAt: Number(item['preparedAt']) });
668
+ }
376
669
  exactKeys(item, ['schemaVersion', 'kind', 'environment', 'commands', 'pack', 'preparedAt'], 'stored source prepared evidence');
377
670
  const environment = objectRecord(item['environment'], 'stored source prepared evidence environment');
378
671
  exactKeys(environment, ['npmConfigIgnoreScripts', 'frozenLockfile', 'offline', 'nodeVersion', 'pnpmVersion'], 'stored source prepared evidence environment');
@@ -388,15 +681,18 @@ function preparedEvidenceFromStored(value) {
388
681
  const commands = item['commands'].map((entry, index) => {
389
682
  const command = objectRecord(entry, `stored source prepared evidence command ${index}`);
390
683
  exactKeys(command, ['command', 'args', 'exitCode', 'durationMs', 'logDigest'], `stored source prepared evidence command ${index}`);
684
+ const args = command['args'];
685
+ const scriptIndex = command['command'] === 'docker' && Array.isArray(args) && args.at(-2) === '-ceu'
686
+ ? args.length - 1 : -1;
391
687
  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)
688
+ || !Array.isArray(args) || args.length > 96
689
+ || args.some((arg, argIndex) => typeof arg !== 'string' || arg.length > (argIndex === scriptIndex ? 32_768 : 4096))
394
690
  || command['exitCode'] !== 0
395
691
  || !Number.isSafeInteger(command['durationMs']) || Number(command['durationMs']) < 0
396
692
  || !DIGEST.test(String(command['logDigest']))) {
397
693
  throw new ControlPlaneStoreError('invalid-state', `stored source prepared evidence command ${index} is corrupt`);
398
694
  }
399
- return Object.freeze({ command: String(command['command']), args: Object.freeze([...command['args']]),
695
+ return Object.freeze({ command: String(command['command']), args: Object.freeze([...args]),
400
696
  exitCode: 0, durationMs: Number(command['durationMs']), logDigest: String(command['logDigest']) });
401
697
  });
402
698
  const pack = objectRecord(item['pack'], 'stored source prepared evidence pack');
@@ -415,8 +711,21 @@ function preparedEvidenceFromStored(value) {
415
711
  sizeBytes: Number(pack['sizeBytes']), sha256: String(pack['sha256']) }),
416
712
  preparedAt: Number(item['preparedAt']) });
417
713
  }
714
+ function preparedArtifactBytesFromRow(row, pack) {
715
+ if (row.pack_sha256 !== pack.sha256 || !Number.isSafeInteger(row.size_bytes)
716
+ || row.size_bytes < 1 || row.size_bytes > MAX_PREPARED_ARTIFACT_BYTES
717
+ || row.size_bytes !== pack.sizeBytes || !(row.bytes instanceof Uint8Array)
718
+ || !Number.isSafeInteger(row.created_at) || row.created_at < 0) {
719
+ throw new ControlPlaneStoreError('invalid-state', 'stored prepared source artifact metadata is corrupt');
720
+ }
721
+ const bytes = Buffer.from(row.bytes);
722
+ if (bytes.length !== row.size_bytes || createHash('sha256').update(bytes).digest('hex') !== pack.sha256) {
723
+ throw new ControlPlaneStoreError('invalid-state', 'stored prepared source artifact bytes are corrupt');
724
+ }
725
+ return bytes;
726
+ }
418
727
  function sourceFromRow(row) {
419
- if (row.mode !== 'create' && row.mode !== 'modify') {
728
+ if (row.mode !== 'create' && row.mode !== 'modify' && row.mode !== 'prepared-create' && row.mode !== 'prepared-revise') {
420
729
  throw new ControlPlaneStoreError('invalid-state', 'stored source plan has an unknown mode');
421
730
  }
422
731
  const gapSnapshot = JSON.parse(row.gap_snapshot_json);
@@ -439,9 +748,23 @@ function sourceFromRow(row) {
439
748
  throw new ControlPlaneStoreError('invalid-state', 'stored source prepared evidence is corrupt');
440
749
  }
441
750
  }
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))) {
751
+ let creation;
752
+ if (row.creation_json !== null) {
753
+ try {
754
+ creation = creationBindingFromStored(JSON.parse(row.creation_json));
755
+ }
756
+ catch {
757
+ throw new ControlPlaneStoreError('invalid-state', 'stored source creation binding is corrupt');
758
+ }
759
+ }
760
+ const revision = row.revision_json === null ? undefined : revisionBindingFromStored(JSON.parse(row.revision_json));
761
+ const prepared = row.mode === 'modify' || row.mode === 'prepared-create' || row.mode === 'prepared-revise';
762
+ if (prepared !== (preparedEvidence !== undefined) || (row.mode === 'modify' && preparedEvidence?.schemaVersion !== 1)
763
+ || (row.mode === 'prepared-create') !== (creation !== undefined) || (row.mode === 'prepared-revise') !== (revision !== undefined)
764
+ || ((row.mode === 'prepared-create' || row.mode === 'prepared-revise') && (!['pending-approval', 'expired'].includes(row.status) || (creation ?? revision)?.generatorDigest !== row.generator_digest
765
+ || row.approval_json !== null || row.release_authorization_json !== null || row.release_id !== null))
766
+ || (prepared && (row.checked_tree_digest === null || row.checked_patch_digest === null || row.checked_at === null))
767
+ || (prepared && ['running-local-checks', 'local-checks-failed'].includes(row.status))) {
445
768
  throw new ControlPlaneStoreError('invalid-state', 'stored source plan mode does not match its evidence or status');
446
769
  }
447
770
  let approval;
@@ -463,8 +786,8 @@ function sourceFromRow(row) {
463
786
  || !Number.isSafeInteger(sourceCheck.checkedAt) || sourceCheck.checkedAt < 0))) {
464
787
  throw new ControlPlaneStoreError('invalid-state', 'stored source check evidence is corrupt');
465
788
  }
466
- const digestBinding = row.mode === 'modify'
467
- ? { ...immutable, mode: row.mode, sourceCheck, preparedEvidence }
789
+ const digestBinding = prepared
790
+ ? { ...immutable, mode: row.mode, ...(creation === undefined ? {} : { creation }), ...(revision === undefined ? {} : { sourceRevision: revision }), sourceCheck, preparedEvidence }
468
791
  : immutable;
469
792
  if (controlPlaneDigest(digestBinding) !== row.plan_digest) {
470
793
  throw new ControlPlaneStoreError('invalid-state', 'stored source plan is corrupt or digest-mismatched');
@@ -493,7 +816,7 @@ function sourceFromRow(row) {
493
816
  && (row.release_id === null || releaseAuthorization === undefined || sourceCheck === undefined))) {
494
817
  throw new ControlPlaneStoreError('invalid-state', 'stored source release state is incomplete');
495
818
  }
496
- return { ...immutable, mode: row.mode, digest: row.plan_digest, status: row.status, revision: row.revision,
819
+ return { ...immutable, mode: row.mode, ...(creation === undefined ? {} : { creation }), ...(revision === undefined ? {} : { sourceRevision: revision }), digest: row.plan_digest, status: row.status, revision: row.revision,
497
820
  ...(approval === undefined ? {} : { approval }),
498
821
  ...(sourceCheck === undefined ? {} : { sourceCheck }),
499
822
  ...(preparedEvidence === undefined ? {} : { preparedEvidence }),
@@ -507,14 +830,14 @@ function sourceSnapshotFromStored(value) {
507
830
  // `mode` was introduced in schema v13; receipt rows written by older binaries
508
831
  // omit it and are backfilled to 'create'. `preparedEvidence` exists only on
509
832
  // 'modify' plans.
510
- const optional = ['approval', 'sourceCheck', 'preparedEvidence', 'releaseAuthorization', 'release']
833
+ const optional = ['approval', 'sourceCheck', 'preparedEvidence', 'creation', 'sourceRevision', 'releaseAuthorization', 'release']
511
834
  .filter(key => Object.hasOwn(item, key));
512
835
  exactKeys(item, [...SOURCE_PLAN_KEYS, 'mode', ...optional], 'stored source plan snapshot');
513
836
  const rawMode = item['mode'];
514
837
  const mode = rawMode === undefined
515
838
  ? 'create'
516
- : rawMode === 'create' || rawMode === 'modify' ? rawMode : 'create';
517
- const modeValid = rawMode === undefined || rawMode === 'create' || rawMode === 'modify';
839
+ : rawMode === 'create' || rawMode === 'modify' || rawMode === 'prepared-create' || rawMode === 'prepared-revise' ? rawMode : 'create';
840
+ const modeValid = rawMode === undefined || rawMode === 'create' || rawMode === 'modify' || rawMode === 'prepared-create' || rawMode === 'prepared-revise';
518
841
  const gapSnapshot = objectRecord(item['gapSnapshot'], 'stored source gap snapshot');
519
842
  exactKeys(gapSnapshot, ['revision', 'inputDigest', 'roi', 'capability'], 'stored source gap snapshot');
520
843
  if (item['schemaVersion'] !== 1 || item['kind'] !== 'source' || typeof item['id'] !== 'string'
@@ -527,8 +850,9 @@ function sourceSnapshotFromStored(value) {
527
850
  || typeof item['name'] !== 'string' || !PLUGIN_NAME.test(item['name']) || typeof item['generatorDigest'] !== 'string'
528
851
  || !DIGEST.test(item['generatorDigest']) || !Array.isArray(item['scope'])
529
852
  || controlPlaneDigest(item['scope']) !== controlPlaneDigest(expectedSourceScope(item['name'], mode))
530
- || (mode === 'modify' && item['preparedEvidence'] === undefined)
531
- || (mode === 'create' && item['preparedEvidence'] !== undefined)
853
+ || ((mode === 'modify' || mode === 'prepared-create' || mode === 'prepared-revise') !== (item['preparedEvidence'] !== undefined))
854
+ || ((mode === 'prepared-create') !== (item['creation'] !== undefined))
855
+ || ((mode === 'prepared-revise') !== (item['sourceRevision'] !== undefined))
532
856
  || !Number.isSafeInteger(gapSnapshot['revision']) || Number(gapSnapshot['revision']) < 1
533
857
  || typeof gapSnapshot['inputDigest'] !== 'string' || !DIGEST.test(gapSnapshot['inputDigest'])
534
858
  || typeof gapSnapshot['roi'] !== 'number' || !Number.isFinite(gapSnapshot['roi']) || typeof gapSnapshot['capability'] !== 'string') {
@@ -538,6 +862,10 @@ function sourceSnapshotFromStored(value) {
538
862
  repository: item['repository'], worktree: item['worktree'], baseCommit: item['baseCommit'], name: item['name'],
539
863
  generatorDigest: item['generatorDigest'], scope: item['scope'], createdAt: item['createdAt'], expiresAt: item['expiresAt'] };
540
864
  const preparedEvidence = item['preparedEvidence'] === undefined ? undefined : preparedEvidenceFromStored(item['preparedEvidence']);
865
+ const creation = item['creation'] === undefined ? undefined : creationBindingFromStored(item['creation']);
866
+ const revision = item['sourceRevision'] === undefined ? undefined : revisionBindingFromStored(item['sourceRevision']);
867
+ if ((mode === 'prepared-create' || mode === 'prepared-revise') && (creation ?? revision)?.generatorDigest !== item['generatorDigest'])
868
+ throw new ControlPlaneStoreError('invalid-state', 'stored source creation generator changed');
541
869
  const approval = item['approval'] === undefined ? undefined : verifiedApprovalFromStored(item['approval'], item['id'], item['digest'], Number(item['createdAt']), Number(item['expiresAt']));
542
870
  let sourceCheck;
543
871
  if (item['sourceCheck'] !== undefined) {
@@ -550,7 +878,8 @@ function sourceSnapshotFromStored(value) {
550
878
  }
551
879
  sourceCheck = check;
552
880
  }
553
- const digestBinding = mode === 'modify' ? { ...immutable, mode, sourceCheck, preparedEvidence } : immutable;
881
+ const digestBinding = mode === 'modify' || mode === 'prepared-create' || mode === 'prepared-revise'
882
+ ? { ...immutable, mode, ...(creation === undefined ? {} : { creation }), ...(revision === undefined ? {} : { sourceRevision: revision }), sourceCheck, preparedEvidence } : immutable;
554
883
  if (controlPlaneDigest(digestBinding) !== item['digest'])
555
884
  throw new ControlPlaneStoreError('invalid-state', 'stored source plan snapshot digest is corrupt');
556
885
  let releaseAuthorization;
@@ -591,18 +920,19 @@ function sourceSnapshotFromStored(value) {
591
920
  // running-local-checks / local-checks-failed states.
592
921
  const postCheckStates = ['ready-for-human-review', 'release-complete',
593
922
  'release-failed', 'publish-ambiguous'];
594
- const requiresCheck = mode === 'modify'
923
+ const requiresCheck = mode === 'modify' || mode === 'prepared-create' || mode === 'prepared-revise'
595
924
  || expectedSourceRelease(status) !== undefined || postCheckStates.includes(status);
596
925
  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))
926
+ if (((mode === 'modify' || mode === 'prepared-create' || mode === 'prepared-revise') && (['running-local-checks', 'local-checks-failed'].includes(status) || sourceCheck === undefined))
927
+ || ((mode === 'prepared-create' || mode === 'prepared-revise') && (!['pending-approval', 'expired'].includes(status) || approval !== undefined || releaseAuthorization !== undefined || release !== undefined))
598
928
  || (status !== 'expired' && requiresApproval !== (approval !== undefined))
599
- || (status === 'expired' && mode !== 'modify')
929
+ || (status === 'expired' && mode !== 'modify' && mode !== 'prepared-create' && mode !== 'prepared-revise')
600
930
  || requiresCheck !== (sourceCheck !== undefined)
601
931
  || requiresRelease !== (releaseAuthorization !== undefined && release !== undefined)
602
932
  || (!requiresRelease && (releaseAuthorization !== undefined || release !== undefined))) {
603
933
  throw new ControlPlaneStoreError('invalid-state', 'stored source plan snapshot fields do not match its status');
604
934
  }
605
- return { ...immutable, mode, digest: item['digest'], status: item['status'], revision: Number(item['revision']),
935
+ return { ...immutable, mode, ...(creation === undefined ? {} : { creation }), ...(revision === undefined ? {} : { sourceRevision: revision }), digest: item['digest'], status: item['status'], revision: Number(item['revision']),
606
936
  ...(approval === undefined ? {} : { approval }), ...(sourceCheck === undefined ? {} : { sourceCheck }),
607
937
  ...(preparedEvidence === undefined ? {} : { preparedEvidence }),
608
938
  ...(releaseAuthorization === undefined ? {} : { releaseAuthorization }),
@@ -782,8 +1112,11 @@ function releaseArtifact(evidence) {
782
1112
  }
783
1113
  function sourceJobIntentFromStored(value) {
784
1114
  const intent = objectRecord(value, 'source job intent');
1115
+ const native = typeof intent['build'] === 'object' && intent['build'] !== null
1116
+ && intent['build']['backend'] === 'macos-seatbelt';
785
1117
  exactKeys(intent, ['authority', 'owner', 'ownerDigest', 'trustDigest', 'repository', 'name', 'gapId', 'gapRevision', 'gapDigest',
786
- 'baseCommit', 'files', 'ttlMs', 'build', 'worktree', 'containerName'], 'source job intent');
1118
+ 'baseCommit', 'files', 'ttlMs', 'build', 'worktree', native ? 'nativeResource' : 'containerName', ...(Object.hasOwn(intent, 'baseline') ? ['baseline'] : []),
1119
+ ...(Object.hasOwn(intent, 'mode') ? ['mode'] : []), ...(Object.hasOwn(intent, 'creation') ? ['creation'] : []), ...(Object.hasOwn(intent, 'revision') ? ['revision'] : [])], 'source job intent');
787
1120
  const authority = objectRecord(intent['authority'], 'source job authority');
788
1121
  exactKeys(authority, ['id', 'digest', 'expiresAt', 'maxSubmissions'], 'source job authority');
789
1122
  const owner = objectRecord(intent['owner'], 'source job owner');
@@ -805,9 +1138,21 @@ function sourceJobIntentFromStored(value) {
805
1138
  || typeof intent['baseCommit'] !== 'string' || !COMMIT.test(intent['baseCommit'])
806
1139
  || !Array.isArray(intent['files']) || intent['files'].length === 0 || intent['files'].length > 64
807
1140
  || !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'])) {
1141
+ || (!native && (typeof intent['containerName'] !== 'string' || !/^dsh-source-job-[a-f0-9]{64}$/u.test(intent['containerName'])))
1142
+ || (native && (intent['mode'] !== 'create' && intent['mode'] !== 'revise-created'))
1143
+ || (intent['mode'] !== undefined && intent['mode'] !== 'create' && intent['mode'] !== 'revise-created')
1144
+ || (intent['mode'] === 'create') !== Object.hasOwn(intent, 'creation')
1145
+ || (intent['mode'] === 'revise-created') !== Object.hasOwn(intent, 'revision')) {
809
1146
  throw new ControlPlaneStoreError('invalid-input', 'source job intent is invalid');
810
1147
  }
1148
+ if (native) {
1149
+ const resource = objectRecord(intent['nativeResource'], 'native source job resource');
1150
+ exactKeys(resource, ['backend', 'key'], 'native source job resource');
1151
+ if (resource['backend'] !== 'macos-seatbelt' || typeof resource['key'] !== 'string'
1152
+ || !/^source-job-[a-f0-9]{64}$/u.test(resource['key'])) {
1153
+ throw new ControlPlaneStoreError('invalid-input', 'native source job resource is invalid');
1154
+ }
1155
+ }
811
1156
  if (Number(owner['principalVersion']) < 1 || Number(owner['bindingVersion']) < 1 || Number(owner['generation']) < 1) {
812
1157
  throw new ControlPlaneStoreError('invalid-input', 'source job owner version is invalid');
813
1158
  }
@@ -826,17 +1171,35 @@ function sourceJobIntentFromStored(value) {
826
1171
  if (new Set(files.map(file => file.path)).size !== files.length)
827
1172
  throw new ControlPlaneStoreError('invalid-input', 'source job files are duplicated');
828
1173
  try {
829
- validateScopedPluginFiles(files);
1174
+ if (intent['mode'] === 'create' || intent['mode'] === 'revise-created')
1175
+ validateSourceCreationFiles(files);
1176
+ else
1177
+ validateScopedPluginFiles(files);
830
1178
  }
831
1179
  catch {
832
1180
  throw new ControlPlaneStoreError('invalid-input', 'source job files are invalid');
833
1181
  }
1182
+ const creation = intent['mode'] === 'create' ? creationBindingFromStored(intent['creation']) : undefined;
1183
+ const revision = intent['mode'] === 'revise-created' ? revisionBindingFromStored(intent['revision']) : undefined;
1184
+ if (revision !== undefined && !intent['name'].startsWith(revision.grant.namePrefix))
1185
+ throw new ControlPlaneStoreError('invalid-input', 'source job plugin name is outside its revision grant');
1186
+ if (creation !== undefined && !intent['name'].startsWith(creation.grant.namePrefix)) {
1187
+ throw new ControlPlaneStoreError('invalid-input', 'source job plugin name is outside its creation grant');
1188
+ }
834
1189
  try {
835
1190
  validateSourceBuildConfig(intent['build']);
836
1191
  }
837
1192
  catch {
838
1193
  throw new ControlPlaneStoreError('invalid-input', 'source job build configuration is invalid');
839
1194
  }
1195
+ if (Object.hasOwn(intent, 'baseline')) {
1196
+ try {
1197
+ validateSourceBaselineConfig(intent['baseline']);
1198
+ }
1199
+ catch {
1200
+ throw new ControlPlaneStoreError('invalid-input', 'source job baseline configuration is invalid');
1201
+ }
1202
+ }
840
1203
  return Object.freeze({ authority: Object.freeze({ id: authority['id'], digest: authority['digest'], expiresAt: Number(authority['expiresAt']), maxSubmissions: Number(authority['maxSubmissions']) }),
841
1204
  owner: Object.freeze({ receiptVersion: 2, authorityId: owner['authorityId'], authorityHash: owner['authorityHash'],
842
1205
  principalId: owner['principalId'], principalRecordId: owner['principalRecordId'], principalVersion: Number(owner['principalVersion']),
@@ -844,7 +1207,13 @@ function sourceJobIntentFromStored(value) {
844
1207
  ownerDigest: intent['ownerDigest'], trustDigest: intent['trustDigest'], repository: intent['repository'],
845
1208
  name: intent['name'], gapId: intent['gapId'], gapRevision: Number(intent['gapRevision']), gapDigest: intent['gapDigest'],
846
1209
  baseCommit: intent['baseCommit'], files: Object.freeze(files), ttlMs: Number(intent['ttlMs']), build: intent['build'],
847
- worktree: intent['worktree'], containerName: intent['containerName'] });
1210
+ worktree: intent['worktree'],
1211
+ ...(native ? { nativeResource: Object.freeze({ backend: 'macos-seatbelt',
1212
+ key: intent['nativeResource'].key }) }
1213
+ : { containerName: intent['containerName'] }),
1214
+ ...(creation === undefined ? {} : { mode: 'create', creation }),
1215
+ ...(revision === undefined ? {} : { mode: 'revise-created', revision }),
1216
+ ...(Object.hasOwn(intent, 'baseline') ? { baseline: Object.freeze(intent['baseline']) } : {}) });
848
1217
  }
849
1218
  function exactInputRecord(value, label) {
850
1219
  if (typeof value !== 'object' || value === null || Array.isArray(value)) {
@@ -923,10 +1292,14 @@ function sourceJobFromRow(row) {
923
1292
  throw new ControlPlaneStoreError('invalid-state', 'stored source job intent is corrupt');
924
1293
  }
925
1294
  if (!DIGEST.test(row.intent_digest) || controlPlaneDigest(intent) !== row.intent_digest || row.authority_id !== intent.authority.id
1295
+ || ('backend' in intent.build ? intent.nativeResource?.key !== row.id : intent.containerName !== `dsh-${row.id}`)
926
1296
  || row.expires_at !== intent.authority.expiresAt || !KEY.test(row.idempotency_key) || !Number.isSafeInteger(row.revision) || row.revision < 1
927
1297
  || !Number.isSafeInteger(row.created_at) || !Number.isSafeInteger(row.updated_at) || row.updated_at < row.created_at
928
1298
  || !['queued', 'running', 'prepared', 'failed', 'unknown'].includes(row.status)
929
1299
  || (row.definition_hash !== null && !DIGEST.test(row.definition_hash)) || (row.occurrence_id !== null && !KEY.test(row.occurrence_id))
1300
+ || (row.dispatch_at !== null && (!Number.isSafeInteger(row.dispatch_at) || row.dispatch_at < row.created_at))
1301
+ || (row.previous_definition_hash !== null && !DIGEST.test(row.previous_definition_hash))
1302
+ || (row.previous_definition_version !== null && (!Number.isSafeInteger(row.previous_definition_version) || row.previous_definition_version < 1))
930
1303
  || (row.plan_id !== null && !/^source-[a-f0-9-]{36}$/u.test(row.plan_id)) || (row.failure_code !== null && !KEY.test(row.failure_code))) {
931
1304
  throw new ControlPlaneStoreError('invalid-state', 'stored source job is corrupt');
932
1305
  }
@@ -936,11 +1309,17 @@ function sourceJobFromRow(row) {
936
1309
  || (row.status === 'prepared' && (row.definition_hash === null || row.occurrence_id === null || row.plan_id === null || row.failure_code !== null))
937
1310
  || (row.status === 'unknown' && (row.definition_hash === null || row.occurrence_id === null || row.plan_id !== null || row.failure_code === null))
938
1311
  || (row.status === 'failed' && (row.plan_id !== null || row.failure_code === null))
939
- || (!active && row.status !== 'prepared' && row.plan_id !== null))
1312
+ || (!active && row.status !== 'prepared' && row.plan_id !== null)
1313
+ || (row.previous_definition_hash === null) !== (row.previous_definition_version === null)
1314
+ || (row.previous_definition_hash !== null && (row.status !== 'queued' || row.definition_hash !== null || row.dispatch_at === null)))
940
1315
  throw new ControlPlaneStoreError('invalid-state', 'stored source job state is corrupt');
941
1316
  return Object.freeze({ id: row.id, automationId: row.automation_id, idempotencyKey: row.idempotency_key, intent, intentDigest: row.intent_digest,
942
1317
  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 }),
1318
+ ...(row.definition_hash === null ? {} : { definitionHash: row.definition_hash }),
1319
+ ...(row.dispatch_at === null ? {} : { dispatchAt: row.dispatch_at }),
1320
+ ...(row.previous_definition_hash === null ? {} : { previousDefinitionHash: row.previous_definition_hash }),
1321
+ ...(row.previous_definition_version === null ? {} : { previousDefinitionVersion: row.previous_definition_version }),
1322
+ ...(row.occurrence_id === null ? {} : { occurrenceId: row.occurrence_id }),
944
1323
  ...(row.plan_id === null ? {} : { planId: row.plan_id }), ...(row.failure_code === null ? {} : { failureCode: row.failure_code }) });
945
1324
  }
946
1325
  function readOwnerTaskFailureReference(database, gapId) {
@@ -1014,7 +1393,7 @@ export function readOwnerSourceAdoptionPlan(database, activationPlanId) {
1014
1393
  const row = database.prepare('SELECT * FROM activation_plans WHERE id = ?').get(activationPlanId);
1015
1394
  if (!link || !row)
1016
1395
  throw new ControlPlaneStoreError('not-found', 'owner source adoption binding absent');
1017
- const plan = activationFromRow(row), { plan: sourcePlan, source } = readOwnerPreparedSourcePlan(database, link.source_plan_id);
1396
+ const plan = effectiveActivationFromRow(database, row), { plan: sourcePlan, source } = readOwnerPreparedSourcePlan(database, link.source_plan_id);
1018
1397
  if (sourcePlan.status !== 'release-complete' || !sourcePlan.release || plan.gapId !== sourcePlan.gapId)
1019
1398
  throw new ControlPlaneStoreError('invalid-state', 'source adoption requires a completed owner release');
1020
1399
  const expected = adoptionBinding(sourcePlan, source, plan), released = readReleaseCandidate(database, sourcePlan);
@@ -1023,11 +1402,340 @@ export function readOwnerSourceAdoptionPlan(database, activationPlanId) {
1023
1402
  throw new ControlPlaneStoreError('invalid-state', 'source adoption binding changed');
1024
1403
  return { plan, sourcePlan, source, released };
1025
1404
  }
1405
+ function runtimeEpochFromRow(row) {
1406
+ const request = parseRuntimeEpochRequest(JSON.parse(row.request_json));
1407
+ if (request.operationId !== row.operation_id || request.plan.id !== row.plan_id || request.sequence !== row.sequence
1408
+ || request.runtimeIdentityDigest !== row.runtime_digest || controlPlaneDigest(request) !== row.request_digest
1409
+ || request.requestedAt !== row.created_at || (row.status === 'applied') !== (row.receipt_json !== null)) {
1410
+ throw new ControlPlaneStoreError('invalid-state', 'runtime epoch journal binding changed');
1411
+ }
1412
+ return { request, status: row.status, ...(row.receipt_json === null ? {} : { receipt: parseRuntimeEpochReceipt(JSON.parse(row.receipt_json)) }) };
1413
+ }
1414
+ /** Historical adoption remains immutable; only a currently watched deployment can acquire another runtime proof. */
1415
+ export function readCurrentRuntimeEpochDeployment(database, profilePath) {
1416
+ const unsettled = database.prepare(`SELECT 1 FROM activation_plans WHERE target_path=? AND status IN (
1417
+ 'staging','awaiting-reload','awaiting-readiness','awaiting-live-tasks','awaiting-effect-blocked-replay','awaiting-shadow',
1418
+ 'awaiting-canary','awaiting-soak','awaiting-health','commit-pending','rollback-pending') LIMIT 1`).get(profilePath);
1419
+ const latest = database.prepare(`SELECT checkpoint.plan_id,checkpoint.exposure_order,checkpoint.successful_order
1420
+ FROM activation_deployment_checkpoints checkpoint JOIN activation_plans plan ON plan.id=checkpoint.plan_id
1421
+ WHERE plan.target_path=? ORDER BY checkpoint.exposure_order DESC LIMIT 1`).get(profilePath);
1422
+ if (unsettled || !latest || latest.exposure_order !== latest.successful_order) {
1423
+ throw new ControlPlaneStoreError('invalid-state', 'runtime epoch requires the current successful deployment');
1424
+ }
1425
+ const adopted = readOwnerSourceAdoptionPlan(database, latest.plan_id), { plan } = adopted;
1426
+ const watch = database.prepare('SELECT state,activation_id,fence,last_host_generation FROM activation_watch WHERE plan_id=?').get(plan.id);
1427
+ if (plan.status !== 'activated' || !plan.activation || watch?.state !== 'watching'
1428
+ || watch.activation_id !== plan.activation.id || watch.fence !== plan.activation.fence) {
1429
+ throw new ControlPlaneStoreError('invalid-state', 'runtime epoch deployment is no longer watched');
1430
+ }
1431
+ const terms = plan.dossier.handoff;
1432
+ const handoffRow = database.prepare('SELECT * FROM adoption_handoffs WHERE plan_id=?').get(plan.id);
1433
+ if (!terms || !handoffRow || handoffRow.revoked_at !== null || handoffRow.plan_digest !== plan.digest
1434
+ || handoffRow.coordinator_id !== terms.coordinatorId)
1435
+ throw new ControlPlaneStoreError('conflict', 'runtime epoch handoff was revoked or changed');
1436
+ const handoff = { planId: plan.id, planDigest: plan.digest, coordinatorId: handoffRow.coordinator_id,
1437
+ createdAt: handoffRow.created_at, expiresAt: handoffRow.expires_at };
1438
+ const approvalRow = database.prepare('SELECT approval_receipt_json FROM activation_plans WHERE id=?').get(plan.id);
1439
+ const approvalReceipt = parseApprovalReceipt(JSON.parse(approvalRow.approval_receipt_json));
1440
+ if (!plan.approval || controlPlaneDigest(projectedApproval(approvalReceipt)) !== controlPlaneDigest(plan.approval)) {
1441
+ throw new ControlPlaneStoreError('invalid-state', 'runtime epoch historical approval changed');
1442
+ }
1443
+ const witness = hostInputWitnessFromRow(database, plan);
1444
+ if (!witness || witness.activationId !== plan.activation.id || witness.fence !== plan.activation.fence) {
1445
+ throw new ControlPlaneStoreError('invalid-state', 'runtime epoch deployment witness changed');
1446
+ }
1447
+ const readinessRow = database.prepare(`SELECT operation.* FROM host_attestation_operations operation
1448
+ JOIN host_attestations attestation ON attestation.plan_id=operation.plan_id AND attestation.phase=operation.phase
1449
+ AND attestation.receipt_digest=operation.receipt_digest
1450
+ WHERE operation.plan_id=? AND operation.phase='readiness' AND operation.status='applied'`).get(plan.id);
1451
+ if (!readinessRow)
1452
+ throw new ControlPlaneStoreError('invalid-state', 'runtime epoch lacks original applied readiness');
1453
+ const readiness = hostOperationFromRow(readinessRow);
1454
+ if (!readiness.receipt || readiness.receipt.outcome !== 'passed' || readiness.receipt.activationId !== plan.activation.id
1455
+ || readiness.receipt.fence !== plan.activation.fence || readiness.receipt.hostGeneration !== watch.last_host_generation) {
1456
+ throw new ControlPlaneStoreError('conflict', 'runtime epoch original readiness changed');
1457
+ }
1458
+ return { ...adopted, handoff, approvalReceipt, witness, readiness,
1459
+ maintenance: readHostMaintenanceContext(database, plan.id).records };
1460
+ }
1461
+ /** Read-only authority seam; current request admission never extends the historical activation authorization. */
1462
+ export function readOwnerRuntimeEpochContext(database, operationId) {
1463
+ const row = database.prepare('SELECT * FROM deployment_runtime_epochs WHERE operation_id=?').get(operationId);
1464
+ if (!row)
1465
+ throw new ControlPlaneStoreError('not-found', 'runtime epoch operation absent');
1466
+ const record = runtimeEpochFromRow(row), request = record.request;
1467
+ if (record.status !== 'claimed')
1468
+ throw new ControlPlaneStoreError('conflict', 'runtime epoch operation is not claimed');
1469
+ const context = readCurrentRuntimeEpochDeployment(database, request.profile.path), { plan, readiness } = context;
1470
+ const latest = database.prepare('SELECT max(sequence) AS sequence FROM deployment_runtime_epochs WHERE plan_id=?').get(plan.id);
1471
+ if (latest?.sequence !== request.sequence || request.plan.id !== plan.id || request.plan.digest !== plan.digest
1472
+ || request.installationId !== plan.installationId || controlPlaneDigest(request.ledger) !== controlPlaneDigest(plan.ledger)
1473
+ || request.profile.name !== plan.profile || request.activation.id !== plan.activation.id || request.activation.fence !== plan.activation.fence
1474
+ || controlPlaneDigest(request.predecessor) !== controlPlaneDigest({ operationId: readiness.operationId,
1475
+ receiptDigest: controlPlaneDigest(readiness.receipt), hostGeneration: readiness.receipt.hostGeneration })) {
1476
+ throw new ControlPlaneStoreError('conflict', 'runtime epoch request lost current deployment admission');
1477
+ }
1478
+ return { ...context, request, status: 'claimed', createdAt: row.created_at };
1479
+ }
1480
+ export function readOwnerHostAttestationContext(database, operationId) {
1481
+ if (database.prepare('PRAGMA user_version').get()?.user_version !== controlPlaneSchemaVersion
1482
+ || typeof operationId !== 'string' || !KEY.test(operationId)) {
1483
+ throw new ControlPlaneStoreError('invalid-state', 'Host authority database or operation identity is invalid');
1484
+ }
1485
+ const operationRow = database.prepare('SELECT * FROM host_attestation_operations WHERE operation_id=?')
1486
+ .get(operationId);
1487
+ if (!operationRow)
1488
+ throw new ControlPlaneStoreError('not-found', 'Host authority operation is absent');
1489
+ const parsed = hostOperationFromRow(operationRow);
1490
+ if (!isBoundHostRequest(parsed.request) || parsed.status !== 'pending' || parsed.receipt !== undefined
1491
+ || operationRow.completed_at !== null || operationRow.applied_at !== null) {
1492
+ throw new ControlPlaneStoreError('invalid-state', 'Host authority requires an uncompleted schema-2 operation');
1493
+ }
1494
+ const operation = parsed;
1495
+ const { plan, sourcePlan, source, released } = readOwnerSourceAdoptionPlan(database, operation.planId);
1496
+ const request = operation.request, activation = plan.activation, expected = expectedAttestation[plan.status];
1497
+ if (!activation || expected?.phase !== operation.phase || request.phase !== operation.phase
1498
+ || request.operationId !== operation.operationId || request.plan.id !== plan.id || request.plan.digest !== plan.digest
1499
+ || request.installationId !== plan.installationId || controlPlaneDigest(request.ledger) !== controlPlaneDigest(plan.ledger)
1500
+ || request.profile.name !== plan.profile || request.profile.path !== plan.target.profilePath
1501
+ || request.activation.id !== activation.id || request.activation.fence !== activation.fence
1502
+ || request.requestedAt !== operation.createdAt || request.receiptTtlMs < 1_000 || request.receiptTtlMs > 300_000) {
1503
+ throw new ControlPlaneStoreError('conflict', 'Host authority operation lost its current plan binding');
1504
+ }
1505
+ const dispatchRow = database.prepare('SELECT * FROM host_attestation_dispatches WHERE operation_id=?')
1506
+ .get(operationId);
1507
+ if (!dispatchRow || dispatchRow.status !== 'claimed' || dispatchRow.completed_at !== null
1508
+ || !Number.isSafeInteger(dispatchRow.claimed_at) || dispatchRow.claimed_at < operation.createdAt) {
1509
+ throw new ControlPlaneStoreError('conflict', 'Host authority operation was not durably claimed');
1510
+ }
1511
+ const handoffRow = database.prepare('SELECT * FROM adoption_handoffs WHERE plan_id=?').get(plan.id);
1512
+ const terms = plan.dossier.handoff;
1513
+ if (!handoffRow || !terms || handoffRow.plan_digest !== plan.digest || handoffRow.coordinator_id !== terms.coordinatorId
1514
+ || !Number.isSafeInteger(handoffRow.created_at) || handoffRow.created_at < plan.createdAt
1515
+ || handoffRow.expires_at !== Math.min(handoffRow.created_at + terms.maximumWindowMs, plan.expiresAt, plan.approval?.expiresAt ?? 0)) {
1516
+ throw new ControlPlaneStoreError('invalid-state', 'Host authority handoff is not bound to the owner plan');
1517
+ }
1518
+ validateAdoptionHandoffTerms(terms);
1519
+ const handoff = { planId: plan.id, planDigest: plan.digest, coordinatorId: terms.coordinatorId,
1520
+ createdAt: handoffRow.created_at, expiresAt: handoffRow.expires_at,
1521
+ ...(handoffRow.revoked_at === null ? {} : { revokedAt: handoffRow.revoked_at }) };
1522
+ const planRow = database.prepare('SELECT approval_receipt_json FROM activation_plans WHERE id=?').get(plan.id);
1523
+ let approvalReceipt;
1524
+ try {
1525
+ approvalReceipt = parseApprovalReceipt(JSON.parse(planRow.approval_receipt_json ?? 'null'));
1526
+ }
1527
+ catch {
1528
+ throw new ControlPlaneStoreError('invalid-state', 'Host authority approval receipt is corrupt');
1529
+ }
1530
+ if (!plan.approval || controlPlaneDigest(projectedApproval(approvalReceipt)) !== controlPlaneDigest(plan.approval)) {
1531
+ throw new ControlPlaneStoreError('invalid-state', 'Host authority approval projection changed');
1532
+ }
1533
+ const rollback = operation.phase === 'rollback';
1534
+ const now = Date.now();
1535
+ if (!rollback && (now >= plan.expiresAt || now >= plan.approval.expiresAt || now >= handoff.expiresAt
1536
+ || handoff.revokedAt !== undefined))
1537
+ throw new ControlPlaneStoreError('expired', 'Host authority forward authorization expired');
1538
+ const witness = hostInputWitnessFromRow(database, plan);
1539
+ if (!witness || witness.activationId !== activation.id || (!rollback && witness.fence !== activation.fence)) {
1540
+ throw new ControlPlaneStoreError('conflict', 'Host authority lacks exact deployment witness');
1541
+ }
1542
+ if (rollback) {
1543
+ if (!activation.hostRecoveryRequired || !activation.rollbackProfileRestored || activation.targetBaselineFiles === undefined
1544
+ || request.requirements.kind !== 'rollback'
1545
+ || controlPlaneDigest(request.requirements.baselineFiles) !== controlPlaneDigest(activation.targetBaselineFiles)
1546
+ || request.requirements.action !== (activation.targetOriginallyExisted ? 'restore' : 'stop')) {
1547
+ throw new ControlPlaneStoreError('conflict', 'Host rollback lacks a durable physical recovery obligation');
1548
+ }
1549
+ const checkpoint = database.prepare('SELECT exposure_order,successful_order FROM activation_deployment_checkpoints WHERE plan_id=?')
1550
+ .get(plan.id);
1551
+ const postActivated = ['post-activation-regressed', 'post-activation-retracted', 'live-qualification-invalidated']
1552
+ .includes(activation.failureCode ?? '');
1553
+ if (postActivated && (!checkpoint || checkpoint.exposure_order !== checkpoint.successful_order)) {
1554
+ throw new ControlPlaneStoreError('conflict', 'post-activation rollback lacks a successful deployment checkpoint');
1555
+ }
1556
+ if (postActivated) {
1557
+ const readinessRow = database.prepare(`SELECT o.* FROM host_attestation_operations o
1558
+ JOIN host_attestations a ON a.plan_id=o.plan_id AND a.phase=o.phase AND a.receipt_digest=o.receipt_digest
1559
+ WHERE o.plan_id=? AND o.phase='readiness' AND o.status='applied'`).get(plan.id);
1560
+ const readiness = readinessRow && hostOperationFromRow(readinessRow);
1561
+ if (!readiness?.receipt || readiness.receipt.outcome !== 'passed'
1562
+ || readiness.request.activation.id !== activation.id || readiness.request.activation.fence !== witness.fence
1563
+ || readiness.receipt.activationId !== activation.id || readiness.receipt.fence !== witness.fence) {
1564
+ throw new ControlPlaneStoreError('conflict', 'post-activation rollback lacks original applied readiness');
1565
+ }
1566
+ }
1567
+ const active = database.prepare(`SELECT 1 FROM activation_plans WHERE target_path=? AND id<>? AND status IN
1568
+ ('staging','awaiting-reload','awaiting-readiness','awaiting-live-tasks','awaiting-effect-blocked-replay','awaiting-shadow',
1569
+ 'awaiting-canary','awaiting-soak','awaiting-health','commit-pending','rollback-pending') LIMIT 1`)
1570
+ .get(plan.target.profilePath, plan.id);
1571
+ const superseded = checkpoint && database.prepare(`SELECT 1 FROM activation_deployment_checkpoints AS c
1572
+ JOIN activation_plans AS p ON p.id=c.plan_id WHERE p.target_path=? AND c.successful_order IS NOT NULL
1573
+ AND c.exposure_order>? LIMIT 1`).get(plan.target.profilePath, checkpoint.exposure_order);
1574
+ if (active || superseded)
1575
+ throw new ControlPlaneStoreError('conflict', 'Host rollback target has a newer owner');
1576
+ }
1577
+ const latestRow = database.prepare(`SELECT max(generation) AS generation FROM (
1578
+ 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=?
1579
+ 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=?)`)
1580
+ .get(plan.installationId, plan.installationId);
1581
+ const latest = latestRow.generation ?? 0;
1582
+ if ((operation.phase === 'reload' && (request.predecessor !== null || request.requirements.kind !== 'reload'
1583
+ || request.requirements.previousHostGeneration !== latest))
1584
+ || (rollback && (request.requirements.kind !== 'rollback' || request.requirements.previousHostGeneration !== latest))) {
1585
+ throw new ControlPlaneStoreError('conflict', 'Host authority generation changed after operation reservation');
1586
+ }
1587
+ if (operation.phase !== 'reload') {
1588
+ const predecessor = request.predecessor;
1589
+ const priorPhase = rollback ? undefined : predecessorPhase[operation.phase];
1590
+ const priorRows = database.prepare(`SELECT o.* FROM host_attestation_operations AS o
1591
+ JOIN host_attestations AS a ON a.plan_id=o.plan_id AND a.phase=o.phase AND a.receipt_digest=o.receipt_digest
1592
+ WHERE o.plan_id=? AND o.status='applied' ${priorPhase === undefined ? '' : 'AND o.phase=?'}
1593
+ ORDER BY o.applied_at DESC,
1594
+ CASE o.phase WHEN 'reload' THEN 1 WHEN 'readiness' THEN 2 WHEN 'effect-blocked-replay' THEN 3
1595
+ WHEN 'shadow' THEN 4 WHEN 'canary' THEN 5 WHEN 'soak' THEN 6 WHEN 'health' THEN 7 WHEN 'rollback' THEN 8 END DESC`)
1596
+ .all(...(priorPhase === undefined ? [plan.id] : [plan.id, priorPhase]));
1597
+ let actual = null;
1598
+ for (const priorRow of priorRows) {
1599
+ const prior = hostOperationFromRow(priorRow), receipt = prior.receipt;
1600
+ if (!receipt || priorRow.receipt_digest === null || prior.request.activation.id !== activation.id
1601
+ || receipt.activationId !== activation.id
1602
+ || (!rollback && (prior.request.activation.fence !== activation.fence || receipt.fence !== activation.fence))
1603
+ || (rollback && (prior.request.activation.fence >= activation.fence || receipt.fence >= activation.fence)))
1604
+ continue;
1605
+ actual = { operationId: prior.operationId, receiptId: receipt.receiptId, phase: prior.phase,
1606
+ receiptDigest: priorRow.receipt_digest, hostGeneration: receipt.hostGeneration };
1607
+ break;
1608
+ }
1609
+ if (controlPlaneDigest(actual) !== controlPlaneDigest(predecessor)) {
1610
+ throw new ControlPlaneStoreError('conflict', 'Host authority predecessor is no longer current');
1611
+ }
1612
+ if (predecessor !== null) {
1613
+ const prior = database.prepare(`SELECT o.*, a.host_generation AS applied_generation FROM host_attestation_operations o
1614
+ JOIN host_attestations a ON a.plan_id=o.plan_id AND a.phase=o.phase AND a.receipt_digest=o.receipt_digest
1615
+ WHERE o.operation_id=? AND o.plan_id=? AND o.status='applied'`).get(predecessor.operationId, plan.id);
1616
+ if (!prior)
1617
+ throw new ControlPlaneStoreError('conflict', 'Host authority predecessor is not applied');
1618
+ const predecessorOperation = hostOperationFromRow(prior);
1619
+ if (!predecessorOperation.receipt || predecessorOperation.receipt.receiptId !== predecessor.receiptId
1620
+ || predecessorOperation.phase !== predecessor.phase || prior.receipt_digest !== predecessor.receiptDigest
1621
+ || prior.applied_generation !== predecessor.hostGeneration
1622
+ || predecessorOperation.request.activation.id !== activation.id
1623
+ || (!rollback && (predecessor.phase !== predecessorPhase[operation.phase] || predecessor.hostGeneration !== latest
1624
+ || predecessorOperation.receipt.outcome !== 'passed'))
1625
+ || (rollback && predecessorOperation.request.activation.fence >= activation.fence && witness.fence < activation.fence)) {
1626
+ throw new ControlPlaneStoreError('conflict', 'Host authority predecessor binding changed');
1627
+ }
1628
+ }
1629
+ else if (!rollback)
1630
+ throw new ControlPlaneStoreError('conflict', 'Host authority normal phase lacks predecessor');
1631
+ }
1632
+ return { plan, sourcePlan, source, released, handoff, approvalReceipt, operation,
1633
+ dispatch: { status: 'claimed', claimedAt: dispatchRow.claimed_at }, witness };
1634
+ }
1635
+ function liveQualificationRecord(db, id) {
1636
+ const row = db.prepare('SELECT * FROM live_qualification_batches WHERE id=?').get(id);
1637
+ if (!row)
1638
+ return undefined;
1639
+ if (Buffer.byteLength(row.batch_json) > 262_144)
1640
+ throw new ControlPlaneStoreError('invalid-state', 'live qualification batch too large');
1641
+ const batch = JSON.parse(row.batch_json);
1642
+ assertLiveQualificationBatch(batch);
1643
+ const receipt = row.receipt_json === null ? undefined : parseLiveQualificationReceipt(JSON.parse(row.receipt_json));
1644
+ if (row.id !== batch.id || row.plan_id !== batch.planId || row.batch_digest !== controlPlaneDigest(batch)
1645
+ || (receipt && (row.receipt_digest !== controlPlaneDigest(receipt) || receipt.batchId !== batch.id || receipt.batchDigest !== batch.digest))
1646
+ || (row.state === 'applied' && !receipt))
1647
+ throw new ControlPlaneStoreError('invalid-state', 'live qualification journal changed');
1648
+ return { batch, state: row.state, ...(receipt ? { receipt } : {}) };
1649
+ }
1650
+ function liveQualificationReadiness(db, plan) {
1651
+ const row = db.prepare(`SELECT operation.* FROM host_attestation_operations operation
1652
+ JOIN host_attestations attestation ON attestation.plan_id=operation.plan_id AND attestation.phase=operation.phase
1653
+ AND attestation.receipt_digest=operation.receipt_digest
1654
+ WHERE operation.plan_id=? AND operation.phase='readiness' AND operation.status='applied'`).get(plan.id);
1655
+ if (!row)
1656
+ throw new ControlPlaneStoreError('invalid-state', 'live qualification lacks applied readiness');
1657
+ const operation = hostOperationFromRow(row), receipt = operation.receipt;
1658
+ 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);
1659
+ if (!receipt || receipt.outcome !== 'passed' || operation.request.schemaVersion !== 2
1660
+ || receipt.planDigest !== plan.digest || receipt.activationId !== plan.activation?.id || receipt.fence !== plan.activation?.fence
1661
+ || receipt.hostGeneration !== generation.generation || operation.request.activation.fence !== receipt.fence) {
1662
+ throw new ControlPlaneStoreError('conflict', 'live qualification readiness generation changed');
1663
+ }
1664
+ return operation;
1665
+ }
1666
+ function liveQualificationWindow(db, plan) {
1667
+ const row = db.prepare('SELECT * FROM live_qualification_windows WHERE plan_id=?').get(plan.id);
1668
+ const terms = plan.dossier.liveQualification;
1669
+ if (!terms || !row || row.activation_id !== plan.activation?.id || row.fence !== plan.activation?.fence
1670
+ || row.deadline_at <= row.started_at || row.deadline_at > row.started_at + terms.maximumWindowMs
1671
+ || row.deadline_at > plan.expiresAt || row.deadline_at > (plan.approval?.expiresAt ?? 0)) {
1672
+ throw new ControlPlaneStoreError('invalid-state', 'live qualification window changed');
1673
+ }
1674
+ const readiness = liveQualificationReadiness(db, plan);
1675
+ return { startedAt: row.started_at, deadlineAt: row.deadline_at,
1676
+ readinessDigest: controlPlaneDigest(readiness.receipt), hostGeneration: readiness.receipt.hostGeneration };
1677
+ }
1678
+ /** Independent signers use this read-only seam, never a migrating Store. */
1679
+ export function readLiveQualificationContext(db, id) {
1680
+ if (Number(db.prepare('PRAGMA user_version').get()?.user_version) !== controlPlaneSchemaVersion)
1681
+ throw new ControlPlaneStoreError('invalid-state', 'live qualification schema differs');
1682
+ const record = liveQualificationRecord(db, id);
1683
+ if (!record)
1684
+ throw new ControlPlaneStoreError('not-found', 'live qualification batch absent');
1685
+ const { batch } = record;
1686
+ const { plan, source } = readOwnerSourceAdoptionPlan(db, batch.planId);
1687
+ const window = liveQualificationWindow(db, plan);
1688
+ if (!['awaiting-live-tasks', 'commit-pending', 'activated'].includes(plan.status)
1689
+ || !plan.dossier.liveQualification || batch.planDigest !== plan.digest || batch.installationId !== plan.installationId
1690
+ || batch.profilePath !== plan.target.profilePath || batch.activationId !== plan.activation?.id || batch.fence !== plan.activation?.fence
1691
+ || controlPlaneDigest(batch.terms) !== controlPlaneDigest(plan.dossier.liveQualification)
1692
+ || ['authorityId', 'authorityHash', 'principalId', 'principalRecordId', 'principalVersion', 'workspace', 'agentPreset'].some(key => batch.owner[key] !== source.owner[key])
1693
+ || batch.startedAt !== window.startedAt || batch.deadlineAt !== window.deadlineAt
1694
+ || batch.readinessDigest !== window.readinessDigest || batch.hostGeneration !== window.hostGeneration
1695
+ || db.prepare('SELECT 1 FROM live_qualification_invalidations WHERE plan_id=?').get(plan.id)) {
1696
+ throw new ControlPlaneStoreError('conflict', 'live qualification deployment or owner changed');
1697
+ }
1698
+ const competing = db.prepare(`SELECT 1 FROM activation_plans WHERE target_path=? AND id<>? AND status IN
1699
+ ('staging','awaiting-reload','awaiting-readiness','awaiting-live-tasks','awaiting-effect-blocked-replay','awaiting-shadow',
1700
+ 'awaiting-canary','awaiting-soak','awaiting-health','commit-pending','rollback-pending')`).get(plan.target.profilePath, plan.id);
1701
+ if (competing)
1702
+ throw new ControlPlaneStoreError('conflict', 'live qualification target superseded');
1703
+ if (plan.status === 'activated') {
1704
+ const latest = db.prepare(`SELECT checkpoint.plan_id FROM activation_deployment_checkpoints checkpoint
1705
+ 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);
1706
+ if (latest?.plan_id !== plan.id)
1707
+ throw new ControlPlaneStoreError('conflict', 'live qualification deployment superseded');
1708
+ }
1709
+ const deployments = batch.votes.map(vote => {
1710
+ const row = db.prepare('SELECT record_json,record_digest,plan_id FROM foreground_deployments WHERE inbox_id=?').get(vote.inboxId);
1711
+ if (!row || row.plan_id !== plan.id || Buffer.byteLength(row.record_json) > 262_144)
1712
+ throw new ControlPlaneStoreError('invalid-state', 'live task witness absent');
1713
+ const deployment = JSON.parse(row.record_json);
1714
+ assertForegroundDeployment(deployment);
1715
+ if (controlPlaneDigest(deployment) !== row.record_digest || row.record_digest !== vote.deploymentDigest
1716
+ || deployment.state !== 'observed' || !deployment.execution || !deployment.execution.quiescent
1717
+ || deployment.execution.status !== 'succeeded' || deployment.execution.modelSelectionState !== 'frozen' || !deployment.execution.modelSelection
1718
+ || deployment.task.inboxId !== vote.inboxId || deployment.execution.completedAt !== vote.completedAt
1719
+ || deployment.task.dispatchedAt < window.startedAt || deployment.task.dispatchedAt >= window.deadlineAt
1720
+ || vote.completedAt >= window.deadlineAt || deployment.task.owner.principalRecordId !== batch.owner.principalRecordId
1721
+ || deployment.task.owner.principalVersion !== batch.owner.principalVersion || deployment.task.scope.workspace !== batch.owner.workspace
1722
+ || deployment.task.scope.preset !== batch.owner.agentPreset || deployment.readiness.planDigest !== plan.digest
1723
+ || deployment.readiness.planId !== plan.id || deployment.readiness.fence !== batch.fence
1724
+ || deployment.readiness.hostGeneration !== batch.hostGeneration || deployment.readiness.receiptDigest !== batch.readinessDigest
1725
+ || controlPlaneDigest(deployment.readiness.exact) !== controlPlaneDigest({ package: plan.candidate.package, version: plan.candidate.version, integrity: plan.candidate.integrity })) {
1726
+ throw new ControlPlaneStoreError('conflict', 'live task witness differs');
1727
+ }
1728
+ return deployment;
1729
+ });
1730
+ return { record, plan, source, deployments };
1731
+ }
1026
1732
  export class ControlPlaneStore {
1027
1733
  #database;
1028
1734
  #now;
1029
1735
  #ownerTaskFailureGapAdmission;
1030
1736
  #foregroundDeploymentAdmission;
1737
+ #liveQualificationAdmission;
1738
+ #withLiveQualificationFence;
1031
1739
  #withOwnerActivationFence;
1032
1740
  #adoptionCoordinatorId;
1033
1741
  constructor(options) {
@@ -1036,8 +1744,9 @@ export class ControlPlaneStore {
1036
1744
  }
1037
1745
  if (options.adoptionCoordinatorId && options.withOwnerActivationFence)
1038
1746
  throw new ControlPlaneStoreError('invalid-input', 'adoption coordinator cannot hold owner fence');
1039
- this.#database = openControlPlaneDatabase(options.path);
1747
+ this.#database = options.readOnly === true ? openControlPlaneDatabaseReadOnly(options.path) : openControlPlaneDatabase(options.path);
1040
1748
  this.#now = options.now ?? Date.now;
1749
+ this.#withLiveQualificationFence = options.withLiveQualificationFence;
1041
1750
  this.#withOwnerActivationFence = options.withOwnerActivationFence;
1042
1751
  this.#adoptionCoordinatorId = options.adoptionCoordinatorId;
1043
1752
  }
@@ -1177,7 +1886,7 @@ export class ControlPlaneStore {
1177
1886
  this.#assertAdoptionHandoffBinding(planId, this.#adoptionCoordinatorId);
1178
1887
  return callback();
1179
1888
  }
1180
- if (!['approved', 'staging', 'awaiting-reload', 'awaiting-readiness', 'awaiting-effect-blocked-replay',
1889
+ if (!['approved', 'staging', 'awaiting-reload', 'awaiting-readiness', 'awaiting-live-tasks', 'awaiting-effect-blocked-replay',
1181
1890
  'awaiting-shadow', 'awaiting-canary', 'awaiting-soak', 'awaiting-health'].includes(plan.status)) {
1182
1891
  throw new ControlPlaneStoreError('invalid-state', 'adoption coordinator cannot advance this activation state');
1183
1892
  }
@@ -1190,6 +1899,10 @@ export class ControlPlaneStore {
1190
1899
  const current = this.getPlan(planId);
1191
1900
  if (current.status === 'commit-pending' && current.dossier.handoff !== undefined)
1192
1901
  this.#assertAdoptionHandoff(planId);
1902
+ if (current.status === 'commit-pending' && current.dossier.liveQualification) {
1903
+ this.#assertLiveQualificationCommit(planId);
1904
+ return callback();
1905
+ }
1193
1906
  return callback();
1194
1907
  };
1195
1908
  if (this.#ownerTaskFailureGapAdmission === plan.gapId)
@@ -1240,9 +1953,17 @@ export class ControlPlaneStore {
1240
1953
  const requestBinding = { operation: 'create-activation-plan', gapId: input.gapId, candidate, catalog: input.catalog,
1241
1954
  matchedCapabilities, profile, target: input.target, installationId: input.installationId,
1242
1955
  ledger: input.ledger, executor: input.executor, ttlMs: input.ttlMs,
1243
- ...(input.sourcePlanId === undefined ? {} : { sourcePlanId: input.sourcePlanId }), ...(input.handoff === undefined ? {} : { handoff: input.handoff }) };
1956
+ ...(input.sourcePlanId === undefined ? {} : { sourcePlanId: input.sourcePlanId }), ...(input.handoff === undefined ? {} : { handoff: input.handoff }), ...(input.liveQualification === undefined ? {} : { liveQualification: input.liveQualification }),
1957
+ ...(input.hostDeploymentInputs === undefined ? {} : { hostDeploymentInputs: input.hostDeploymentInputs }) };
1244
1958
  this.#assertOwnerTaskFailureGapAdmission(input.gapId);
1245
1959
  const source = input.sourcePlanId === undefined ? undefined : readOwnerPreparedSourcePlan(this.#database, input.sourcePlanId);
1960
+ if (input.liveQualification !== undefined) {
1961
+ validateLiveQualificationTerms(input.liveQualification);
1962
+ if (!source || !input.handoff)
1963
+ throw new ControlPlaneStoreError('invalid-input', 'bounded-live adoption requires an exact owner release and external coordinator');
1964
+ }
1965
+ if (input.hostDeploymentInputs !== undefined)
1966
+ validateHostDeploymentInputs(input.hostDeploymentInputs);
1246
1967
  if (input.handoff !== undefined) {
1247
1968
  validateAdoptionHandoffTerms(input.handoff);
1248
1969
  if (!source)
@@ -1272,7 +1993,9 @@ export class ControlPlaneStore {
1272
1993
  const gapSnapshot = Object.freeze({ revision: gap.revision, inputDigest: gap.inputDigest, roi: gap.roi, capability: gap.capability });
1273
1994
  const dossier = Object.freeze({ catalogDigest: input.catalog.digest, catalogProvenance: input.catalog.provenance,
1274
1995
  matchedCapabilities: Object.freeze(matchedCapabilities), authorities: Object.freeze([...candidate.authorities]), packages,
1275
- ...(input.handoff === undefined ? {} : { handoff: Object.freeze({ ...input.handoff }) }) });
1996
+ ...(input.handoff === undefined ? {} : { handoff: Object.freeze({ ...input.handoff }) }),
1997
+ ...(input.liveQualification === undefined ? {} : { liveQualification: Object.freeze({ ...input.liveQualification }) }),
1998
+ ...(input.hostDeploymentInputs === undefined ? {} : { hostDeploymentInputs: Object.freeze([...input.hostDeploymentInputs]) }) });
1276
1999
  const now = this.#now();
1277
2000
  const id = `plugin-${randomUUID()}`;
1278
2001
  const expiresAt = now + input.ttlMs;
@@ -1320,7 +2043,11 @@ export class ControlPlaneStore {
1320
2043
  const row = this.#database.prepare('SELECT * FROM activation_plans WHERE id = ?').get(id);
1321
2044
  if (row === undefined)
1322
2045
  throw new ControlPlaneStoreError('not-found', 'activation plan not found');
1323
- return activationFromRow(row);
2046
+ return effectiveActivationFromRow(this.#database, row);
2047
+ }
2048
+ currentPlanExecutor(planId) {
2049
+ const context = readHostMaintenanceContext(this.#database, planId);
2050
+ return context.records.at(-1)?.after.executor ?? context.plan.executor;
1324
2051
  }
1325
2052
  findSourceAdoption(sourcePlanId) {
1326
2053
  const row = this.#database.prepare('SELECT activation_plan_id FROM source_adoptions WHERE source_plan_id = ?').get(sourcePlanId);
@@ -1431,11 +2158,23 @@ export class ControlPlaneStore {
1431
2158
  const intentDigest = controlPlaneDigest(intent);
1432
2159
  const now = this.#now();
1433
2160
  this.#assertOwnerTaskFailureGapAdmission(intent.gapId);
1434
- if (intent.containerName !== `dsh-${input.id}` || basename(intent.worktree) !== `worktree-job-${input.id.slice('source-job-'.length)}`) {
2161
+ if (intent.mode === 'create' && this.getOwnerTaskFailureReference(intent.gapId) !== undefined
2162
+ && intent.creation?.growthRun === undefined) {
2163
+ throw new ControlPlaneStoreError('invalid-input', 'task-backed creation requires a frozen source growth run');
2164
+ }
2165
+ if (('backend' in intent.build ? intent.nativeResource?.key !== input.id : intent.containerName !== `dsh-${input.id}`)
2166
+ || basename(intent.worktree) !== `worktree-job-${input.id.slice('source-job-'.length)}`) {
1435
2167
  throw new ControlPlaneStoreError('invalid-input', 'source job resource identity is invalid');
1436
2168
  }
1437
2169
  if (now >= intent.authority.expiresAt)
1438
2170
  throw new ControlPlaneStoreError('expired', 'source job authority is expired');
2171
+ if (intent.creation !== undefined && now >= intent.creation.grant.expiresAt)
2172
+ throw new ControlPlaneStoreError('expired', 'source creation grant is expired');
2173
+ if (intent.revision !== undefined && (this.getOwnerTaskFailureReference(intent.gapId) === undefined || now >= intent.revision.grant.expiresAt))
2174
+ throw new ControlPlaneStoreError('expired', 'revision grant or owner task is unavailable');
2175
+ const growthRun = intent.creation?.growthRun ?? intent.revision?.growthRun;
2176
+ if (growthRun !== undefined && (now >= growthRun.generationDeadlineAt || now >= growthRun.expiresAt))
2177
+ throw new ControlPlaneStoreError('expired', 'source growth generation window is expired');
1439
2178
  this.#database.exec('BEGIN IMMEDIATE');
1440
2179
  try {
1441
2180
  const prior = this.#database.prepare('SELECT * FROM source_jobs WHERE authority_id = ? AND idempotency_key = ?')
@@ -1448,6 +2187,30 @@ export class ControlPlaneStore {
1448
2187
  this.#database.exec('COMMIT');
1449
2188
  return record;
1450
2189
  }
2190
+ if (intent.baseline !== undefined) {
2191
+ const rows = this.#database.prepare(`SELECT * FROM source_jobs WHERE json_extract(intent_json, '$.repository') = ?
2192
+ AND json_type(intent_json, '$.baseline') IS NOT NULL AND status IN ('queued', 'running', 'unknown', 'prepared')
2193
+ LIMIT 1001`).all(intent.repository);
2194
+ if (rows.length > 1000)
2195
+ throw new ControlPlaneStoreError('invalid-state', 'managed source job cohort exceeds bound');
2196
+ for (const row of rows) {
2197
+ const competing = sourceJobFromRow(row);
2198
+ if (competing.status !== 'prepared')
2199
+ throw new ControlPlaneStoreError('conflict', 'managed source job is already active for repository');
2200
+ const prepared = competing.planId === undefined ? undefined : this.getSourcePlan(competing.planId);
2201
+ // A revision rebuilds its exact retained parent in a new private tree;
2202
+ // it cannot advance that parent's source release or alter the baseline.
2203
+ if (intent.revision && prepared?.id === intent.revision.parent.planId && prepared.mode === 'prepared-create') {
2204
+ const certificate = this.getCreationVerification(prepared.id);
2205
+ if (certificate && controlPlaneDigest(certificate) === intent.revision.parent.certificateDigest
2206
+ && certificate.plan.artifactSha256 === intent.revision.parent.artifactSha256)
2207
+ continue;
2208
+ }
2209
+ if (prepared === undefined || !['release-complete', 'release-failed', 'expired', 'local-checks-failed'].includes(prepared.status)) {
2210
+ throw new ControlPlaneStoreError('conflict', 'managed source job is awaiting a terminal source release');
2211
+ }
2212
+ }
2213
+ }
1451
2214
  const authority = this.#database.prepare('SELECT * FROM source_job_authorities WHERE authority_id = ?').get(intent.authority.id);
1452
2215
  if (authority !== undefined && (authority.authority_digest !== intent.authority.digest || authority.expires_at !== intent.authority.expiresAt
1453
2216
  || authority.max_submissions !== intent.authority.maxSubmissions)) {
@@ -1461,6 +2224,36 @@ export class ControlPlaneStore {
1461
2224
  }
1462
2225
  this.#database.prepare('UPDATE source_job_authorities SET submissions = submissions + 1 WHERE authority_id = ? AND submissions < max_submissions')
1463
2226
  .run(intent.authority.id);
2227
+ if (intent.creation !== undefined) {
2228
+ const grant = intent.creation.grant, digest = controlPlaneDigest(grant);
2229
+ const previous = this.#database.prepare('SELECT * FROM source_creation_grants WHERE grant_id = ?').get(grant.id);
2230
+ if (previous !== undefined && (previous.grant_digest !== digest || previous.expires_at !== grant.expiresAt
2231
+ || previous.max_creates !== grant.maxCreates))
2232
+ throw new ControlPlaneStoreError('conflict', 'source creation grant definition is immutable');
2233
+ if (previous !== undefined && previous.creates >= previous.max_creates)
2234
+ throw new ControlPlaneStoreError('invalid-state', 'source creation grant quota is exhausted');
2235
+ if (previous === undefined)
2236
+ this.#database.prepare(`INSERT INTO source_creation_grants
2237
+ (grant_id, grant_digest, expires_at, max_creates, creates) VALUES (?, ?, ?, ?, 0)`)
2238
+ .run(grant.id, digest, grant.expiresAt, grant.maxCreates);
2239
+ const quota = this.#database.prepare('UPDATE source_creation_grants SET creates = creates + 1 WHERE grant_id = ? AND creates < max_creates').run(grant.id);
2240
+ if (Number(quota.changes) !== 1)
2241
+ throw new ControlPlaneStoreError('conflict', 'source creation grant quota changed');
2242
+ }
2243
+ if (intent.revision !== undefined) {
2244
+ const grant = intent.revision.grant, digest = controlPlaneDigest(grant);
2245
+ const previous = this.#database.prepare('SELECT * FROM source_revision_grants WHERE grant_id=?').get(grant.id);
2246
+ if (previous && (previous.grant_digest !== digest || previous.expires_at !== grant.expiresAt || previous.max_revisions !== grant.maxRevisions)) {
2247
+ throw new ControlPlaneStoreError('conflict', 'source revision grant definition is immutable');
2248
+ }
2249
+ if (previous && previous.revisions >= previous.max_revisions)
2250
+ throw new ControlPlaneStoreError('invalid-state', 'source revision grant quota is exhausted');
2251
+ if (!previous)
2252
+ this.#database.prepare('INSERT INTO source_revision_grants VALUES(?,?,?,?,0)').run(grant.id, digest, grant.expiresAt, grant.maxRevisions);
2253
+ const quota = this.#database.prepare('UPDATE source_revision_grants SET revisions=revisions+1 WHERE grant_id=? AND revisions<max_revisions').run(grant.id);
2254
+ if (Number(quota.changes) !== 1)
2255
+ throw new ControlPlaneStoreError('conflict', 'source revision grant quota changed');
2256
+ }
1464
2257
  this.#database.prepare(`INSERT INTO source_jobs (id, automation_id, authority_id, idempotency_key, intent_json, intent_digest,
1465
2258
  status, revision, created_at, expires_at, definition_hash, occurrence_id, plan_id, failure_code, updated_at)
1466
2259
  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 +2284,67 @@ export class ControlPlaneStore {
1491
2284
  created_at DESC, id DESC LIMIT ?`).all(limit).map(sourceJobFromRow);
1492
2285
  }
1493
2286
  /** Prepared owner continuations have a bounded recovery query, independent of job history. */
1494
- listPreparedSourceApprovalJobs(includeRelease = false, includeExecution = false, includeAdoption = false) {
2287
+ listPreparedSourceApprovalJobs(includeRelease = false, includeExecution = false, includeAdoption = false, includeCreation = false, includeCreationAdoption = false, includeRevision = false, includeRegression = false) {
1495
2288
  return this.#database.prepare(`SELECT j.* FROM source_jobs j JOIN source_plans p ON p.id = j.plan_id
1496
2289
  JOIN owner_task_failure_gaps g ON g.gap_id = p.gap_id
1497
2290
  LEFT JOIN source_adoptions a ON a.source_plan_id = p.id
1498
2291
  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'))
2292
+ 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
2293
  OR (? = 1 AND p.status IN ('awaiting-pr', 'awaiting-review', 'awaiting-merge', 'awaiting-build', 'awaiting-sign', 'awaiting-publish',
1501
2294
  '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);
2295
+ OR (? = 1 AND p.status = 'release-complete' AND (ap.id IS NULL OR ap.status NOT IN ('activated', 'rolled-back', 'rejected')))))
2296
+ OR (? = 1 AND p.mode = 'prepared-create' AND p.status = 'pending-approval'
2297
+ AND json_type(j.intent_json, '$.creation.growthRun.creationAcceptance') IS NOT NULL
2298
+ AND NOT EXISTS (SELECT 1 FROM source_creation_verifications v WHERE v.plan_id = p.id))
2299
+ OR (? = 1 AND p.mode = 'prepared-create' AND p.status = 'pending-approval'
2300
+ AND EXISTS (SELECT 1 FROM source_creation_verifications v WHERE v.plan_id = p.id AND v.status = 'verified'))
2301
+ OR (? = 1 AND p.mode = 'prepared-revise' AND p.status = 'pending-approval'
2302
+ AND json_type(j.intent_json, '$.revision.growthRun.revisionAcceptance') IS NOT NULL
2303
+ AND NOT EXISTS (SELECT 1 FROM source_revision_verifications v WHERE v.plan_id=p.id))
2304
+ OR (? = 1 AND p.mode = 'prepared-revise' AND p.status = 'pending-approval'
2305
+ AND json_type(j.intent_json, '$.revision.growthRun.revisionRegressionAcceptance') IS NOT NULL
2306
+ AND EXISTS (SELECT 1 FROM source_revision_verifications v WHERE v.plan_id=p.id AND v.status='verified')
2307
+ AND NOT EXISTS (SELECT 1 FROM source_revision_regressions v WHERE v.plan_id=p.id)))
2308
+ 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'))
2309
+ 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
2310
  }
1505
2311
  bindSourceJobDefinition(input) {
1506
2312
  if (!Number.isSafeInteger(input.revision) || input.revision < 1 || !DIGEST.test(input.definitionHash))
1507
2313
  throw new ControlPlaneStoreError('invalid-input', 'source job definition binding is invalid');
1508
2314
  const now = this.#now();
1509
- const result = this.#database.prepare(`UPDATE source_jobs SET definition_hash = ?, revision = revision + 1, updated_at = ?
2315
+ const result = this.#database.prepare(`UPDATE source_jobs SET definition_hash = ?, previous_definition_hash = NULL,
2316
+ previous_definition_version = NULL, revision = revision + 1, updated_at = ?
1510
2317
  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
2318
  if (Number(result.changes) !== 1)
1512
2319
  throw new ControlPlaneStoreError('conflict', 'source job definition binding lost its queued CAS');
1513
2320
  return this.getSourceJob(input.id);
1514
2321
  }
2322
+ /** Persist the only safe rearm: same queued job, with no claimed occurrence. */
2323
+ rearmQueuedGrowthSourceJob(input) {
2324
+ if (!Number.isSafeInteger(input.revision) || input.revision < 1 || !DIGEST.test(input.priorDefinitionHash)
2325
+ || !Number.isSafeInteger(input.priorDefinitionVersion) || input.priorDefinitionVersion < 1
2326
+ || !Number.isSafeInteger(input.dispatchAt) || input.dispatchAt <= this.#now()) {
2327
+ throw new ControlPlaneStoreError('invalid-input', 'source growth rearm request is invalid');
2328
+ }
2329
+ const now = this.#now();
2330
+ const result = this.#database.prepare(`UPDATE source_jobs SET dispatch_at = ?, previous_definition_hash = definition_hash,
2331
+ previous_definition_version = ?,
2332
+ definition_hash = NULL, revision = revision + 1, updated_at = ? WHERE id = ? AND revision = ? AND status = 'queued'
2333
+ 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);
2334
+ if (Number(result.changes) !== 1)
2335
+ throw new ControlPlaneStoreError('conflict', 'source growth rearm lost its queued CAS');
2336
+ return this.getSourceJob(input.id);
2337
+ }
1515
2338
  claimSourceJob(input) {
1516
2339
  if (!Number.isSafeInteger(input.revision) || input.revision < 1 || !DIGEST.test(input.definitionHash) || !KEY.test(input.occurrenceId)) {
1517
2340
  throw new ControlPlaneStoreError('invalid-input', 'source job claim is invalid');
1518
2341
  }
1519
2342
  const now = this.#now();
1520
2343
  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);
2344
+ WHERE id = ? AND revision = ? AND status = 'queued' AND definition_hash = ? AND occurrence_id IS NULL AND expires_at > ?
2345
+ AND (json_type(intent_json, '$.creation') IS NULL OR json_extract(intent_json, '$.creation.grant.expiresAt') > ?)
2346
+ AND (json_type(intent_json, '$.revision') IS NULL OR json_extract(intent_json, '$.revision.grant.expiresAt') > ?)`)
2347
+ .run(input.occurrenceId, now, input.id, input.revision, input.definitionHash, now, now, now);
1522
2348
  if (Number(result.changes) !== 1)
1523
2349
  throw new ControlPlaneStoreError('conflict', 'source job claim lost its queued CAS');
1524
2350
  return this.getSourceJob(input.id);
@@ -1528,7 +2354,8 @@ export class ControlPlaneStore {
1528
2354
  throw new ControlPlaneStoreError('invalid-input', 'source job settlement is invalid');
1529
2355
  const permitted = input.status === 'unknown' ? "status = 'running'" : "status IN ('queued', 'running', 'unknown')";
1530
2356
  const now = this.#now();
1531
- const result = this.#database.prepare(`UPDATE source_jobs SET status = ?, failure_code = ?, revision = revision + 1, updated_at = ?
2357
+ const result = this.#database.prepare(`UPDATE source_jobs SET status = ?, failure_code = ?,
2358
+ previous_definition_hash = NULL, previous_definition_version = NULL, revision = revision + 1, updated_at = ?
1532
2359
  WHERE id = ? AND revision = ? AND ${permitted}`).run(input.status, input.failureCode, now, input.id, input.revision);
1533
2360
  if (Number(result.changes) !== 1)
1534
2361
  throw new ControlPlaneStoreError('conflict', 'source job settlement lost its state CAS');
@@ -1540,10 +2367,22 @@ export class ControlPlaneStore {
1540
2367
  revision = revision + 1, updated_at = ? WHERE status = 'running'`).run(now);
1541
2368
  return Number(result.changes);
1542
2369
  }
2370
+ /** Revoke claimed task-created jobs when their Host run producer disappears. */
2371
+ interruptGrowthSourceJobs() {
2372
+ const now = this.#now();
2373
+ const result = this.#database.prepare(`UPDATE source_jobs SET status = 'unknown', failure_code = 'source-growth-provider-disposed',
2374
+ revision = revision + 1, updated_at = ? WHERE status = 'running'
2375
+ AND (json_type(intent_json, '$.creation.growthRun') IS NOT NULL OR json_type(intent_json, '$.revision.growthRun') IS NOT NULL)`).run(now);
2376
+ return Number(result.changes);
2377
+ }
1543
2378
  createSourcePlan(input) {
1544
2379
  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');
2380
+ if (mode !== 'create' && mode !== 'modify' && mode !== 'prepared-create' && mode !== 'prepared-revise')
2381
+ throw new ControlPlaneStoreError('invalid-input', 'source plan mode is invalid');
2382
+ if (input.sourceJob !== undefined && mode === 'create')
2383
+ throw new ControlPlaneStoreError('invalid-input', 'source jobs only complete checked modify or prepared-create plans');
2384
+ if ((mode === 'prepared-create' || mode === 'prepared-revise') && input.sourceJob === undefined)
2385
+ throw new ControlPlaneStoreError('invalid-input', 'prepared creation requires a running source job');
1547
2386
  const key = bounded(input.idempotencyKey, 'idempotencyKey', 160);
1548
2387
  const name = bounded(input.name, 'name', 64);
1549
2388
  if (!KEY.test(key) || !PLUGIN_NAME.test(name) || !COMMIT.test(input.baseCommit) || !DIGEST.test(input.generatorDigest)
@@ -1552,20 +2391,48 @@ export class ControlPlaneStore {
1552
2391
  if (mode === 'modify' && input.generatorDigest !== MODIFY_GENERATOR_DIGEST) {
1553
2392
  throw new ControlPlaneStoreError('invalid-input', 'modify source plans must bind the modify generator constant');
1554
2393
  }
2394
+ const creation = input.creation === undefined ? undefined : creationBindingFromStored(input.creation);
2395
+ if ((mode === 'prepared-create') !== (creation !== undefined)
2396
+ || (creation !== undefined && (creation.generatorDigest !== input.generatorDigest || !name.startsWith(creation.grant.namePrefix)))) {
2397
+ throw new ControlPlaneStoreError('invalid-input', 'prepared source creation binding is invalid');
2398
+ }
2399
+ const revision = input.revision === undefined ? undefined : revisionBindingFromStored(input.revision);
2400
+ if ((mode === 'prepared-revise') !== (revision !== undefined)
2401
+ || (revision !== undefined && (revision.generatorDigest !== input.generatorDigest || !name.startsWith(revision.grant.namePrefix)))) {
2402
+ throw new ControlPlaneStoreError('invalid-input', 'prepared source revision binding is invalid');
2403
+ }
1555
2404
  let preparedEvidence;
1556
- if (mode === 'modify') {
2405
+ if (mode === 'modify' || mode === 'prepared-create' || mode === 'prepared-revise') {
1557
2406
  if (input.prepared === undefined) {
1558
- throw new ControlPlaneStoreError('invalid-input', 'modify source plan requires prepared check evidence');
2407
+ throw new ControlPlaneStoreError('invalid-input', 'prepared source plan requires check evidence');
1559
2408
  }
1560
2409
  if (!DIGEST.test(input.prepared.treeDigest) || !DIGEST.test(input.prepared.patchDigest)
1561
2410
  || !Number.isSafeInteger(input.prepared.checkedAt) || input.prepared.checkedAt < 0) {
1562
2411
  throw new ControlPlaneStoreError('invalid-input', 'modify source plan prepared digests are invalid');
1563
2412
  }
1564
2413
  preparedEvidence = preparedEvidenceFromStored(input.prepared.evidence);
2414
+ if ((mode === 'modify' && preparedEvidence.schemaVersion !== 1)
2415
+ || (preparedEvidence.schemaVersion === 2 && input.sourceJob === undefined)) {
2416
+ throw new ControlPlaneStoreError('invalid-input', 'native source evidence requires a durable created plugin job');
2417
+ }
1565
2418
  }
1566
2419
  else if (input.prepared !== undefined) {
1567
2420
  throw new ControlPlaneStoreError('invalid-input', 'create source plans cannot carry prepared evidence');
1568
2421
  }
2422
+ let preparedArtifact;
2423
+ if (input.preparedArtifact !== undefined) {
2424
+ if (mode !== 'prepared-create' && mode !== 'prepared-revise' || input.sourceJob === undefined || !Buffer.isBuffer(input.preparedArtifact)
2425
+ || preparedEvidence === undefined || input.preparedArtifact.length < 1
2426
+ || input.preparedArtifact.length > MAX_PREPARED_ARTIFACT_BYTES
2427
+ || input.preparedArtifact.length !== preparedEvidence.pack.sizeBytes
2428
+ || createHash('sha256').update(input.preparedArtifact).digest('hex') !== preparedEvidence.pack.sha256) {
2429
+ throw new ControlPlaneStoreError('invalid-input', 'prepared source artifact differs from checked package evidence');
2430
+ }
2431
+ if (this.getOwnerTaskFailureReference(input.gapId) === undefined) {
2432
+ throw new ControlPlaneStoreError('invalid-state', 'prepared source artifact requires an owner task failure gap');
2433
+ }
2434
+ preparedArtifact = Buffer.from(input.preparedArtifact);
2435
+ }
1569
2436
  positiveInteger(input.ttlMs, 'ttlMs');
1570
2437
  if (input.ttlMs < 60_000 || input.ttlMs > 86_400_000)
1571
2438
  throw new ControlPlaneStoreError('invalid-input', 'ttlMs is invalid');
@@ -1582,11 +2449,24 @@ export class ControlPlaneStore {
1582
2449
  this.#assertOwnerTaskFailureGapAdmission(input.gapId);
1583
2450
  const sourceJobBinding = input.sourceJob === undefined ? undefined : { jobId: input.sourceJob.jobId, jobRevision: input.sourceJob.jobRevision,
1584
2451
  occurrenceId: input.sourceJob.occurrenceId };
1585
- const inputDigest = controlPlaneDigest({ ...(mode === 'modify' ? { ...requestBinding, mode, prepared: input.prepared } : requestBinding),
1586
- ...(sourceJobBinding === undefined ? {} : { sourceJob: sourceJobBinding }) });
2452
+ const inputDigest = controlPlaneDigest({ ...(mode === 'create' ? requestBinding : { ...requestBinding, mode,
2453
+ ...(creation === undefined ? {} : { creation }), ...(revision === undefined ? {} : { revision }), prepared: input.prepared }),
2454
+ ...(sourceJobBinding === undefined ? {} : { sourceJob: sourceJobBinding }),
2455
+ ...(preparedArtifact === undefined ? {} : { preparedArtifactSha256: preparedEvidence.pack.sha256 }) });
1587
2456
  const prior = this.#sourcePlanReceiptByKey(key, 'create-source-plan', inputDigest);
1588
- if (prior !== undefined)
2457
+ if (prior !== undefined) {
2458
+ if (preparedArtifact !== undefined) {
2459
+ const reference = this.#database.prepare('SELECT pack_sha256 FROM source_prepared_artifact_refs WHERE plan_id = ?')
2460
+ .get(prior.result.id);
2461
+ const row = this.#database.prepare('SELECT * FROM source_prepared_artifacts WHERE pack_sha256 = ?')
2462
+ .get(preparedEvidence.pack.sha256);
2463
+ if (reference?.pack_sha256 !== preparedEvidence.pack.sha256 || row === undefined
2464
+ || !preparedArtifactBytesFromRow(row, preparedEvidence.pack).equals(preparedArtifact)) {
2465
+ throw new ControlPlaneStoreError('invalid-state', 'idempotent prepared source artifact is missing or changed');
2466
+ }
2467
+ }
1589
2468
  return prior;
2469
+ }
1590
2470
  const gap = this.getGap(input.gapId);
1591
2471
  if (gap.status !== 'open' || gap.candidateId !== undefined) {
1592
2472
  throw new ControlPlaneStoreError('invalid-state', 'only an unreserved open gap can create a source plan');
@@ -1598,8 +2478,8 @@ export class ControlPlaneStore {
1598
2478
  const immutable = { schemaVersion: 1, kind: 'source', id, gapId: gap.id, gapSnapshot,
1599
2479
  repository: input.repository, worktree: input.worktree, baseCommit: input.baseCommit, name,
1600
2480
  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,
2481
+ const digest = controlPlaneDigest(mode !== 'create'
2482
+ ? { ...immutable, mode, ...(creation === undefined ? {} : { creation }), ...(revision === undefined ? {} : { sourceRevision: revision }), sourceCheck: { treeDigest: input.prepared.treeDigest, patchDigest: input.prepared.patchDigest,
1603
2483
  checkedAt: input.prepared.checkedAt }, preparedEvidence }
1604
2484
  : immutable);
1605
2485
  this.#database.exec('BEGIN IMMEDIATE');
@@ -1615,11 +2495,43 @@ export class ControlPlaneStore {
1615
2495
  || sourceJob.occurrenceId !== sourceJobBinding.occurrenceId || sourceJob.intent.gapId !== gap.id
1616
2496
  || sourceJob.intent.gapRevision !== currentGap.revision || sourceJob.intent.gapDigest !== controlPlaneDigest(currentGap)
1617
2497
  || 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) {
2498
+ || sourceJob.intent.repository !== input.repository || sourceJob.intent.worktree !== input.worktree || sourceJob.intent.ttlMs !== input.ttlMs
2499
+ || (mode === 'prepared-create') !== (sourceJob.intent.mode === 'create')
2500
+ || (preparedEvidence !== undefined && ('backend' in sourceJob.intent.build) !== (preparedEvidence.schemaVersion === 2))
2501
+ || (mode === 'prepared-create' && controlPlaneDigest(sourceJob.intent.creation) !== controlPlaneDigest(creation))
2502
+ || (mode === 'prepared-revise') !== (sourceJob.intent.mode === 'revise-created')
2503
+ || (mode === 'prepared-revise' && controlPlaneDigest(sourceJob.intent.revision) !== controlPlaneDigest(revision))) {
1619
2504
  throw new ControlPlaneStoreError('conflict', 'source job completion lost its immutable running binding');
1620
2505
  }
1621
- if (now >= sourceJob.expiresAt)
2506
+ if (now >= sourceJob.expiresAt || (creation !== undefined && now >= creation.grant.expiresAt) || (revision !== undefined && now >= revision.grant.expiresAt))
1622
2507
  throw new ControlPlaneStoreError('expired', 'source job authority is expired');
2508
+ if (mode === 'prepared-create' && this.getOwnerTaskFailureReference(input.gapId) !== undefined
2509
+ && (!creation?.growthRun || now >= creation.growthRun.expiresAt)) {
2510
+ throw new ControlPlaneStoreError('expired', 'task-backed creation growth run is absent or expired');
2511
+ }
2512
+ }
2513
+ if (revision !== undefined && (this.getOwnerTaskFailureReference(input.gapId) === undefined || now >= revision.growthRun.expiresAt)) {
2514
+ throw new ControlPlaneStoreError('expired', 'revision requires a current owner task and frozen growth run');
2515
+ }
2516
+ if (preparedArtifact !== undefined) {
2517
+ const pack = preparedEvidence.pack;
2518
+ const existing = this.#database.prepare('SELECT * FROM source_prepared_artifacts WHERE pack_sha256 = ?')
2519
+ .get(pack.sha256);
2520
+ if (existing !== undefined) {
2521
+ if (!preparedArtifactBytesFromRow(existing, pack).equals(preparedArtifact)) {
2522
+ throw new ControlPlaneStoreError('invalid-state', 'stored prepared source artifact differs from checked bytes');
2523
+ }
2524
+ }
2525
+ else {
2526
+ const totals = this.#database.prepare('SELECT count(*) AS count, coalesce(sum(size_bytes), 0) AS total FROM source_prepared_artifacts')
2527
+ .get();
2528
+ if (!Number.isSafeInteger(totals.count) || !Number.isSafeInteger(totals.total)
2529
+ || totals.count >= MAX_STORED_ARTIFACTS || totals.total > MAX_STORED_ARTIFACT_BYTES - preparedArtifact.length) {
2530
+ throw new ControlPlaneStoreError('invalid-state', 'prepared source artifact storage quota is exhausted');
2531
+ }
2532
+ this.#database.prepare('INSERT INTO source_prepared_artifacts (pack_sha256, size_bytes, bytes, created_at) VALUES (?, ?, ?, ?)')
2533
+ .run(pack.sha256, preparedArtifact.length, preparedArtifact, now);
2534
+ }
1623
2535
  }
1624
2536
  this.#database.prepare('INSERT INTO gap_plan_claims (gap_id, plan_id, plan_kind, claimed_at) VALUES (?, ?, ?, ?)').run(gap.id, id, 'source', now);
1625
2537
  // 'modify' rows carry their checked digests and frozen-build evidence from
@@ -1627,8 +2539,12 @@ export class ControlPlaneStore {
1627
2539
  // post-approval scaffold run finishes.
1628
2540
  this.#database.prepare(`INSERT INTO source_plans (id, plan_digest, gap_id, gap_snapshot_json, repository, worktree,
1629
2541
  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);
2542
+ approval_json, checked_tree_digest, checked_patch_digest, checked_at, prepared_evidence_json, creation_json, revision_json, updated_at)
2543
+ 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);
2544
+ if (preparedArtifact !== undefined) {
2545
+ this.#database.prepare('INSERT INTO source_prepared_artifact_refs (plan_id, pack_sha256) VALUES (?, ?)')
2546
+ .run(id, preparedEvidence.pack.sha256);
2547
+ }
1632
2548
  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
2549
  if (Number(matched.changes) !== 1)
1634
2550
  throw new ControlPlaneStoreError('conflict', 'capability gap changed while source plan was created');
@@ -1658,6 +2574,735 @@ export class ControlPlaneStore {
1658
2574
  throw new ControlPlaneStoreError('not-found', 'source plan not found');
1659
2575
  return sourceFromRow(row);
1660
2576
  }
2577
+ /** Exact durable source job for a currently admitted prepared creation. */
2578
+ getPreparedSourceJob(planId) {
2579
+ const plan = this.getSourcePlan(planId);
2580
+ if (plan.mode !== 'prepared-create' || plan.status !== 'pending-approval'
2581
+ || plan.creation === undefined || plan.preparedEvidence === undefined
2582
+ || this.#now() >= plan.expiresAt || this.#now() >= plan.creation.grant.expiresAt) {
2583
+ throw new ControlPlaneStoreError('invalid-state', 'prepared creation requires a current pending plan');
2584
+ }
2585
+ const owner = this.getOwnerTaskFailureReference(plan.gapId);
2586
+ if (owner === undefined)
2587
+ throw new ControlPlaneStoreError('invalid-state', 'prepared creation requires an owner task failure gap');
2588
+ this.#assertOwnerTaskFailureGapAdmission(plan.gapId);
2589
+ return this.getRetainedPreparedCreation(planId).job;
2590
+ }
2591
+ /** Read-only exact historical binding for an already signed adoption. Caller must hold the current owner fence. */
2592
+ getRetainedPreparedCreation(planId) {
2593
+ const plan = this.getSourcePlan(planId);
2594
+ if (plan.mode !== 'prepared-create' || !['pending-approval', 'expired'].includes(plan.status)
2595
+ || plan.creation === undefined || plan.preparedEvidence === undefined || plan.sourceCheck === undefined) {
2596
+ throw new ControlPlaneStoreError('invalid-state', 'retained creation requires exact prepared evidence');
2597
+ }
2598
+ const owner = this.getOwnerTaskFailureReference(plan.gapId);
2599
+ if (owner === undefined)
2600
+ throw new ControlPlaneStoreError('invalid-state', 'retained creation 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 creation 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 !== 'create'
2608
+ || job.intent.creation === 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.creation) !== controlPlaneDigest(plan.creation)
2614
+ || job.intent.creation.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 creation source job binding is corrupt');
2619
+ }
2620
+ return { plan, job, reference: owner };
2621
+ }
2622
+ /** Exact durable source job for a currently admitted prepared revision. */
2623
+ getPreparedRevisionJob(planId) {
2624
+ const plan = this.getSourcePlan(planId);
2625
+ if (plan.mode !== 'prepared-revise' || plan.status !== 'pending-approval'
2626
+ || plan.sourceRevision === undefined || plan.preparedEvidence === undefined
2627
+ || this.#now() >= plan.expiresAt || this.#now() >= plan.sourceRevision.grant.expiresAt) {
2628
+ throw new ControlPlaneStoreError('invalid-state', 'prepared revision requires a current pending plan');
2629
+ }
2630
+ const owner = this.getOwnerTaskFailureReference(plan.gapId);
2631
+ if (owner === undefined)
2632
+ throw new ControlPlaneStoreError('invalid-state', 'prepared revision requires an owner task failure gap');
2633
+ this.#assertOwnerTaskFailureGapAdmission(plan.gapId);
2634
+ return this.getRetainedPreparedRevision(planId).job;
2635
+ }
2636
+ /** Read-only exact historical binding for an already signed adoption. Caller must hold the current owner fence. */
2637
+ getRetainedPreparedRevision(planId) {
2638
+ const plan = this.getSourcePlan(planId);
2639
+ if (plan.mode !== 'prepared-revise' || !['pending-approval', 'expired'].includes(plan.status)
2640
+ || plan.sourceRevision === undefined || plan.preparedEvidence === undefined || plan.sourceCheck === undefined) {
2641
+ throw new ControlPlaneStoreError('invalid-state', 'retained revision requires exact prepared evidence');
2642
+ }
2643
+ const owner = this.getOwnerTaskFailureReference(plan.gapId);
2644
+ if (owner === undefined)
2645
+ throw new ControlPlaneStoreError('invalid-state', 'retained revision requires an owner task failure gap');
2646
+ const row = this.#database.prepare('SELECT * FROM source_jobs WHERE plan_id = ?')
2647
+ .get(plan.id);
2648
+ if (row === undefined)
2649
+ throw new ControlPlaneStoreError('invalid-state', 'prepared revision has no source job');
2650
+ const job = sourceJobFromRow(row);
2651
+ const gap = this.getGap(plan.gapId);
2652
+ if (job.status !== 'prepared' || job.planId !== plan.id || job.intent.mode !== 'revise-created'
2653
+ || job.intent.revision === undefined || job.intent.gapId !== plan.gapId
2654
+ || job.intent.gapRevision !== plan.gapSnapshot.revision
2655
+ || gap.inputDigest !== plan.gapSnapshot.inputDigest
2656
+ || job.intent.ownerDigest !== controlPlaneDigest(owner.owner)
2657
+ || controlPlaneDigest(job.intent.owner) !== job.intent.ownerDigest
2658
+ || controlPlaneDigest(job.intent.revision) !== controlPlaneDigest(plan.sourceRevision)
2659
+ || job.intent.revision.generatorDigest !== plan.generatorDigest
2660
+ || job.intent.repository !== plan.repository || job.intent.worktree !== plan.worktree
2661
+ || job.intent.baseCommit !== plan.baseCommit || job.intent.name !== plan.name
2662
+ || plan.expiresAt !== plan.createdAt + job.intent.ttlMs) {
2663
+ throw new ControlPlaneStoreError('invalid-state', 'prepared revision source job binding is corrupt');
2664
+ }
2665
+ return { plan, job, reference: owner };
2666
+ }
2667
+ /** Host-only package bytes for an owner-admitted, still-pending created plugin. */
2668
+ readPreparedSourceArtifact(planId) {
2669
+ const plan = this.getSourcePlan(planId);
2670
+ if (plan.mode !== 'prepared-create' && plan.mode !== 'prepared-revise' || plan.status !== 'pending-approval'
2671
+ || plan.preparedEvidence === undefined || this.#now() >= plan.expiresAt) {
2672
+ throw new ControlPlaneStoreError('invalid-state', 'prepared source artifact requires a current pending creation plan');
2673
+ }
2674
+ if (plan.mode === 'prepared-revise')
2675
+ this.getPreparedRevisionJob(planId);
2676
+ else
2677
+ this.getPreparedSourceJob(planId);
2678
+ const pack = plan.preparedEvidence.pack;
2679
+ const reference = this.#database.prepare('SELECT pack_sha256 FROM source_prepared_artifact_refs WHERE plan_id = ?')
2680
+ .get(plan.id);
2681
+ if (reference?.pack_sha256 !== pack.sha256)
2682
+ throw new ControlPlaneStoreError('invalid-state', 'prepared source artifact reference is missing or corrupt');
2683
+ const row = this.#database.prepare('SELECT * FROM source_prepared_artifacts WHERE pack_sha256 = ?')
2684
+ .get(pack.sha256);
2685
+ if (row === undefined)
2686
+ throw new ControlPlaneStoreError('invalid-state', 'prepared source artifact is missing');
2687
+ return preparedArtifactBytesFromRow(row, pack);
2688
+ }
2689
+ /** One immutable, independently signed result per exact prepared creation. */
2690
+ recordCreationVerification(certificate) {
2691
+ validatePluginCreationVerificationCertificate(certificate);
2692
+ const plan = this.getSourcePlan(certificate.plan.id);
2693
+ const job = this.getPreparedSourceJob(plan.id);
2694
+ const reference = this.getOwnerTaskFailureReference(plan.gapId);
2695
+ if (!reference || !plan.sourceCheck || !plan.preparedEvidence || !plan.creation?.growthRun
2696
+ || !job.intent.creation?.growthRun || plan.digest !== certificate.plan.digest || plan.name !== certificate.plan.name
2697
+ || plan.sourceCheck.treeDigest !== certificate.plan.sourceTreeDigest
2698
+ || plan.sourceCheck.patchDigest !== certificate.plan.sourcePatchDigest
2699
+ || plan.preparedEvidence.pack.sha256 !== certificate.plan.artifactSha256
2700
+ || plan.preparedEvidence.pack.sizeBytes !== certificate.plan.artifactBytes
2701
+ || plan.generatorDigest !== certificate.plan.generatorDigest
2702
+ || controlPlaneDigest(reference) !== certificate.source.referenceDigest
2703
+ || controlPlaneDigest(reference.owner) !== certificate.source.ownerDigest
2704
+ || sourceGrowthRunDigest(plan.creation.growthRun) !== certificate.source.growthRunDigest
2705
+ || sourceGrowthRunDigest(job.intent.creation.growthRun) !== certificate.source.growthRunDigest
2706
+ || controlPlaneDigest(plan.creation.growthRun.model) !== controlPlaneDigest(certificate.model)
2707
+ || certificate.expiresAt > Math.min(plan.expiresAt, plan.creation.grant.expiresAt, plan.creation.growthRun.expiresAt, certificate.authority.expiresAt)) {
2708
+ throw new ControlPlaneStoreError('invalid-input', 'creation verification certificate does not match exact prepared source');
2709
+ }
2710
+ this.readPreparedSourceArtifact(plan.id);
2711
+ const serialized = JSON.stringify(certificate), certificateDigest = controlPlaneDigest(certificate);
2712
+ this.#database.exec('BEGIN IMMEDIATE');
2713
+ try {
2714
+ const previous = this.#database.prepare('SELECT status,certificate_digest FROM source_creation_verifications WHERE plan_id=?')
2715
+ .get(plan.id);
2716
+ if (previous?.status === 'verified' && previous.certificate_digest === certificateDigest) { /* idempotent */ }
2717
+ else if (previous?.status !== 'claimed')
2718
+ throw new ControlPlaneStoreError('conflict', 'creation verification was not claimed or already settled');
2719
+ else
2720
+ this.#database.prepare(`UPDATE source_creation_verifications SET status='verified',certificate_json=?,certificate_digest=?,updated_at=?
2721
+ WHERE plan_id=? AND status='claimed'`).run(serialized, certificateDigest, this.#now(), plan.id);
2722
+ this.#database.exec('COMMIT');
2723
+ }
2724
+ catch (error) {
2725
+ this.#database.exec('ROLLBACK');
2726
+ throw error;
2727
+ }
2728
+ }
2729
+ getCreationVerification(planId) {
2730
+ const row = this.#database.prepare('SELECT status,certificate_json,certificate_digest FROM source_creation_verifications WHERE plan_id=?')
2731
+ .get(planId);
2732
+ if (!row || row.status !== 'verified')
2733
+ return undefined;
2734
+ let certificate;
2735
+ try {
2736
+ const parsed = JSON.parse(row.certificate_json);
2737
+ validatePluginCreationVerificationCertificate(parsed);
2738
+ certificate = parsed;
2739
+ }
2740
+ catch {
2741
+ throw new ControlPlaneStoreError('invalid-state', 'stored creation verification certificate is corrupt');
2742
+ }
2743
+ if (certificate.plan.id !== planId || controlPlaneDigest(certificate) !== row.certificate_digest) {
2744
+ throw new ControlPlaneStoreError('invalid-state', 'stored creation verification certificate digest changed');
2745
+ }
2746
+ return certificate;
2747
+ }
2748
+ getCreationVerificationStatus(planId) {
2749
+ const row = this.#database.prepare('SELECT status FROM source_creation_verifications WHERE plan_id=?')
2750
+ .get(planId);
2751
+ return row?.status;
2752
+ }
2753
+ /** Content-free diagnostic projection; caller must hold current owner/source fence. */
2754
+ inspectCreationVerificationRecord(planId) {
2755
+ const row = this.#database.prepare('SELECT status,reason,updated_at FROM source_creation_verifications WHERE plan_id=?')
2756
+ .get(planId);
2757
+ if (!row)
2758
+ return undefined;
2759
+ if (!['claimed', 'verified', 'unknown', 'rejected'].includes(row.status)
2760
+ || !Number.isSafeInteger(row.updated_at) || row.updated_at < 1
2761
+ || (row.reason !== null && !/^[a-z][a-z0-9-]{0,79}$/u.test(row.reason))) {
2762
+ throw new ControlPlaneStoreError('invalid-state', 'creation verification diagnostic record is corrupt');
2763
+ }
2764
+ return { status: row.status, ...(row.reason === null ? {} : { reason: row.reason }), updatedAt: row.updated_at };
2765
+ }
2766
+ claimCreationVerification(planId) {
2767
+ const job = this.getPreparedSourceJob(planId);
2768
+ const pinned = job.intent.creation?.growthRun?.creationAcceptance;
2769
+ if (!pinned || this.#now() >= pinned.expiresAt) {
2770
+ throw new ControlPlaneStoreError('invalid-state', 'creation verification requires a current pre-author policy binding');
2771
+ }
2772
+ this.#database.exec('BEGIN IMMEDIATE');
2773
+ try {
2774
+ const count = this.#database.prepare('SELECT count(*) AS n FROM source_creation_verifications').get();
2775
+ if (count.n >= 256)
2776
+ throw new ControlPlaneStoreError('conflict', 'creation verification record limit reached');
2777
+ const now = this.#now();
2778
+ this.#database.prepare(`INSERT INTO source_creation_verifications
2779
+ (plan_id,status,certificate_json,certificate_digest,reason,created_at,updated_at) VALUES (?,'claimed',NULL,NULL,NULL,?,?)`)
2780
+ .run(planId, now, now);
2781
+ this.#database.exec('COMMIT');
2782
+ }
2783
+ catch (error) {
2784
+ this.#database.exec('ROLLBACK');
2785
+ throw error;
2786
+ }
2787
+ }
2788
+ settleCreationVerification(planId, status, reason) {
2789
+ if (typeof reason !== 'string' || !/^[a-z][a-z0-9-]{0,79}$/u.test(reason)) {
2790
+ throw new ControlPlaneStoreError('invalid-input', 'creation verification reason is invalid');
2791
+ }
2792
+ const result = this.#database.prepare(`UPDATE source_creation_verifications SET status=?,reason=?,updated_at=?
2793
+ WHERE plan_id=? AND status='claimed'`).run(status, reason, this.#now(), planId);
2794
+ if (Number(result.changes) !== 1)
2795
+ throw new ControlPlaneStoreError('conflict', 'creation verification claim is not current');
2796
+ }
2797
+ /** One immutable, independently signed result per exact prepared revision. */
2798
+ recordRevisionVerification(certificate, source) {
2799
+ validatePluginRevisionVerificationCertificate(certificate);
2800
+ const plan = this.getSourcePlan(certificate.plan.id);
2801
+ const job = this.getPreparedRevisionJob(plan.id);
2802
+ const reference = this.getOwnerTaskFailureReference(plan.gapId);
2803
+ validateCreationCapabilitySource(source, certificate);
2804
+ if (!reference || controlPlaneDigest(plan.sourceRevision?.parent) !== controlPlaneDigest(certificate.parent) || !plan.sourceCheck || !plan.preparedEvidence || !plan.sourceRevision?.growthRun
2805
+ || !job.intent.revision?.growthRun || plan.digest !== certificate.plan.digest || plan.name !== certificate.plan.name
2806
+ || plan.sourceCheck.treeDigest !== certificate.plan.sourceTreeDigest
2807
+ || plan.sourceCheck.patchDigest !== certificate.plan.sourcePatchDigest
2808
+ || plan.preparedEvidence.pack.sha256 !== certificate.plan.artifactSha256
2809
+ || plan.preparedEvidence.pack.sizeBytes !== certificate.plan.artifactBytes
2810
+ || plan.generatorDigest !== certificate.plan.generatorDigest
2811
+ || controlPlaneDigest(reference) !== certificate.source.referenceDigest
2812
+ || controlPlaneDigest(reference.owner) !== certificate.source.ownerDigest
2813
+ || sourceGrowthRunDigest(plan.sourceRevision.growthRun) !== certificate.source.growthRunDigest
2814
+ || sourceGrowthRunDigest(job.intent.revision.growthRun) !== certificate.source.growthRunDigest
2815
+ || controlPlaneDigest(plan.sourceRevision.growthRun.model) !== controlPlaneDigest(certificate.model)
2816
+ || certificate.expiresAt > Math.min(plan.expiresAt, plan.sourceRevision.grant.expiresAt, plan.sourceRevision.growthRun.expiresAt, certificate.authority.expiresAt)) {
2817
+ throw new ControlPlaneStoreError('invalid-input', 'revision verification certificate does not match exact prepared source');
2818
+ }
2819
+ this.readPreparedSourceArtifact(plan.id);
2820
+ if (source.baseCommit !== plan.baseCommit || controlPlaneDigest(source.scope) !== controlPlaneDigest(plan.scope))
2821
+ throw new ControlPlaneStoreError('invalid-input', 'revision source archive base or scope changed');
2822
+ const sourceJson = JSON.stringify(source);
2823
+ if (Buffer.byteLength(sourceJson) > 2_097_152)
2824
+ throw new ControlPlaneStoreError('invalid-input', 'revision source archive exceeds bound');
2825
+ const serialized = JSON.stringify(certificate), certificateDigest = controlPlaneDigest(certificate);
2826
+ this.#database.exec('BEGIN IMMEDIATE');
2827
+ try {
2828
+ const previous = this.#database.prepare('SELECT status,certificate_digest FROM source_revision_verifications WHERE plan_id=?')
2829
+ .get(plan.id);
2830
+ if (previous?.status === 'verified' && previous.certificate_digest === certificateDigest) { /* idempotent */ }
2831
+ else if (previous?.status !== 'claimed')
2832
+ throw new ControlPlaneStoreError('conflict', 'revision verification was not claimed or already settled');
2833
+ else
2834
+ this.#database.prepare(`UPDATE source_revision_verifications SET status='verified',certificate_json=?,certificate_digest=?,updated_at=?
2835
+ WHERE plan_id=? AND status='claimed'`).run(serialized, certificateDigest, this.#now(), plan.id);
2836
+ const oldSource = this.#database.prepare('SELECT source_json FROM source_revision_sources WHERE plan_id=?').get(plan.id);
2837
+ if (oldSource && oldSource.source_json !== sourceJson)
2838
+ throw new ControlPlaneStoreError('conflict', 'revision source archive changed');
2839
+ if (!oldSource)
2840
+ this.#database.prepare('INSERT INTO source_revision_sources VALUES(?,?)').run(plan.id, sourceJson);
2841
+ this.#database.exec('COMMIT');
2842
+ }
2843
+ catch (error) {
2844
+ this.#database.exec('ROLLBACK');
2845
+ throw error;
2846
+ }
2847
+ }
2848
+ getRevisionVerification(planId) {
2849
+ const row = this.#database.prepare('SELECT status,certificate_json,certificate_digest FROM source_revision_verifications WHERE plan_id=?')
2850
+ .get(planId);
2851
+ if (!row || row.status !== 'verified')
2852
+ return undefined;
2853
+ let certificate;
2854
+ try {
2855
+ const parsed = JSON.parse(row.certificate_json);
2856
+ validatePluginRevisionVerificationCertificate(parsed);
2857
+ certificate = parsed;
2858
+ }
2859
+ catch {
2860
+ throw new ControlPlaneStoreError('invalid-state', 'stored revision verification certificate is corrupt');
2861
+ }
2862
+ if (certificate.plan.id !== planId || controlPlaneDigest(certificate) !== row.certificate_digest) {
2863
+ throw new ControlPlaneStoreError('invalid-state', 'stored revision verification certificate digest changed');
2864
+ }
2865
+ return certificate;
2866
+ }
2867
+ getRevisionVerificationStatus(planId) {
2868
+ const row = this.#database.prepare('SELECT status FROM source_revision_verifications WHERE plan_id=?')
2869
+ .get(planId);
2870
+ return row?.status;
2871
+ }
2872
+ /** Content-free diagnostic projection; caller must hold current owner/source fence. */
2873
+ inspectRevisionVerificationRecord(planId) {
2874
+ const row = this.#database.prepare('SELECT status,reason,updated_at FROM source_revision_verifications WHERE plan_id=?')
2875
+ .get(planId);
2876
+ if (!row)
2877
+ return undefined;
2878
+ if (!['claimed', 'verified', 'unknown', 'rejected'].includes(row.status)
2879
+ || !Number.isSafeInteger(row.updated_at) || row.updated_at < 1
2880
+ || (row.reason !== null && !/^[a-z][a-z0-9-]{0,79}$/u.test(row.reason))) {
2881
+ throw new ControlPlaneStoreError('invalid-state', 'revision verification diagnostic record is corrupt');
2882
+ }
2883
+ return { status: row.status, ...(row.reason === null ? {} : { reason: row.reason }), updatedAt: row.updated_at };
2884
+ }
2885
+ claimRevisionVerification(planId) {
2886
+ const job = this.getPreparedRevisionJob(planId);
2887
+ const pinned = job.intent.revision?.growthRun?.revisionAcceptance;
2888
+ if (!pinned || this.#now() >= pinned.expiresAt) {
2889
+ throw new ControlPlaneStoreError('invalid-state', 'revision verification requires a current pre-author policy binding');
2890
+ }
2891
+ this.#database.exec('BEGIN IMMEDIATE');
2892
+ try {
2893
+ const count = this.#database.prepare('SELECT count(*) AS n FROM source_revision_verifications').get();
2894
+ if (count.n >= 256)
2895
+ throw new ControlPlaneStoreError('conflict', 'revision verification record limit reached');
2896
+ const now = this.#now();
2897
+ this.#database.prepare(`INSERT INTO source_revision_verifications
2898
+ (plan_id,status,certificate_json,certificate_digest,reason,created_at,updated_at) VALUES (?,'claimed',NULL,NULL,NULL,?,?)`)
2899
+ .run(planId, now, now);
2900
+ this.#database.exec('COMMIT');
2901
+ }
2902
+ catch (error) {
2903
+ this.#database.exec('ROLLBACK');
2904
+ throw error;
2905
+ }
2906
+ }
2907
+ settleRevisionVerification(planId, status, reason) {
2908
+ if (typeof reason !== 'string' || !/^[a-z][a-z0-9-]{0,79}$/u.test(reason)) {
2909
+ throw new ControlPlaneStoreError('invalid-input', 'revision verification reason is invalid');
2910
+ }
2911
+ const result = this.#database.prepare(`UPDATE source_revision_verifications SET status=?,reason=?,updated_at=?
2912
+ WHERE plan_id=? AND status='claimed'`).run(status, reason, this.#now(), planId);
2913
+ if (Number(result.changes) !== 1)
2914
+ throw new ControlPlaneStoreError('conflict', 'revision verification claim is not current');
2915
+ }
2916
+ /** Purpose-separated paired held-out proof; never changes plan or adoption state. */
2917
+ recordRevisionRegression(certificate) {
2918
+ validatePluginRevisionRegressionCertificate(certificate);
2919
+ const plan = this.getSourcePlan(certificate.plan.id);
2920
+ const job = this.getPreparedRevisionJob(plan.id);
2921
+ const candidate = this.getRevisionVerification(plan.id);
2922
+ const parent = this.getCreationVerification(certificate.parent.planId);
2923
+ const source = this.readRevisionSource(plan.id);
2924
+ const reference = this.getOwnerTaskFailureReference(plan.gapId);
2925
+ const run = plan.sourceRevision?.growthRun;
2926
+ const previousJobs = new Set([...(parent?.observations ?? []), ...(candidate?.observations ?? [])].map(item => item.jobId));
2927
+ if (certificate.verifiedAt > this.#now() || certificate.expiresAt <= this.#now()
2928
+ || !candidate || !parent || !source || !reference || !run?.revisionRegressionAcceptance
2929
+ || !job.intent.revision?.growthRun.revisionRegressionAcceptance
2930
+ || controlPlaneDigest(certificate.plan) !== controlPlaneDigest(candidate.plan)
2931
+ || controlPlaneDigest(certificate.source) !== controlPlaneDigest(candidate.source)
2932
+ || controlPlaneDigest(certificate.parent) !== controlPlaneDigest(candidate.parent)
2933
+ || certificate.candidateVerificationDigest !== controlPlaneDigest(candidate)
2934
+ || certificate.sourceDigest !== source.digest
2935
+ || certificate.contractDigest !== parent.contractDigest
2936
+ || certificate.schemaDigest !== candidate.schemaDigest
2937
+ || controlPlaneDigest(certificate.environment) !== controlPlaneDigest(candidate.environment)
2938
+ || controlPlaneDigest(certificate.environment) !== controlPlaneDigest(parent.environment)
2939
+ || controlPlaneDigest(certificate.model) !== controlPlaneDigest(run.model)
2940
+ || controlPlaneDigest(certificate.authority) !== controlPlaneDigest(run.revisionRegressionAcceptance)
2941
+ || controlPlaneDigest(certificate.authority) !== controlPlaneDigest(job.intent.revision.growthRun.revisionRegressionAcceptance)
2942
+ || certificate.source.referenceDigest !== controlPlaneDigest(reference)
2943
+ || certificate.source.ownerDigest !== controlPlaneDigest(reference.owner)
2944
+ || certificate.source.growthRunDigest !== sourceGrowthRunDigest(run)
2945
+ || sourceGrowthRunDigest(run) !== sourceGrowthRunDigest(job.intent.revision.growthRun)
2946
+ || certificate.observations.length !== parent.observations.length
2947
+ || certificate.observations.some((item, index) => item.caseId !== parent.observations[index]?.caseId
2948
+ || previousJobs.has(item.parent.jobId) || previousJobs.has(item.candidate.jobId))
2949
+ || certificate.expiresAt > Math.min(candidate.expiresAt, plan.expiresAt, run.expiresAt, plan.sourceRevision.grant.expiresAt, certificate.authority.expiresAt)) {
2950
+ throw new ControlPlaneStoreError('invalid-input', 'regression certificate does not match exact parent and candidate');
2951
+ }
2952
+ this.readPreparedSourceArtifact(plan.id);
2953
+ const serialized = JSON.stringify(certificate), digest = controlPlaneDigest(certificate);
2954
+ this.#database.exec('BEGIN IMMEDIATE');
2955
+ try {
2956
+ const old = this.#database.prepare('SELECT status,certificate_digest FROM source_revision_regressions WHERE plan_id=?')
2957
+ .get(plan.id);
2958
+ if (old?.status === 'verified' && old.certificate_digest === digest) { /* immutable idempotent result */ }
2959
+ else if (old?.status !== 'claimed')
2960
+ throw new ControlPlaneStoreError('conflict', 'regression was not claimed or is settled');
2961
+ else
2962
+ this.#database.prepare(`UPDATE source_revision_regressions SET status='verified',certificate_json=?,certificate_digest=?,updated_at=?
2963
+ WHERE plan_id=? AND status='claimed'`).run(serialized, digest, this.#now(), plan.id);
2964
+ this.#database.exec('COMMIT');
2965
+ }
2966
+ catch (error) {
2967
+ this.#database.exec('ROLLBACK');
2968
+ throw error;
2969
+ }
2970
+ }
2971
+ getRevisionRegression(planId) {
2972
+ const row = this.#database.prepare('SELECT status,certificate_json,certificate_digest FROM source_revision_regressions WHERE plan_id=?')
2973
+ .get(planId);
2974
+ if (!row || row.status !== 'verified')
2975
+ return undefined;
2976
+ let certificate;
2977
+ try {
2978
+ const parsed = JSON.parse(row.certificate_json);
2979
+ validatePluginRevisionRegressionCertificate(parsed);
2980
+ certificate = parsed;
2981
+ }
2982
+ catch {
2983
+ throw new ControlPlaneStoreError('invalid-state', 'stored revision regression certificate is corrupt');
2984
+ }
2985
+ if (certificate.plan.id !== planId || controlPlaneDigest(certificate) !== row.certificate_digest) {
2986
+ throw new ControlPlaneStoreError('invalid-state', 'stored revision regression certificate digest changed');
2987
+ }
2988
+ return certificate;
2989
+ }
2990
+ getRevisionRegressionStatus(planId) {
2991
+ const row = this.#database.prepare('SELECT status FROM source_revision_regressions WHERE plan_id=?')
2992
+ .get(planId);
2993
+ return row?.status;
2994
+ }
2995
+ /** Content-free diagnostic projection; caller must hold current owner/source fence. */
2996
+ inspectRevisionRegressionRecord(planId) {
2997
+ const row = this.#database.prepare('SELECT status,reason,updated_at FROM source_revision_regressions WHERE plan_id=?')
2998
+ .get(planId);
2999
+ if (!row)
3000
+ return undefined;
3001
+ if (!['claimed', 'verified', 'unknown', 'rejected'].includes(row.status)
3002
+ || !Number.isSafeInteger(row.updated_at) || row.updated_at < 1
3003
+ || (row.reason !== null && !/^[a-z][a-z0-9-]{0,79}$/u.test(row.reason))) {
3004
+ throw new ControlPlaneStoreError('invalid-state', 'revision regression diagnostic record is corrupt');
3005
+ }
3006
+ return { status: row.status, ...(row.reason === null ? {} : { reason: row.reason }), updatedAt: row.updated_at };
3007
+ }
3008
+ claimRevisionRegression(planId) {
3009
+ const job = this.getPreparedRevisionJob(planId);
3010
+ const pinned = job.intent.revision?.growthRun?.revisionRegressionAcceptance;
3011
+ if (!pinned || this.#now() >= pinned.expiresAt) {
3012
+ throw new ControlPlaneStoreError('invalid-state', 'revision regression requires a current pre-author policy binding');
3013
+ }
3014
+ this.#database.exec('BEGIN IMMEDIATE');
3015
+ try {
3016
+ const count = this.#database.prepare('SELECT count(*) AS n FROM source_revision_regressions').get();
3017
+ if (count.n >= 256)
3018
+ throw new ControlPlaneStoreError('conflict', 'revision regression record limit reached');
3019
+ const now = this.#now();
3020
+ this.#database.prepare(`INSERT INTO source_revision_regressions
3021
+ (plan_id,status,certificate_json,certificate_digest,reason,created_at,updated_at) VALUES (?,'claimed',NULL,NULL,NULL,?,?)`)
3022
+ .run(planId, now, now);
3023
+ this.#database.exec('COMMIT');
3024
+ }
3025
+ catch (error) {
3026
+ this.#database.exec('ROLLBACK');
3027
+ throw error;
3028
+ }
3029
+ }
3030
+ settleRevisionRegression(planId, status, reason) {
3031
+ if (typeof reason !== 'string' || !/^[a-z][a-z0-9-]{0,79}$/u.test(reason)) {
3032
+ throw new ControlPlaneStoreError('invalid-input', 'revision regression reason is invalid');
3033
+ }
3034
+ const result = this.#database.prepare(`UPDATE source_revision_regressions SET status=?,reason=?,updated_at=?
3035
+ WHERE plan_id=? AND status='claimed'`).run(status, reason, this.#now(), planId);
3036
+ if (Number(result.changes) !== 1)
3037
+ throw new ControlPlaneStoreError('conflict', 'revision regression claim is not current');
3038
+ }
3039
+ readRevisionSource(planId) {
3040
+ const certificate = this.getRevisionVerification(planId);
3041
+ if (!certificate)
3042
+ return undefined;
3043
+ const row = this.#database.prepare('SELECT source_json FROM source_revision_sources WHERE plan_id=?').get(planId);
3044
+ if (!row || Buffer.byteLength(row.source_json) > 2_097_152)
3045
+ throw new ControlPlaneStoreError('invalid-state', 'revision source archive is absent or oversized');
3046
+ const source = JSON.parse(row.source_json);
3047
+ validateCreationCapabilitySource(source, certificate);
3048
+ const plan = this.getSourcePlan(planId);
3049
+ if (source.baseCommit !== plan.baseCommit || controlPlaneDigest(source.scope) !== controlPlaneDigest(plan.scope))
3050
+ throw new ControlPlaneStoreError('invalid-state', 'revision source archive scope changed');
3051
+ return source;
3052
+ }
3053
+ /** Reclaim only orphaned bytes or bytes referenced solely by expired plans. */
3054
+ deleteExpiredPreparedSourceArtifacts(now) {
3055
+ if (!Number.isSafeInteger(now) || now < 0)
3056
+ throw new ControlPlaneStoreError('invalid-input', 'artifact expiry time is invalid');
3057
+ this.#database.exec('BEGIN IMMEDIATE');
3058
+ try {
3059
+ const references = this.#database.prepare('SELECT plan_id, pack_sha256 FROM source_prepared_artifact_refs')
3060
+ .all();
3061
+ for (const reference of references) {
3062
+ const plan = this.getSourcePlan(reference.plan_id);
3063
+ if (plan.mode !== 'prepared-create' && plan.mode !== 'prepared-revise' || plan.preparedEvidence?.pack.sha256 !== reference.pack_sha256) {
3064
+ throw new ControlPlaneStoreError('invalid-state', 'prepared source artifact reference is corrupt');
3065
+ }
3066
+ if (plan.status === 'expired' && plan.expiresAt < now) {
3067
+ this.#database.prepare('DELETE FROM source_prepared_artifact_refs WHERE plan_id = ?').run(plan.id);
3068
+ }
3069
+ }
3070
+ const artifacts = this.#database.prepare(`SELECT a.pack_sha256 FROM source_prepared_artifacts a
3071
+ WHERE NOT EXISTS (SELECT 1 FROM source_prepared_artifact_refs r WHERE r.pack_sha256 = a.pack_sha256)`)
3072
+ .all();
3073
+ let deleted = 0;
3074
+ for (const artifact of artifacts) {
3075
+ const result = this.#database.prepare('DELETE FROM source_prepared_artifacts WHERE pack_sha256 = ?')
3076
+ .run(artifact.pack_sha256);
3077
+ deleted += Number(result.changes);
3078
+ }
3079
+ this.#database.exec('COMMIT');
3080
+ return deleted;
3081
+ }
3082
+ catch (error) {
3083
+ this.#database.exec('ROLLBACK');
3084
+ throw error;
3085
+ }
3086
+ }
3087
+ /** Durable, structurally bound applied merge history; receipt signatures are checked by the baseline resolver. */
3088
+ getSourceBaselineHistory(repository) {
3089
+ if (!isAbsolute(repository) || resolve(repository) !== repository)
3090
+ throw new ControlPlaneStoreError('invalid-input', 'source repository path is invalid');
3091
+ // A SAVEPOINT provides one SQLite read snapshot, and also works inside the
3092
+ // start-release BEGIN IMMEDIATE writer without nesting another BEGIN.
3093
+ this.#database.exec('SAVEPOINT source_baseline_history');
3094
+ try {
3095
+ const history = this.#readSourceBaselineHistory(repository);
3096
+ this.#database.exec('RELEASE source_baseline_history');
3097
+ return history;
3098
+ }
3099
+ catch (error) {
3100
+ this.#database.exec('ROLLBACK TO source_baseline_history');
3101
+ this.#database.exec('RELEASE source_baseline_history');
3102
+ throw error;
3103
+ }
3104
+ }
3105
+ /** Must run inside an open read transaction/SAVEPOINT so the caller sees one snapshot. */
3106
+ #readSourceBaselineHistory(repository) {
3107
+ const rows = this.#database.prepare(`SELECT * FROM source_plans WHERE repository = ?
3108
+ AND (release_id IS NOT NULL OR status IN ('release-complete','release-failed'))
3109
+ ORDER BY created_at, id LIMIT 1001`).all(repository);
3110
+ if (rows.length > 1000)
3111
+ throw new ControlPlaneStoreError('invalid-state', 'source release history exceeds bound');
3112
+ const history = [];
3113
+ for (const row of rows) {
3114
+ const plan = sourceFromRow(row);
3115
+ if (plan.release === undefined)
3116
+ throw new ControlPlaneStoreError('invalid-state', 'started source release lacks release identity');
3117
+ if (plan.status !== 'release-complete' && plan.status !== 'release-failed') {
3118
+ throw new ControlPlaneStoreError('conflict', 'source repository has an unfinished release');
3119
+ }
3120
+ const merges = this.#database.prepare(`SELECT * FROM source_release_operations WHERE plan_id = ? AND phase = 'merge'
3121
+ AND status = 'applied' LIMIT 2`).all(plan.id);
3122
+ if (plan.status === 'release-failed') {
3123
+ if (merges.length !== 0)
3124
+ throw new ControlPlaneStoreError('conflict', 'failed source release has unresolved applied remote merge');
3125
+ continue;
3126
+ }
3127
+ if (merges.length !== 1)
3128
+ throw new ControlPlaneStoreError('invalid-state', 'completed source release lacks one applied merge');
3129
+ const operation = sourceReleaseOperationFromRow(merges[0]);
3130
+ const receipt = operation.receipt;
3131
+ if (operation.status !== 'applied' || operation.phase !== 'merge' || operation.request.phase !== 'merge' || operation.planId !== plan.id
3132
+ || operation.request.plan.digest !== plan.digest || operation.request.plan.revision > plan.revision
3133
+ || operation.request.release.id !== plan.release.id || operation.request.release.fence !== plan.release.fence
3134
+ || receipt?.outcome !== 'passed' || receipt.evidence.kind !== 'merge'
3135
+ || receipt.planDigest !== plan.digest || receipt.evidence.targetBranch !== plan.releaseAuthorization?.releasePolicy.targetBranch
3136
+ || receipt.evidence.reviewedHeadCommit !== operation.request.input.headCommit
3137
+ || receipt.evidence.reviewEvidenceDigest !== operation.request.input.reviewEvidenceDigest
3138
+ || receipt.evidence.reviewId !== operation.request.input.reviewId
3139
+ || receipt.evidence.prId !== operation.request.input.prId) {
3140
+ throw new ControlPlaneStoreError('invalid-state', 'completed source release merge is not bound to current release');
3141
+ }
3142
+ history.push({ plan, operation });
3143
+ }
3144
+ return history;
3145
+ }
3146
+ /** Release edges and maintenance edges in one SQLite snapshot, for preflight consumers that must compare both. */
3147
+ readSourceMaintenanceState(repository) {
3148
+ if (!isAbsolute(repository) || resolve(repository) !== repository)
3149
+ throw new ControlPlaneStoreError('invalid-input', 'source repository path is invalid');
3150
+ this.#database.exec('SAVEPOINT source_maintenance_state');
3151
+ try {
3152
+ const unsettledJob = this.#database.prepare(`SELECT id FROM source_jobs WHERE json_extract(intent_json,'$.repository')=?
3153
+ AND status IN ('queued','running','unknown') LIMIT 1`).get(repository);
3154
+ const unsettledPlan = this.#database.prepare(`SELECT id FROM source_plans WHERE repository=?
3155
+ AND status NOT IN ('expired','local-checks-failed','release-failed','release-complete') LIMIT 1`).get(repository);
3156
+ if (unsettledJob || unsettledPlan)
3157
+ throw new ControlPlaneStoreError('conflict', 'source maintenance has unsettled source work');
3158
+ const history = this.#readSourceBaselineHistory(repository);
3159
+ const records = this.getSourceMaintenanceRecords(repository);
3160
+ this.#database.exec('RELEASE source_maintenance_state');
3161
+ return { history, records };
3162
+ }
3163
+ catch (error) {
3164
+ this.#database.exec('ROLLBACK TO source_maintenance_state');
3165
+ this.#database.exec('RELEASE source_maintenance_state');
3166
+ throw error;
3167
+ }
3168
+ }
3169
+ getSourceMaintenanceRecords(repository) {
3170
+ if (!isAbsolute(repository) || resolve(repository) !== repository)
3171
+ throw new ControlPlaneStoreError('invalid-input', 'source repository path is invalid');
3172
+ const rows = this.#database.prepare('SELECT sequence,host_plan_id,record_json,record_digest FROM source_maintenance WHERE repository=? ORDER BY sequence LIMIT 257')
3173
+ .all(repository);
3174
+ if (rows.length > 256)
3175
+ throw new ControlPlaneStoreError('invalid-state', 'source maintenance history exceeds bound');
3176
+ return rows.map((row, index) => {
3177
+ const record = parseSourceMaintenanceRecord(JSON.parse(row.record_json));
3178
+ if (record.repository !== repository || record.sequence !== index + 1 || record.sequence !== row.sequence
3179
+ || record.host?.planId !== (row.host_plan_id ?? undefined)
3180
+ || sourceMaintenanceDigest(record) !== row.record_digest) {
3181
+ throw new ControlPlaneStoreError('invalid-state', 'source maintenance stored record changed');
3182
+ }
3183
+ return record;
3184
+ });
3185
+ }
3186
+ /** Offline owner transaction only. No service/model-facing signing path exists. */
3187
+ async appendSourceMaintenance(recordInput, input) {
3188
+ const record = parseSourceMaintenanceRecord(recordInput), { trust, baseline } = input;
3189
+ validateSourceBaselineConfig(baseline);
3190
+ const key = resolveTrustKey(trust, 'host-attestation', record.authority, record.keyId);
3191
+ if (record.installationId !== trust.installationId || controlPlaneDigest(record.ledger) !== controlPlaneDigest(trust.ledger)
3192
+ || record.repository !== recordInput.repository || controlPlaneDigest(record.baseline) !== controlPlaneDigest(baseline)
3193
+ || !sameSourceMaintenancePublicKey(record.publicKeyPem, key.publicKeyPem) || record.issuedAt > this.#now()) {
3194
+ throw new ControlPlaneStoreError('invalid-input', 'source maintenance owner binding changed');
3195
+ }
3196
+ const verifiedHistory = this.getSourceBaselineHistory(record.repository);
3197
+ const verifiedMaintenance = this.getSourceMaintenanceRecords(record.repository);
3198
+ const authorityDigest = controlPlaneDigest({ trust, baseline });
3199
+ await verifySourceBaselineHistory({ repository: record.repository, config: baseline, trust,
3200
+ history: verifiedHistory, maintenance: verifiedMaintenance, signal: new AbortController().signal });
3201
+ const verifiedSnapshot = controlPlaneDigest({ history: verifiedHistory, maintenance: verifiedMaintenance });
3202
+ this.#database.exec('SAVEPOINT append_source_maintenance');
3203
+ try {
3204
+ this.#database.exec('UPDATE source_maintenance SET sequence=sequence WHERE 0');
3205
+ const records = this.getSourceMaintenanceRecords(record.repository);
3206
+ const history = this.getSourceBaselineHistory(record.repository);
3207
+ if (controlPlaneDigest({ trust, baseline }) !== authorityDigest) {
3208
+ throw new ControlPlaneStoreError('conflict', 'source maintenance trust or baseline changed before append');
3209
+ }
3210
+ if (controlPlaneDigest({ history, maintenance: records }) !== verifiedSnapshot) {
3211
+ throw new ControlPlaneStoreError('conflict', 'source maintenance signed history changed before append');
3212
+ }
3213
+ const priorTransaction = records.find(item => item.transactionId === record.transactionId);
3214
+ if (priorTransaction) {
3215
+ if (sourceMaintenanceDigest(priorTransaction) !== sourceMaintenanceDigest(record)) {
3216
+ throw new ControlPlaneStoreError('conflict', 'source maintenance transaction changed');
3217
+ }
3218
+ this.#database.exec('RELEASE append_source_maintenance');
3219
+ return;
3220
+ }
3221
+ verifySourceMaintenanceRecords([...records, record], { installationId: trust.installationId,
3222
+ ledger: trust.ledger, repository: record.repository, baseline, hostIdentity: key });
3223
+ const unsettledJob = this.#database.prepare(`SELECT id FROM source_jobs WHERE json_extract(intent_json,'$.repository')=?
3224
+ AND status IN ('queued','running','unknown') LIMIT 1`).get(record.repository);
3225
+ const unsettledPlan = this.#database.prepare(`SELECT id FROM source_plans WHERE repository=?
3226
+ AND status NOT IN ('expired','local-checks-failed','release-failed','release-complete') LIMIT 1`).get(record.repository);
3227
+ if (unsettledJob || unsettledPlan)
3228
+ throw new ControlPlaneStoreError('conflict', 'source maintenance has unsettled source work');
3229
+ const activationState = this.readSourceMaintenanceActivationState(trust.dshHome);
3230
+ if (activationState.kind === 'unsettled') {
3231
+ throw new ControlPlaneStoreError('conflict', 'source maintenance activation is unsettled');
3232
+ }
3233
+ if (activationState.kind === 'watched') {
3234
+ const context = readCurrentRuntimeEpochDeployment(this.#database, this.getPlan(activationState.planId).target.profilePath);
3235
+ const { plan, readiness } = context, receipt = readiness.receipt;
3236
+ if (!record.host || record.host.planId !== plan.id || record.host.planDigest !== plan.digest
3237
+ || record.host.readinessOperationId !== readiness.operationId || !receipt
3238
+ || record.host.readinessReceiptDigest !== hostMaintenanceDigest(receipt)
3239
+ || receipt.authority !== record.authority || receipt.keyId !== record.keyId) {
3240
+ throw new ControlPlaneStoreError('conflict', 'source maintenance watched Host anchor changed');
3241
+ }
3242
+ const { signature, ...unsigned } = receipt;
3243
+ if (!verify(null, Buffer.from(hostMaintenanceCanonical(unsigned)), createPublicKey(key.publicKeyPem), Buffer.from(signature, 'base64'))) {
3244
+ throw new ControlPlaneStoreError('conflict', 'source maintenance original Host receipt signature changed');
3245
+ }
3246
+ }
3247
+ else if (record.host !== null)
3248
+ throw new ControlPlaneStoreError('conflict', 'source maintenance has no watched Host anchor');
3249
+ const current = sourceBaselineChain(baseline, history, records).at(-1);
3250
+ if (record.previousTip !== current)
3251
+ throw new ControlPlaneStoreError('conflict', 'source maintenance previous tip is stale');
3252
+ sourceBaselineChain(baseline, history, [...records, record]);
3253
+ assertSourceMaintenanceGit(record, input.physicalHome ?? trust.dshHome, trust.dshHome);
3254
+ this.#database.prepare('INSERT INTO source_maintenance VALUES (?,?,?,?,?,?)').run(record.repository, record.sequence, record.transactionId, record.host?.planId ?? null, JSON.stringify(record), sourceMaintenanceDigest(record));
3255
+ this.#database.exec('RELEASE append_source_maintenance');
3256
+ }
3257
+ catch (error) {
3258
+ this.#database.exec('ROLLBACK TO append_source_maintenance; RELEASE append_source_maintenance');
3259
+ throw error;
3260
+ }
3261
+ }
3262
+ /** Seed a previously owner-signed pre-adoption sidecar into a new ledger. */
3263
+ async importSourceMaintenanceRecords(recordInputs, input) {
3264
+ if (recordInputs.length === 0 || recordInputs.length > 256)
3265
+ throw new ControlPlaneStoreError('invalid-input', 'source maintenance import is empty or unbounded');
3266
+ const { trust, baseline } = input;
3267
+ validateSourceBaselineConfig(baseline);
3268
+ const records = recordInputs.map(parseSourceMaintenanceRecord), first = records[0];
3269
+ const key = resolveTrustKey(trust, 'host-attestation', first.authority, first.keyId);
3270
+ verifySourceMaintenanceRecords(records, { installationId: trust.installationId, ledger: trust.ledger,
3271
+ repository: first.repository, baseline, hostIdentity: key });
3272
+ if (records.some(record => record.host !== null))
3273
+ throw new ControlPlaneStoreError('invalid-input', 'pre-adoption source history carries a deployment anchor');
3274
+ sourceBaselineChain(baseline, [], records);
3275
+ const authorityDigest = controlPlaneDigest({ trust, baseline, records });
3276
+ this.#database.exec('SAVEPOINT import_source_maintenance');
3277
+ try {
3278
+ this.#database.exec('UPDATE source_maintenance SET sequence=sequence WHERE 0');
3279
+ if (controlPlaneDigest({ trust, baseline, records }) !== authorityDigest)
3280
+ throw new ControlPlaneStoreError('conflict', 'source import authority changed');
3281
+ const present = this.getSourceMaintenanceRecords(first.repository);
3282
+ if (present.length > records.length || present.some((record, index) => sourceMaintenanceDigest(record) !== sourceMaintenanceDigest(records[index]))) {
3283
+ throw new ControlPlaneStoreError('conflict', 'source import differs from existing ledger history');
3284
+ }
3285
+ if (this.getSourceBaselineHistory(first.repository).length)
3286
+ throw new ControlPlaneStoreError('conflict', 'source import cannot cross a published release');
3287
+ const anyJob = this.#database.prepare(`SELECT id FROM source_jobs WHERE json_extract(intent_json,'$.repository')=? LIMIT 1`)
3288
+ .get(first.repository);
3289
+ const anyPlan = this.#database.prepare('SELECT id FROM source_plans WHERE repository=? LIMIT 1').get(first.repository);
3290
+ const anyActivation = this.#database.prepare('SELECT id FROM activation_plans WHERE dsh_home=? LIMIT 1').get(trust.dshHome);
3291
+ if (anyJob || anyPlan || anyActivation)
3292
+ throw new ControlPlaneStoreError('conflict', 'source import requires an empty owner ledger');
3293
+ for (let index = 0; index < records.length; index++) {
3294
+ assertSourceMaintenanceGit(records[index], input.physicalHome ?? trust.dshHome, trust.dshHome, index === records.length - 1);
3295
+ }
3296
+ for (const record of records.slice(present.length)) {
3297
+ this.#database.prepare('INSERT INTO source_maintenance VALUES (?,?,?,?,?,?)').run(record.repository, record.sequence, record.transactionId, null, JSON.stringify(record), sourceMaintenanceDigest(record));
3298
+ }
3299
+ this.#database.exec('RELEASE import_source_maintenance');
3300
+ }
3301
+ catch (error) {
3302
+ this.#database.exec('ROLLBACK TO import_source_maintenance; RELEASE import_source_maintenance');
3303
+ throw error;
3304
+ }
3305
+ }
1661
3306
  /**
1662
3307
  * Enumerate 'modify' plans for isolated-worktree garbage collection. Only
1663
3308
  * modify plans own control-plane-created worktrees under the state root;
@@ -1671,14 +3316,24 @@ export class ControlPlaneStore {
1671
3316
  .all(filter.expiredBefore);
1672
3317
  return rows.map(sourceFromRow);
1673
3318
  }
3319
+ /** Isolated worktrees owned by the background prepared flows, including owner-granted creation. */
3320
+ listPreparedSourcePlans(filter) {
3321
+ const rows = filter?.expiredBefore === undefined
3322
+ ? this.#database.prepare(`SELECT * FROM source_plans WHERE mode IN ('modify', 'prepared-create', 'prepared-revise') ORDER BY created_at, id`)
3323
+ .all()
3324
+ : this.#database.prepare(`SELECT * FROM source_plans WHERE mode IN ('modify', 'prepared-create', 'prepared-revise') AND expires_at < ? ORDER BY created_at, id`)
3325
+ .all(filter.expiredBefore);
3326
+ return rows.map(sourceFromRow);
3327
+ }
1674
3328
  /** Retire an expired prepared plan before removing its worktree. The CAS
1675
3329
  * fences owner approval/review, and releases only this plan's gap claim. */
1676
3330
  expirePreparedSourcePlan(input) {
1677
3331
  if (!Number.isSafeInteger(input.now) || input.now < 0)
1678
3332
  throw new ControlPlaneStoreError('invalid-input', 'expiry time is invalid');
1679
3333
  const plan = this.getSourcePlan(input.planId);
1680
- if (plan.mode !== 'modify')
1681
- throw new ControlPlaneStoreError('invalid-state', 'only prepared modify plans may expire here');
3334
+ if (plan.mode !== 'modify' && plan.mode !== 'prepared-create' && plan.mode !== 'prepared-revise') {
3335
+ throw new ControlPlaneStoreError('invalid-state', 'only prepared source plans may expire here');
3336
+ }
1682
3337
  const expiryDigest = controlPlaneDigest({ planId: plan.id, planDigest: plan.digest, expiresAt: plan.expiresAt });
1683
3338
  if (plan.status === 'expired') {
1684
3339
  const receipt = this.#sourcePlanReceipt(`source-expired:${plan.id}`, 'source-expired', expiryDigest, plan.id);
@@ -1689,7 +3344,8 @@ export class ControlPlaneStore {
1689
3344
  this.#database.exec('BEGIN IMMEDIATE');
1690
3345
  try {
1691
3346
  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 < ?`)
3347
+ WHERE id = ? AND revision = ? AND ((mode = 'modify' AND status IN ('pending-approval', 'approved'))
3348
+ OR (mode IN ('prepared-create', 'prepared-revise') AND status = 'pending-approval')) AND expires_at < ?`)
1693
3349
  .run(input.now, plan.id, input.expectedRevision, input.now);
1694
3350
  if (Number(retired.changes) !== 1)
1695
3351
  throw new ControlPlaneStoreError('conflict', 'prepared plan changed or is not expired');
@@ -1714,6 +3370,9 @@ export class ControlPlaneStore {
1714
3370
  return this.#approvePlan('activation', input);
1715
3371
  }
1716
3372
  async approveSource(input) {
3373
+ if (['prepared-create', 'prepared-revise'].includes(this.getSourcePlan(input.planId).mode)) {
3374
+ throw new ControlPlaneStoreError('invalid-state', 'prepared creation requires its own owner creation authority');
3375
+ }
1717
3376
  return this.#approvePlan('source', input);
1718
3377
  }
1719
3378
  async #approvePlan(kind, input) {
@@ -1794,7 +3453,7 @@ export class ControlPlaneStore {
1794
3453
  const row = this.#database.prepare('SELECT * FROM activation_plans WHERE id = ?').get(input.planId);
1795
3454
  if (row === undefined)
1796
3455
  throw new ControlPlaneStoreError('not-found', 'activation plan not found');
1797
- const plan = activationFromRow(row);
3456
+ const plan = effectiveActivationFromRow(this.#database, row);
1798
3457
  if (plan.revision !== input.expectedRevision)
1799
3458
  throw new ControlPlaneStoreError('conflict', 'activation plan revision conflict');
1800
3459
  if (row.approval_receipt_json === null || plan.approval === undefined) {
@@ -1817,7 +3476,7 @@ export class ControlPlaneStore {
1817
3476
  if (now > plan.expiresAt)
1818
3477
  throw new ControlPlaneStoreError('expired', 'activation plan expired before its first claim');
1819
3478
  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',
3479
+ 'staging', 'awaiting-reload', 'awaiting-readiness', 'awaiting-live-tasks', 'awaiting-effect-blocked-replay', 'awaiting-shadow',
1821
3480
  'awaiting-canary', 'awaiting-soak', 'awaiting-health', 'commit-pending', 'rollback-pending') LIMIT 1`).get(plan.target.profilePath, plan.id);
1822
3481
  if (targetOwner !== undefined)
1823
3482
  throw new ControlPlaneStoreError('conflict', 'target profile already has an active activation');
@@ -1830,7 +3489,7 @@ export class ControlPlaneStore {
1830
3489
  const activationId = row.activation_id ?? `activation-${randomUUID()}`;
1831
3490
  const status = plan.status === 'approved' ? 'staging' : plan.status;
1832
3491
  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);
3492
+ 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
3493
  if (Number(result.changes) !== 1)
1835
3494
  throw new ControlPlaneStoreError('conflict', 'activation plan changed while being claimed');
1836
3495
  const claimed = this.getPlan(plan.id);
@@ -1863,7 +3522,7 @@ export class ControlPlaneStore {
1863
3522
  const row = this.#database.prepare('SELECT * FROM activation_plans WHERE id = ?').get(input.planId);
1864
3523
  if (row === undefined)
1865
3524
  throw new ControlPlaneStoreError('not-found', 'activation plan not found');
1866
- const plan = activationFromRow(row);
3525
+ const plan = effectiveActivationFromRow(this.#database, row);
1867
3526
  if (plan.revision !== input.expectedRevision || plan.activation?.fence !== input.fence || plan.status !== 'staging'
1868
3527
  || Number(row.activation_lease_until ?? 0) < now)
1869
3528
  throw new ControlPlaneStoreError('conflict', 'activation lost its claim before recording the target baseline');
@@ -1905,7 +3564,7 @@ export class ControlPlaneStore {
1905
3564
  const row = this.#database.prepare('SELECT * FROM activation_plans WHERE id = ?').get(input.planId);
1906
3565
  if (row === undefined)
1907
3566
  throw new ControlPlaneStoreError('not-found', 'activation plan not found');
1908
- const plan = activationFromRow(row);
3567
+ const plan = effectiveActivationFromRow(this.#database, row);
1909
3568
  if (plan.revision !== input.expectedRevision || plan.activation?.fence !== input.fence || plan.status !== 'commit-pending'
1910
3569
  || Number(row.activation_lease_until ?? 0) < now) {
1911
3570
  throw new ControlPlaneStoreError('conflict', 'activation lost its claim before recording the installed baseline');
@@ -1961,7 +3620,8 @@ export class ControlPlaneStore {
1961
3620
  catch {
1962
3621
  throw new ControlPlaneStoreError('invalid-state', 'stored installed activation baseline is corrupt');
1963
3622
  }
1964
- return activationCoreFiles(value, plan.target.profilePath, 'stored installed activation baseline');
3623
+ const original = activationCoreFiles(value, plan.target.profilePath, 'stored installed activation baseline');
3624
+ return readHostMaintenanceContext(this.#database, plan.id).records.at(-1)?.after.profileFiles ?? original;
1965
3625
  }
1966
3626
  /** Move a signed closed deployment watch into the existing physical rollback lifecycle. */
1967
3627
  beginPostActivationRollback(input) {
@@ -1971,7 +3631,7 @@ export class ControlPlaneStore {
1971
3631
  const row = this.#database.prepare('SELECT * FROM activation_plans WHERE id = ?').get(input.planId);
1972
3632
  if (row === undefined)
1973
3633
  throw new ControlPlaneStoreError('not-found', 'activation plan not found');
1974
- const plan = activationFromRow(row);
3634
+ const plan = effectiveActivationFromRow(this.#database, row);
1975
3635
  if (plan.revision !== input.expectedRevision)
1976
3636
  throw new ControlPlaneStoreError('conflict', 'activation plan revision conflict');
1977
3637
  // A restart or retry must retain the original rollback identity and never
@@ -1997,7 +3657,7 @@ export class ControlPlaneStore {
1997
3657
  }
1998
3658
  this.getActivationInstalledBaseline(plan.id);
1999
3659
  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',
3660
+ 'staging', 'awaiting-reload', 'awaiting-readiness', 'awaiting-live-tasks', 'awaiting-effect-blocked-replay', 'awaiting-shadow',
2001
3661
  'awaiting-canary', 'awaiting-soak', 'awaiting-health', 'commit-pending', 'rollback-pending') LIMIT 1`)
2002
3662
  .get(plan.target.profilePath, plan.id);
2003
3663
  if (active !== undefined)
@@ -2038,9 +3698,65 @@ export class ControlPlaneStore {
2038
3698
  ORDER BY older.exposure_order`).all(current.target.profilePath, current.id, checkpoint.exposure_order);
2039
3699
  return rows.map(activationFromRow);
2040
3700
  }
3701
+ /** Freeze exact physical pins before a signed deployment becomes Host-visible. */
3702
+ recordActivationHostInputWitness(input) {
3703
+ return this.#withActivationWrite(input.planId, () => {
3704
+ const plan = this.getPlan(input.planId);
3705
+ const row = this.#database.prepare('SELECT activation_lease_until FROM activation_plans WHERE id=?').get(plan.id);
3706
+ const inputs = plan.dossier.hostDeploymentInputs;
3707
+ if (!inputs)
3708
+ throw new ControlPlaneStoreError('invalid-state', 'plan has no signed Host deployment inputs');
3709
+ if (plan.status !== 'staging' || plan.revision !== input.expectedRevision || plan.activation?.fence !== input.fence
3710
+ || (row.activation_lease_until ?? 0) < this.#now() || plan.activation.hostRecoveryRequired
3711
+ || plan.activation.targetOriginallyExisted === undefined || plan.activation.targetBaselineFiles === undefined) {
3712
+ throw new ControlPlaneStoreError('conflict', 'Host input witness lost its staging claim or target baseline');
3713
+ }
3714
+ let profileFiles;
3715
+ let deploymentFiles;
3716
+ let baselineDeploymentFiles;
3717
+ try {
3718
+ profileFiles = activationCoreFiles(input.profileFiles, plan.target.profilePath, 'Host input witness core files');
3719
+ if (profileFiles.some(file => file.sha256 === null))
3720
+ throw new Error('missing core file');
3721
+ deploymentFiles = hostInputPins(input.deploymentFiles, inputs, plan.target.profilePath, 'Host input witness deployment files');
3722
+ baselineDeploymentFiles = plan.activation.targetOriginallyExisted
3723
+ ? hostInputPins(input.baselineDeploymentFiles, inputs, plan.target.profilePath, 'Host input witness baseline files')
3724
+ : input.baselineDeploymentFiles.length === 0 ? [] : (() => { throw new Error('unexpected baseline files'); })();
3725
+ }
3726
+ catch {
3727
+ throw new ControlPlaneStoreError('invalid-input', 'Host input witness pins do not match the signed profile deployment');
3728
+ }
3729
+ const existing = hostInputWitnessFromRow(this.#database, plan);
3730
+ if (existing) {
3731
+ if (controlPlaneDigest(existing.profileFiles) !== controlPlaneDigest(profileFiles)
3732
+ || controlPlaneDigest(existing.deploymentFiles) !== controlPlaneDigest(deploymentFiles)
3733
+ || controlPlaneDigest(existing.baselineDeploymentFiles) !== controlPlaneDigest(baselineDeploymentFiles)) {
3734
+ throw new ControlPlaneStoreError('conflict', 'Host input witness is immutable');
3735
+ }
3736
+ return plan;
3737
+ }
3738
+ const unsigned = { schemaVersion: 1, kind: 'dsh-host-input-witness', planId: plan.id,
3739
+ planDigest: plan.digest, activationId: plan.activation.id, fence: input.fence, createdAt: this.#now(),
3740
+ inputs, profileFiles, deploymentFiles, baselineDeploymentFiles };
3741
+ const witness = { ...unsigned, digest: controlPlaneDigest(unsigned) };
3742
+ this.#database.prepare('INSERT INTO activation_host_input_witnesses (plan_id,witness_json,witness_digest) VALUES (?,?,?)')
3743
+ .run(plan.id, JSON.stringify(witness), controlPlaneDigest(witness));
3744
+ return plan;
3745
+ });
3746
+ }
3747
+ getActivationHostInputWitness(planId) {
3748
+ return hostInputWitnessFromRow(this.#database, this.getPlan(planId));
3749
+ }
2041
3750
  /** Freeze recovery obligation before the profile can become Host-visible. */
2042
3751
  markActivationHostExposure(input) {
2043
3752
  return this.#withActivationWrite(input.planId, () => {
3753
+ const before = this.getPlan(input.planId);
3754
+ if (before.dossier.hostDeploymentInputs) {
3755
+ const witness = hostInputWitnessFromRow(this.#database, before);
3756
+ if (!witness || witness.fence !== input.fence || before.revision !== input.expectedRevision) {
3757
+ throw new ControlPlaneStoreError('conflict', 'Host exposure requires an exact staged input witness');
3758
+ }
3759
+ }
2044
3760
  const now = this.#now();
2045
3761
  const result = this.#database.prepare(`UPDATE activation_plans SET host_recovery_required = 1, updated_at = ?
2046
3762
  WHERE id = ? AND revision = ? AND activation_fence = ? AND status = 'staging'
@@ -2048,7 +3764,19 @@ export class ControlPlaneStore {
2048
3764
  AND activation_lease_until >= ?`).run(now, input.planId, input.expectedRevision, input.fence, now);
2049
3765
  if (Number(result.changes) !== 1)
2050
3766
  throw new ControlPlaneStoreError('conflict', 'Host exposure lost its claim or original baseline');
2051
- return this.getPlan(input.planId);
3767
+ const plan = this.getPlan(input.planId);
3768
+ if (plan.dossier.liveQualification) {
3769
+ const handoff = this.getAdoptionHandoff(plan.id);
3770
+ const deadline = Math.min(now + plan.dossier.liveQualification.maximumWindowMs, plan.expiresAt, plan.approval.expiresAt, handoff?.expiresAt ?? 0);
3771
+ if (deadline <= now)
3772
+ throw new ControlPlaneStoreError('expired', 'live qualification authorization expired before exposure');
3773
+ this.#database.prepare(`INSERT INTO live_qualification_windows (plan_id,activation_id,fence,started_at,deadline_at)
3774
+ VALUES (?,?,?,?,?) ON CONFLICT(plan_id) DO NOTHING`).run(plan.id, plan.activation.id, plan.activation.fence, now, deadline);
3775
+ const prior = this.#database.prepare('SELECT activation_id,fence FROM live_qualification_windows WHERE plan_id=?').get(plan.id);
3776
+ if (prior?.activation_id !== plan.activation.id || prior.fence !== plan.activation.fence)
3777
+ throw new ControlPlaneStoreError('conflict', 'live qualification exposure cannot be replayed');
3778
+ }
3779
+ return plan;
2052
3780
  });
2053
3781
  }
2054
3782
  markRollbackProfileRestored(input) {
@@ -2082,7 +3810,7 @@ export class ControlPlaneStore {
2082
3810
  const row = this.#database.prepare('SELECT * FROM activation_plans WHERE id = ?').get(input.planId);
2083
3811
  if (row === undefined)
2084
3812
  throw new ControlPlaneStoreError('not-found', 'activation plan not found');
2085
- const plan = activationFromRow(row);
3813
+ const plan = effectiveActivationFromRow(this.#database, row);
2086
3814
  if (plan.revision !== input.expectedRevision || plan.activation?.fence !== input.fence || plan.status !== input.status
2087
3815
  || (requireLiveLease && Number(row.activation_lease_until ?? 0) < this.#now()))
2088
3816
  throw new ControlPlaneStoreError('conflict', 'activation no longer owns the filesystem fence');
@@ -2131,6 +3859,177 @@ export class ControlPlaneStore {
2131
3859
  throw new ControlPlaneStoreError('conflict', 'activation no longer owns the exact revision/fence/status');
2132
3860
  return plan;
2133
3861
  }
3862
+ currentRuntimeEpochDeployment(profilePath) {
3863
+ return readCurrentRuntimeEpochDeployment(this.#database, profilePath);
3864
+ }
3865
+ /**
3866
+ * Classify activation state for a Home in one read snapshot, using the exact
3867
+ * watch/plan queries that gate {@link appendSourceMaintenance}. Preflight
3868
+ * producers use it to distinguish a genuinely unanchored ledger (`none`) from
3869
+ * a watched deployment that must bind its readiness receipt (`watched`) and
3870
+ * from any conflict that must abort rather than silently degrade (`unsettled`).
3871
+ */
3872
+ readSourceMaintenanceActivationState(dshHome) {
3873
+ if (!isAbsolute(dshHome) || resolve(dshHome) !== dshHome) {
3874
+ throw new ControlPlaneStoreError('invalid-input', 'source maintenance Home path is invalid');
3875
+ }
3876
+ this.#database.exec('SAVEPOINT source_maintenance_activation_state');
3877
+ try {
3878
+ const watching = this.#database.prepare(`SELECT plan.id FROM activation_plans plan JOIN activation_watch watch ON watch.plan_id=plan.id
3879
+ WHERE plan.dsh_home=? AND watch.state='watching' LIMIT 2`).all(dshHome);
3880
+ const active = this.#database.prepare(`SELECT id FROM activation_plans WHERE dsh_home=?
3881
+ AND status NOT IN ('rolled-back','activated') LIMIT 1`).get(dshHome);
3882
+ const activated = this.#database.prepare(`SELECT plan.id,plan.target_path,watch.state,
3883
+ checkpoint.exposure_order,checkpoint.successful_order FROM activation_plans plan
3884
+ LEFT JOIN activation_watch watch ON watch.plan_id=plan.id
3885
+ LEFT JOIN activation_deployment_checkpoints checkpoint ON checkpoint.plan_id=plan.id
3886
+ WHERE plan.dsh_home=? AND plan.status='activated' LIMIT 1001`).all(dshHome);
3887
+ if (activated.length > 1000)
3888
+ throw new ControlPlaneStoreError('invalid-state', 'source maintenance activation history exceeds bound');
3889
+ let result = watching.length > 1 || active || watching.length === 0 && activated.length !== 0
3890
+ || watching.length === 1 && !activated.some(row => row.id === watching[0].id)
3891
+ ? { kind: 'unsettled' }
3892
+ : watching.length === 1
3893
+ ? { kind: 'watched', planId: watching[0].id }
3894
+ : { kind: 'none' };
3895
+ if (result.kind === 'watched') {
3896
+ const plan = this.getPlan(result.planId), watch = this.getActivationWatch(result.planId);
3897
+ const assertBinding = (candidate, boundWatch) => {
3898
+ if (boundWatch.activationId !== candidate.activation?.id || boundWatch.fence !== candidate.activation.fence
3899
+ || boundWatch.exact.package !== candidate.candidate.package || boundWatch.exact.version !== candidate.candidate.version
3900
+ || boundWatch.exact.integrity !== candidate.candidate.integrity) {
3901
+ throw new ControlPlaneStoreError('invalid-state', 'source maintenance activation watch binding is corrupt');
3902
+ }
3903
+ };
3904
+ assertBinding(plan, watch);
3905
+ const current = activated.find(row => row.id === plan.id);
3906
+ const latest = this.#database.prepare(`SELECT checkpoint.plan_id FROM activation_deployment_checkpoints checkpoint
3907
+ JOIN activation_plans plan ON plan.id=checkpoint.plan_id WHERE plan.target_path=?
3908
+ ORDER BY checkpoint.exposure_order DESC LIMIT 1`).get(plan.target.profilePath);
3909
+ if (current.exposure_order === null || current.successful_order !== current.exposure_order || latest?.plan_id !== plan.id) {
3910
+ result = { kind: 'unsettled' };
3911
+ }
3912
+ else
3913
+ for (const older of activated) {
3914
+ if (older.id === plan.id)
3915
+ continue;
3916
+ // Closed watches retain activated status after a successor takes over.
3917
+ // Only a proved older success on this same profile is retired history.
3918
+ if (older.target_path !== plan.target.profilePath || !['closed-regressed', 'closed-retracted'].includes(older.state ?? '')
3919
+ || older.exposure_order === null || older.successful_order !== older.exposure_order
3920
+ || older.exposure_order >= current.exposure_order) {
3921
+ result = { kind: 'unsettled' };
3922
+ break;
3923
+ }
3924
+ assertBinding(this.getPlan(older.id), this.getActivationWatch(older.id));
3925
+ }
3926
+ }
3927
+ this.#database.exec('RELEASE source_maintenance_activation_state');
3928
+ return result;
3929
+ }
3930
+ catch (error) {
3931
+ this.#database.exec('ROLLBACK TO source_maintenance_activation_state; RELEASE source_maintenance_activation_state');
3932
+ throw error;
3933
+ }
3934
+ }
3935
+ prepareRuntimeEpoch(input) {
3936
+ const runtimeDigest = runtimeIdentityDigest(input.runtime);
3937
+ if (input.runtime.observedAt > this.#now() || input.runtime.observedAt < this.#now() - 30_000
3938
+ || input.runtime.entries.some(entry => !entry.active))
3939
+ throw new ControlPlaneStoreError('invalid-input', 'runtime epoch needs a fresh active runtime');
3940
+ const current = readCurrentRuntimeEpochDeployment(this.#database, input.runtime.profilePath);
3941
+ return this.#withActivationWrite(current.plan.id, () => {
3942
+ const context = readCurrentRuntimeEpochDeployment(this.#database, input.runtime.profilePath), { plan, readiness } = context;
3943
+ const previous = this.#database.prepare(`SELECT * FROM deployment_runtime_epochs WHERE plan_id=? ORDER BY sequence DESC LIMIT 1`)
3944
+ .get(plan.id);
3945
+ if (previous) {
3946
+ const record = runtimeEpochFromRow(previous);
3947
+ if (record.request.runtimeIdentityDigest === runtimeDigest && controlPlaneDigest(record.request.issuer) === controlPlaneDigest(input.issuer)
3948
+ && record.status !== 'stale'
3949
+ && (record.status === 'applied' || this.#now() < record.request.requestedAt + record.request.receiptTtlMs)) {
3950
+ return record;
3951
+ }
3952
+ // An expired read-only observation may be replaced, never relabelled or replayed as a new proof.
3953
+ this.#database.prepare("UPDATE deployment_runtime_epochs SET status='stale' WHERE plan_id=? AND status IN ('pending','claimed')").run(plan.id);
3954
+ }
3955
+ const sequence = Number(this.#database.prepare('SELECT max(sequence) AS sequence FROM deployment_runtime_epochs WHERE plan_id=?').get(plan.id)?.sequence ?? 0) + 1;
3956
+ const request = parseRuntimeEpochRequest({ schemaVersion: 1, kind: 'dsh-runtime-epoch-request', operationId: `runtime-epoch-${randomUUID()}`,
3957
+ requestedAt: this.#now(), receiptTtlMs: input.receiptTtlMs, installationId: plan.installationId, ledger: plan.ledger,
3958
+ plan: { id: plan.id, digest: plan.digest }, activation: { id: plan.activation.id, fence: plan.activation.fence },
3959
+ profile: { name: plan.profile, path: plan.target.profilePath }, issuer: input.issuer, predecessor: {
3960
+ operationId: readiness.operationId, receiptDigest: controlPlaneDigest(readiness.receipt), hostGeneration: readiness.receipt.hostGeneration
3961
+ },
3962
+ sequence, runtimeIdentityDigest: runtimeDigest });
3963
+ 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);
3964
+ return { request, status: 'pending' };
3965
+ });
3966
+ }
3967
+ getRuntimeEpoch(operationId) {
3968
+ const row = this.#database.prepare('SELECT * FROM deployment_runtime_epochs WHERE operation_id=?').get(operationId);
3969
+ if (!row)
3970
+ throw new ControlPlaneStoreError('not-found', 'runtime epoch absent');
3971
+ return runtimeEpochFromRow(row);
3972
+ }
3973
+ latestAppliedRuntimeEpoch(planId) {
3974
+ const row = this.#database.prepare(`SELECT * FROM deployment_runtime_epochs WHERE plan_id=? ORDER BY sequence DESC LIMIT 1`)
3975
+ .get(planId);
3976
+ if (row && row.status !== 'applied')
3977
+ throw new ControlPlaneStoreError('conflict', 'current runtime epoch is not independently verified');
3978
+ return row && runtimeEpochFromRow(row);
3979
+ }
3980
+ pendingRuntimeEpochs(coordinatorId) {
3981
+ return this.#database.prepare(`SELECT epoch.* FROM deployment_runtime_epochs epoch
3982
+ JOIN adoption_handoffs handoff ON handoff.plan_id=epoch.plan_id
3983
+ JOIN activation_plans plan ON plan.id=epoch.plan_id
3984
+ WHERE handoff.coordinator_id=? AND handoff.revoked_at IS NULL AND plan.status='activated'
3985
+ AND epoch.status IN ('pending','claimed') AND epoch.created_at + json_extract(epoch.request_json,'$.receiptTtlMs') > ?
3986
+ AND epoch.sequence=(SELECT max(other.sequence) FROM deployment_runtime_epochs other WHERE other.plan_id=epoch.plan_id)
3987
+ ORDER BY epoch.created_at,epoch.operation_id LIMIT 100`).all(coordinatorId, this.#now()).map(runtimeEpochFromRow);
3988
+ }
3989
+ claimRuntimeEpoch(operationId, coordinatorId) {
3990
+ this.#database.exec('BEGIN IMMEDIATE');
3991
+ try {
3992
+ const record = this.getRuntimeEpoch(operationId);
3993
+ if (!['pending', 'claimed'].includes(record.status) || this.#now() >= record.request.requestedAt + record.request.receiptTtlMs) {
3994
+ throw new ControlPlaneStoreError('expired', 'runtime epoch request is not live');
3995
+ }
3996
+ this.#database.prepare("UPDATE deployment_runtime_epochs SET status='claimed' WHERE operation_id=?").run(operationId);
3997
+ const context = readOwnerRuntimeEpochContext(this.#database, operationId);
3998
+ if (context.handoff.coordinatorId !== coordinatorId || (this.#adoptionCoordinatorId !== undefined && this.#adoptionCoordinatorId !== coordinatorId)) {
3999
+ throw new ControlPlaneStoreError('conflict', 'runtime epoch coordinator changed');
4000
+ }
4001
+ this.#database.exec('COMMIT');
4002
+ return { ...record, status: 'claimed' };
4003
+ }
4004
+ catch (error) {
4005
+ this.#database.exec('ROLLBACK');
4006
+ throw error;
4007
+ }
4008
+ }
4009
+ applyRuntimeEpoch(receiptInput, trust) {
4010
+ const receipt = parseRuntimeEpochReceipt(receiptInput);
4011
+ this.#database.exec('BEGIN IMMEDIATE');
4012
+ try {
4013
+ const context = readOwnerRuntimeEpochContext(this.#database, receipt.operationId);
4014
+ if (this.#now() >= context.request.requestedAt + context.request.receiptTtlMs)
4015
+ throw new ControlPlaneStoreError('expired', 'runtime epoch request expired before application');
4016
+ verifyRuntimeEpochReceipt(receipt, context.request, trust, this.#now());
4017
+ if (receipt.outcome !== 'passed' || receipt.evidence.failures !== 0)
4018
+ throw new ControlPlaneStoreError('conflict', 'failed runtime epoch cannot be applied');
4019
+ if (context.request.ledger.id !== trust.ledger.id || context.request.ledger.path !== trust.ledger.path) {
4020
+ throw new ControlPlaneStoreError('conflict', 'runtime epoch trust ledger changed');
4021
+ }
4022
+ this.#database.prepare("UPDATE deployment_runtime_epochs SET status='applied',receipt_json=? WHERE operation_id=? AND status='claimed'")
4023
+ .run(JSON.stringify(receipt), receipt.operationId);
4024
+ const result = this.getRuntimeEpoch(receipt.operationId);
4025
+ this.#database.exec('COMMIT');
4026
+ return result;
4027
+ }
4028
+ catch (error) {
4029
+ this.#database.exec('ROLLBACK');
4030
+ throw error;
4031
+ }
4032
+ }
2134
4033
  prepareHostAttestationOperation(input) {
2135
4034
  return this.#withActivationSource(input.planId, () => {
2136
4035
  const now = this.#now();
@@ -2283,25 +4182,194 @@ export class ControlPlaneStore {
2283
4182
  };
2284
4183
  return input.requireCurrentSource ? this.#withActivationSource(plan.id, commit) : commit();
2285
4184
  }
4185
+ listLiveQualificationDeployments(planId, limit = 1001) {
4186
+ if (!Number.isSafeInteger(limit) || limit < 1 || limit > 1001)
4187
+ throw new ControlPlaneStoreError('invalid-input', 'invalid live task limit');
4188
+ const window = this.getLiveQualificationWindow(planId);
4189
+ const committed = this.#database.prepare('SELECT started_at FROM activation_watch WHERE plan_id=?').get(planId);
4190
+ const cutoff = Math.min(window.deadlineAt, committed ? Number(committed.started_at) : window.deadlineAt);
4191
+ return this.#database.prepare(`SELECT inbox_id FROM foreground_deployments WHERE plan_id=?
4192
+ AND json_extract(record_json,'$.task.dispatchedAt')>=? AND json_extract(record_json,'$.task.dispatchedAt')<?
4193
+ ORDER BY inbox_id LIMIT ?`).all(planId, window.startedAt, cutoff, limit)
4194
+ .map(row => this.getForegroundDeployment(row.inbox_id));
4195
+ }
4196
+ listLiveQualificationPlans(profilePath, limit = 100) {
4197
+ if (!Number.isSafeInteger(limit) || limit < 1 || limit > 1000)
4198
+ throw new ControlPlaneStoreError('invalid-input', 'invalid qualification limit');
4199
+ return this.#database.prepare(`SELECT plan.* FROM activation_plans plan WHERE plan.target_path=? AND plan.status IN
4200
+ ('awaiting-live-tasks','commit-pending','activated') AND json_type(plan.dossier_json,'$.liveQualification')='object'
4201
+ AND (plan.status<>'activated' OR (plan.id=(SELECT checkpoint.plan_id FROM activation_deployment_checkpoints checkpoint
4202
+ 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)
4203
+ AND NOT EXISTS (SELECT 1 FROM activation_plans active WHERE active.target_path=plan.target_path AND active.id<>plan.id
4204
+ AND active.status IN ('staging','awaiting-reload','awaiting-readiness','awaiting-live-tasks','awaiting-effect-blocked-replay',
4205
+ 'awaiting-shadow','awaiting-canary','awaiting-soak','awaiting-health','commit-pending','rollback-pending'))))
4206
+ ORDER BY plan.created_at DESC,plan.id LIMIT ?`).all(profilePath, limit).map(activationFromRow);
4207
+ }
4208
+ getLiveQualificationDeadline(planId) {
4209
+ const plan = this.getPlan(planId);
4210
+ if (!plan.dossier.liveQualification)
4211
+ return undefined;
4212
+ const window = this.#database.prepare('SELECT deadline_at FROM live_qualification_windows WHERE plan_id=?').get(planId);
4213
+ return window ? Number(window.deadline_at) : undefined;
4214
+ }
4215
+ getLiveQualificationWindow(planId) { return liveQualificationWindow(this.#database, this.getPlan(planId)); }
4216
+ getLiveQualificationReadiness(planId) { return liveQualificationReadiness(this.#database, this.getPlan(planId)); }
4217
+ getLiveQualification(id) { return liveQualificationRecord(this.#database, id); }
4218
+ listLiveQualifications(planId, limit = 100) {
4219
+ if (!Number.isSafeInteger(limit) || limit < 1 || limit > 1000)
4220
+ throw new ControlPlaneStoreError('invalid-input', 'invalid qualification record limit');
4221
+ 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));
4222
+ }
4223
+ assertCurrentLiveQualification(id) {
4224
+ const { record, plan } = readLiveQualificationContext(this.#database, id);
4225
+ if (record.state === 'stale' || (plan.status !== 'activated' && (this.#now() >= record.batch.deadlineAt || this.#now() >= record.batch.expiresAt))) {
4226
+ throw new ControlPlaneStoreError('expired', 'live qualification expired or became stale');
4227
+ }
4228
+ }
4229
+ putLiveQualification(batch) {
4230
+ assertLiveQualificationBatch(batch);
4231
+ this.#database.exec('BEGIN IMMEDIATE');
4232
+ try {
4233
+ const plan = this.getPlan(batch.planId);
4234
+ if (plan.status !== 'awaiting-live-tasks' || this.#now() >= batch.deadlineAt || this.#now() >= batch.expiresAt)
4235
+ throw new ControlPlaneStoreError('expired', 'live qualification admission ended');
4236
+ const prior = this.getLiveQualification(batch.id);
4237
+ if (prior) {
4238
+ if (controlPlaneDigest(prior.batch) !== controlPlaneDigest(batch) || prior.state === 'stale')
4239
+ throw new ControlPlaneStoreError('conflict', 'live qualification identity reused');
4240
+ }
4241
+ else
4242
+ this.#database.prepare(`INSERT INTO live_qualification_batches
4243
+ (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());
4244
+ readLiveQualificationContext(this.#database, batch.id);
4245
+ this.#database.exec('COMMIT');
4246
+ }
4247
+ catch (error) {
4248
+ this.#database.exec('ROLLBACK');
4249
+ throw error;
4250
+ }
4251
+ }
4252
+ staleLiveQualification(id) {
4253
+ this.#database.prepare("UPDATE live_qualification_batches SET state='stale' WHERE id=? AND state IN ('pending','signed')").run(id);
4254
+ }
4255
+ async applyLiveQualification(input) {
4256
+ const record = this.getLiveQualification(input.receipt.batchId);
4257
+ if (!record)
4258
+ throw new ControlPlaneStoreError('not-found', 'live qualification batch absent');
4259
+ const receipt = verifyLiveQualificationReceipt(input.receipt, record.batch, input.publicKeyPem, this.#now());
4260
+ return input.withSourceFence(() => {
4261
+ this.#database.exec('BEGIN IMMEDIATE');
4262
+ try {
4263
+ const current = this.getLiveQualification(record.batch.id);
4264
+ if (current.state === 'applied') {
4265
+ if (controlPlaneDigest(current.receipt) !== controlPlaneDigest(receipt))
4266
+ throw new ControlPlaneStoreError('conflict', 'qualification receipt changed');
4267
+ this.#database.exec('COMMIT');
4268
+ return this.getPlan(record.batch.planId);
4269
+ }
4270
+ this.assertCurrentLiveQualification(record.batch.id);
4271
+ const plan = this.getPlan(record.batch.planId);
4272
+ if (plan.status !== 'awaiting-live-tasks' || this.#adoptionCoordinatorId !== undefined)
4273
+ throw new ControlPlaneStoreError('invalid-state', 'only target feedback runtime may qualify this deployment');
4274
+ const updated = this.#database.prepare(`UPDATE activation_plans SET status=?,revision=revision+1,updated_at=?,failure_code=?,activation_lease_until=NULL
4275
+ 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);
4276
+ if (Number(updated.changes) !== 1)
4277
+ throw new ControlPlaneStoreError('conflict', 'qualification lost plan CAS');
4278
+ this.#database.prepare("UPDATE live_qualification_batches SET state='applied',receipt_json=?,receipt_digest=? WHERE id=? AND state IN ('pending','signed')")
4279
+ .run(JSON.stringify(receipt), controlPlaneDigest(receipt), record.batch.id);
4280
+ this.#database.exec('COMMIT');
4281
+ return this.getPlan(plan.id);
4282
+ }
4283
+ catch (error) {
4284
+ this.#database.exec('ROLLBACK');
4285
+ throw error;
4286
+ }
4287
+ });
4288
+ }
4289
+ /** Negative-only Host seam. The signed terms permit conservative removal of unconfirmable qualification. */
4290
+ invalidateLiveQualification(input) {
4291
+ const reason = bounded(input.reason, 'qualification invalidation reason', 160);
4292
+ this.#database.exec('BEGIN IMMEDIATE');
4293
+ try {
4294
+ const plan = this.getPlan(input.planId), record = this.getLiveQualification(input.batchId);
4295
+ if (!record || record.batch.planId !== plan.id || record.state !== 'applied' || record.receipt?.disposition !== 'qualified'
4296
+ || record.batch.planDigest !== plan.digest || record.batch.activationId !== plan.activation?.id || !plan.dossier.liveQualification) {
4297
+ throw new ControlPlaneStoreError('conflict', 'qualification invalidation lacks exact accepted evidence');
4298
+ }
4299
+ if (plan.status === 'rollback-pending' || plan.status === 'rolled-back') {
4300
+ this.#database.exec('COMMIT');
4301
+ return plan;
4302
+ }
4303
+ if (!['commit-pending', 'activated'].includes(plan.status) || record.batch.fence !== plan.activation.fence)
4304
+ throw new ControlPlaneStoreError('conflict', 'qualification invalidation no longer targets current deployment');
4305
+ this.assertNoClaimedHostAttestation(plan.id);
4306
+ if (plan.status === 'activated') {
4307
+ const latest = this.#database.prepare(`SELECT checkpoint.plan_id,checkpoint.successful_order,checkpoint.exposure_order FROM activation_deployment_checkpoints checkpoint
4308
+ 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);
4309
+ if (!latest || latest.plan_id !== plan.id || latest.successful_order !== latest.exposure_order || !plan.activation?.hostRecoveryRequired
4310
+ || !this.getActivationInstalledBaseline(plan.id))
4311
+ throw new ControlPlaneStoreError('conflict', 'qualification invalidation cannot restore a superseded deployment');
4312
+ }
4313
+ const competing = this.#database.prepare(`SELECT 1 FROM activation_plans WHERE target_path=? AND id<>? AND status IN
4314
+ ('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);
4315
+ if (competing)
4316
+ throw new ControlPlaneStoreError('conflict', 'another deployment owns the profile');
4317
+ const now = this.#now();
4318
+ this.#database.prepare(`INSERT INTO live_qualification_invalidations
4319
+ (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);
4320
+ // A live commit worker is fenced by revision immediately; its final source guard must fail.
4321
+ this.#database.prepare(`UPDATE activation_plans SET status='rollback-pending',revision=revision+1,
4322
+ activation_lease_until=NULL,rollback_profile_restored=0,failure_code='live-qualification-invalidated',updated_at=?
4323
+ WHERE id=? AND revision=? AND activation_fence=?`).run(now, plan.id, plan.revision, plan.activation.fence);
4324
+ this.#database.exec('COMMIT');
4325
+ return this.getPlan(plan.id);
4326
+ }
4327
+ catch (error) {
4328
+ this.#database.exec('ROLLBACK');
4329
+ throw error;
4330
+ }
4331
+ }
4332
+ #assertLiveQualificationCommit(planId) {
4333
+ const plan = this.getPlan(planId);
4334
+ if (!plan.dossier.liveQualification)
4335
+ return;
4336
+ if (!this.#withLiveQualificationFence)
4337
+ throw new ControlPlaneStoreError('invalid-state', 'target canonical qualification fence unavailable');
4338
+ const record = this.listLiveQualifications(planId).find(item => item.state === 'applied' && item.receipt?.disposition === 'qualified');
4339
+ if (!record || this.#now() >= record.batch.deadlineAt || this.#now() >= record.receipt.expiresAt)
4340
+ throw new ControlPlaneStoreError('expired', 'accepted live qualification is absent or expired');
4341
+ this.assertCurrentLiveQualification(record.batch.id);
4342
+ }
2286
4343
  /** Same synchronous transaction owns current-profile selection and capture. */
2287
4344
  withForegroundDeployment(task, profilePath, callback) {
2288
4345
  assertForegroundTask(task);
2289
4346
  this.#database.exec('BEGIN IMMEDIATE');
2290
4347
  try {
2291
4348
  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',
4349
+ 'staging', 'awaiting-reload', 'awaiting-readiness', 'awaiting-live-tasks', 'awaiting-effect-blocked-replay', 'awaiting-shadow',
2293
4350
  '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');
4351
+ let plan;
4352
+ if (active) {
4353
+ plan = this.getPlan(String(active.id));
4354
+ if (!plan.dossier.liveQualification || !['awaiting-live-tasks', 'commit-pending'].includes(plan.status))
4355
+ throw new ControlPlaneStoreError('conflict', 'profile has an unsettled activation');
4356
+ const window = this.getLiveQualificationWindow(plan.id);
4357
+ if (task.dispatchedAt < window.startedAt || task.dispatchedAt >= window.deadlineAt || this.#now() >= window.deadlineAt) {
4358
+ throw new ControlPlaneStoreError('expired', 'task is outside the live qualification window');
4359
+ }
4360
+ }
4361
+ else {
4362
+ const latest = this.#database.prepare(`SELECT checkpoint.plan_id, checkpoint.exposure_order, checkpoint.successful_order
4363
+ FROM activation_deployment_checkpoints checkpoint JOIN activation_plans plan ON plan.id = checkpoint.plan_id
4364
+ WHERE plan.target_path = ? ORDER BY checkpoint.exposure_order DESC LIMIT 1`).get(profilePath);
4365
+ if (!latest || latest.exposure_order !== latest.successful_order)
4366
+ throw new ControlPlaneStoreError('invalid-state', 'profile has no current successful deployment');
4367
+ plan = this.getPlan(latest.plan_id);
4368
+ const watch = this.getActivationWatch(plan.id);
4369
+ if (plan.status !== 'activated' || !plan.activation || watch.state !== 'watching'
4370
+ || watch.activationId !== plan.activation.id || watch.fence !== plan.activation.fence || watch.startedAt > task.dispatchedAt) {
4371
+ throw new ControlPlaneStoreError('invalid-state', 'task did not begin under a watched deployment');
4372
+ }
2305
4373
  }
2306
4374
  const source = readOwnerSourceAdoptionPlan(this.#database, plan.id).source;
2307
4375
  const owner = source.owner;
@@ -2497,19 +4565,26 @@ export class ControlPlaneStore {
2497
4565
  }
2498
4566
  #isPostActivationRollback(plan) {
2499
4567
  if (plan.status !== 'rollback-pending' || plan.activation === undefined || plan.activation.failureCode === undefined
2500
- || !['post-activation-regressed', 'post-activation-retracted'].includes(plan.activation.failureCode))
4568
+ || !['post-activation-regressed', 'post-activation-retracted', 'live-qualification-invalidated'].includes(plan.activation.failureCode))
2501
4569
  return false;
2502
4570
  const checkpoint = this.#database.prepare('SELECT successful_order, exposure_order FROM activation_deployment_checkpoints WHERE plan_id = ?')
2503
4571
  .get(plan.id);
2504
4572
  return checkpoint !== undefined && checkpoint.successful_order === checkpoint.exposure_order;
2505
4573
  }
2506
4574
  #hasPostActivationRollbackProvenance(plan) {
2507
- if (plan.activation === undefined || !['post-activation-regressed', 'post-activation-retracted'].includes(plan.activation.failureCode ?? ''))
4575
+ if (plan.activation === undefined || !['post-activation-regressed', 'post-activation-retracted', 'live-qualification-invalidated'].includes(plan.activation.failureCode ?? ''))
2508
4576
  return false;
2509
4577
  const checkpoint = this.#database.prepare('SELECT successful_order, exposure_order FROM activation_deployment_checkpoints WHERE plan_id = ?')
2510
4578
  .get(plan.id);
2511
4579
  if (checkpoint === undefined || checkpoint.successful_order !== checkpoint.exposure_order)
2512
4580
  return false;
4581
+ if (plan.activation.failureCode === 'live-qualification-invalidated') {
4582
+ const invalidation = this.#database.prepare('SELECT * FROM live_qualification_invalidations WHERE plan_id=?').get(plan.id);
4583
+ const accepted = invalidation && this.getLiveQualification(invalidation.batch_id);
4584
+ return !!invalidation && accepted?.state === 'applied' && accepted.receipt?.disposition === 'qualified'
4585
+ && invalidation.batch_digest === accepted.batch.digest && invalidation.receipt_digest === controlPlaneDigest(accepted.receipt)
4586
+ && invalidation.activation_id === plan.activation.id && invalidation.fence === accepted.batch.fence && invalidation.fence <= plan.activation.fence;
4587
+ }
2513
4588
  const watch = this.#database.prepare('SELECT activation_id, fence, state, close_signature_digest FROM activation_watch WHERE plan_id = ?').get(plan.id);
2514
4589
  return watch !== undefined && (watch.state === 'closed-regressed' || watch.state === 'closed-retracted')
2515
4590
  && watch.close_signature_digest !== null && watch.activation_id === plan.activation.id && watch.fence <= plan.activation.fence;
@@ -2614,7 +4689,7 @@ export class ControlPlaneStore {
2614
4689
  AND NOT EXISTS (SELECT 1 FROM host_attestation_operations operation JOIN host_attestation_dispatches dispatch
2615
4690
  ON dispatch.operation_id = operation.operation_id WHERE operation.plan_id = activation_plans.id
2616
4691
  AND dispatch.status = 'claimed')
2617
- AND (status IN ('awaiting-reload', 'awaiting-readiness', 'awaiting-effect-blocked-replay', 'awaiting-shadow',
4692
+ AND (status IN ('awaiting-reload', 'awaiting-readiness', 'awaiting-live-tasks', 'awaiting-effect-blocked-replay', 'awaiting-shadow',
2618
4693
  'awaiting-canary', 'awaiting-soak', 'awaiting-health')
2619
4694
  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
4695
  if (Number(result.changes) !== 1)
@@ -2623,6 +4698,18 @@ export class ControlPlaneStore {
2623
4698
  }, true);
2624
4699
  }
2625
4700
  advanceActivation(input) {
4701
+ if (input.to === 'activated' && this.getPlan(input.planId).dossier.liveQualification && this.#liveQualificationAdmission !== input.planId) {
4702
+ this.#assertLiveQualificationCommit(input.planId);
4703
+ return this.#withLiveQualificationFence(input.planId, () => {
4704
+ this.#liveQualificationAdmission = input.planId;
4705
+ try {
4706
+ return this.withOwnerTaskFailureGapAdmission(this.getPlan(input.planId).gapId, () => this.advanceActivation(input));
4707
+ }
4708
+ finally {
4709
+ this.#liveQualificationAdmission = undefined;
4710
+ }
4711
+ });
4712
+ }
2626
4713
  return this.#withActivationSource(input.planId, () => {
2627
4714
  const allowed = { staging: ['awaiting-reload', 'rollback-pending'],
2628
4715
  'rollback-pending': ['rolled-back'], 'commit-pending': ['activated', 'rollback-pending'] };
@@ -2675,7 +4762,7 @@ export class ControlPlaneStore {
2675
4762
  if (replay !== undefined) {
2676
4763
  const transition = Object.values(expectedAttestation).find(item => item.phase === input.receipt.phase);
2677
4764
  const expectedStatus = input.receipt.outcome === 'passed' && transition !== undefined
2678
- ? transition.next : 'rollback-pending';
4765
+ ? (input.receipt.phase === 'readiness' && replay.result.dossier.liveQualification ? 'awaiting-live-tasks' : transition.next) : 'rollback-pending';
2679
4766
  const expectedFailure = input.receipt.outcome === 'passed' ? undefined : 'host-attestation-failed';
2680
4767
  if (replay.result.status !== expectedStatus || replay.result.revision !== input.expectedRevision + 1
2681
4768
  || replay.result.activation?.id !== input.receipt.activationId || replay.result.activation.fence !== input.expectedFence
@@ -2702,7 +4789,7 @@ export class ControlPlaneStore {
2702
4789
  throw new ControlPlaneStoreError('conflict', 'non-transition Host receipt changed generation from its predecessor');
2703
4790
  }
2704
4791
  const now = this.#now();
2705
- const nextStatus = verified.outcome === 'passed' ? expected.next : 'rollback-pending';
4792
+ const nextStatus = verified.outcome === 'passed' ? (verified.phase === 'readiness' && plan.dossier.liveQualification ? 'awaiting-live-tasks' : expected.next) : 'rollback-pending';
2706
4793
  return this.#withActivationSource(input.planId, () => {
2707
4794
  this.#database.exec('BEGIN IMMEDIATE');
2708
4795
  try {
@@ -2938,6 +5025,8 @@ export class ControlPlaneStore {
2938
5025
  ON CONFLICT(plan_id) DO NOTHING`).run(plan.id, plan.candidate.package, plan.candidate.version, plan.candidate.integrity, activationId, fence, now, now);
2939
5026
  if (Number(result.changes) !== 1)
2940
5027
  throw new ControlPlaneStoreError('invalid-state', 'activated plan has no post-activation watch');
5028
+ if (plan.dossier.liveQualification)
5029
+ this.#database.prepare('UPDATE activation_watch SET last_host_generation=? WHERE plan_id=?').run(this.getLiveQualificationWindow(plan.id).hostGeneration, plan.id);
2941
5030
  }
2942
5031
  }
2943
5032
  #reopenGapAfterActivation(plan, now) {
@@ -3085,9 +5174,64 @@ export class ControlPlaneStore {
3085
5174
  const now = this.#now();
3086
5175
  if (now > plan.expiresAt || now > verified.expiresAt)
3087
5176
  throw new ControlPlaneStoreError('expired', 'source release authorization is no longer applicable');
5177
+ const prelinked = this.#database.prepare('SELECT * FROM source_jobs WHERE plan_id=? LIMIT 2')
5178
+ .all(plan.id);
5179
+ const prebaseline = prelinked.length === 1 ? sourceJobFromRow(prelinked[0]).intent.baseline : undefined;
5180
+ const premaintenance = prebaseline ? this.getSourceMaintenanceRecords(plan.repository) : [];
5181
+ let lineageDigest;
5182
+ if (prebaseline && premaintenance.length) {
5183
+ if (!input.trust)
5184
+ throw new ControlPlaneStoreError('invalid-input', 'managed source maintenance requires current trust');
5185
+ const prehistory = this.getSourceBaselineHistory(plan.repository);
5186
+ await verifySourceBaselineHistory({ repository: plan.repository, config: prebaseline, trust: input.trust,
5187
+ history: prehistory, maintenance: premaintenance, signal: new AbortController().signal });
5188
+ lineageDigest = controlPlaneDigest({ history: prehistory, maintenance: premaintenance, trust: input.trust, baseline: prebaseline });
5189
+ }
3088
5190
  return withCurrentSource(plan.gapId, () => {
3089
5191
  this.#database.exec('BEGIN IMMEDIATE');
3090
5192
  try {
5193
+ const linkedRows = this.#database.prepare('SELECT * FROM source_jobs WHERE plan_id = ? LIMIT 2')
5194
+ .all(plan.id);
5195
+ if (linkedRows.length > 1)
5196
+ throw new ControlPlaneStoreError('invalid-state', 'source plan has multiple source jobs');
5197
+ const linked = linkedRows[0] === undefined ? undefined : sourceJobFromRow(linkedRows[0]);
5198
+ const managedRows = this.#database.prepare(`SELECT * FROM source_jobs WHERE json_extract(intent_json, '$.repository') = ?
5199
+ AND json_type(intent_json, '$.baseline') IS NOT NULL AND status IN ('queued', 'running', 'unknown', 'prepared')
5200
+ LIMIT 1001`).all(plan.repository);
5201
+ if (managedRows.length > 1000)
5202
+ throw new ControlPlaneStoreError('invalid-state', 'managed source job cohort exceeds bound');
5203
+ for (const row of managedRows) {
5204
+ const managed = sourceJobFromRow(row);
5205
+ if (managed.planId === plan.id)
5206
+ continue;
5207
+ if (managed.status !== 'prepared' || managed.planId === undefined
5208
+ || !['release-complete', 'release-failed', 'expired', 'local-checks-failed'].includes(this.getSourcePlan(managed.planId).status)) {
5209
+ throw new ControlPlaneStoreError('conflict', 'managed source job owns this repository release');
5210
+ }
5211
+ }
5212
+ if (linked?.intent.baseline !== undefined) {
5213
+ if (linked.status !== 'prepared' || linked.intent.repository !== plan.repository
5214
+ || linked.intent.baseCommit !== plan.baseCommit) {
5215
+ throw new ControlPlaneStoreError('invalid-state', 'managed source release is not bound to its prepared job');
5216
+ }
5217
+ const active = this.#database.prepare(`SELECT id FROM source_plans WHERE repository = ? AND id <> ?
5218
+ AND release_id IS NOT NULL AND status NOT IN ('release-complete', 'release-failed') LIMIT 1`)
5219
+ .get(plan.repository, plan.id);
5220
+ if (active !== undefined)
5221
+ throw new ControlPlaneStoreError('conflict', 'source repository has another active release');
5222
+ const history = this.getSourceBaselineHistory(plan.repository);
5223
+ const maintenance = this.getSourceMaintenanceRecords(plan.repository);
5224
+ if (lineageDigest !== undefined && controlPlaneDigest({ history, maintenance, trust: input.trust,
5225
+ baseline: linked.intent.baseline }) !== lineageDigest) {
5226
+ throw new ControlPlaneStoreError('conflict', 'managed source signed lineage changed before release');
5227
+ }
5228
+ if (maintenance.length && lineageDigest === undefined) {
5229
+ throw new ControlPlaneStoreError('conflict', 'managed source maintenance was not preverified');
5230
+ }
5231
+ const expectedCommit = sourceBaselineChain(linked.intent.baseline, history, maintenance).at(-1);
5232
+ if (plan.baseCommit !== expectedCommit)
5233
+ throw new ControlPlaneStoreError('conflict', 'managed source release base commit is stale');
5234
+ }
3091
5235
  const releaseId = `release-${randomUUID()}`;
3092
5236
  const result = this.#database.prepare(`UPDATE source_plans SET status = 'awaiting-pr', revision = revision + 1,
3093
5237
  release_authorization_json = ?, release_authorization_digest = ?, release_id = ?, release_fence = release_fence + 1,
@@ -3785,7 +5929,7 @@ export class ControlPlaneStore {
3785
5929
  const row = this.#database.prepare(`SELECT
3786
5930
  (SELECT count(*) FROM capability_gaps WHERE status = 'open') AS gaps,
3787
5931
  (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',
5932
+ (SELECT count(*) FROM activation_plans WHERE status IN ('staging', 'awaiting-reload', 'awaiting-readiness', 'awaiting-live-tasks',
3789
5933
  'awaiting-effect-blocked-replay', 'awaiting-shadow', 'awaiting-canary', 'awaiting-soak', 'awaiting-health', 'commit-pending')) AS active_activations,
3790
5934
  (SELECT count(*) FROM activation_plans WHERE status = 'rolled-back') +
3791
5935
  (SELECT count(*) FROM source_plans WHERE status IN ('local-checks-failed', 'release-failed')) AS failed,
@@ -3826,15 +5970,32 @@ export class ControlPlaneStore {
3826
5970
  const replayBinding = { operation: 'create-source-plan', gapId: snapshot.gapId, repository: snapshot.repository,
3827
5971
  worktree: snapshot.worktree, baseCommit: snapshot.baseCommit, name: snapshot.name, generatorDigest: snapshot.generatorDigest,
3828
5972
  scope: snapshot.scope, ttlMs: snapshot.expiresAt - snapshot.createdAt };
5973
+ const linkedRow = this.#database.prepare('SELECT * FROM source_jobs WHERE plan_id = ?').get(snapshot.id);
5974
+ const linked = linkedRow === undefined ? undefined : sourceJobFromRow(linkedRow);
5975
+ const sourceJob = linked === undefined ? undefined : { jobId: linked.id, jobRevision: linked.revision - 1,
5976
+ occurrenceId: linked.occurrenceId };
5977
+ const replayInput = { ...(snapshot.mode === 'create' ? replayBinding : { ...replayBinding,
5978
+ mode: snapshot.mode, ...(snapshot.creation === undefined ? {} : { creation: snapshot.creation }),
5979
+ ...(snapshot.sourceRevision === undefined ? {} : { revision: snapshot.sourceRevision }),
5980
+ prepared: { treeDigest: snapshot.sourceCheck.treeDigest, patchDigest: snapshot.sourceCheck.patchDigest,
5981
+ checkedAt: snapshot.sourceCheck.checkedAt, evidence: snapshot.preparedEvidence } }),
5982
+ ...(sourceJob === undefined ? {} : { sourceJob }) };
5983
+ const replayDigest = controlPlaneDigest(replayInput);
5984
+ const artifactReplayDigest = (snapshot.mode === 'prepared-create' || snapshot.mode === 'prepared-revise') && snapshot.preparedEvidence !== undefined
5985
+ ? controlPlaneDigest({ ...replayInput, preparedArtifactSha256: snapshot.preparedEvidence.pack.sha256 }) : undefined;
3829
5986
  if (snapshot.digest !== authoritative.digest || snapshot.gapId !== authoritative.gapId
3830
5987
  || snapshot.revision !== 1 || snapshot.status !== 'pending-approval' || snapshot.createdAt !== receipt.createdAt
3831
5988
  || 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
5989
+ || snapshot.release !== undefined || (replayDigest !== inputDigest && artifactReplayDigest !== inputDigest)
3835
5990
  || (snapshot.mode === 'create' && (snapshot.sourceCheck !== undefined || snapshot.preparedEvidence !== undefined))
3836
5991
  || (snapshot.mode === 'modify' && (snapshot.sourceCheck === undefined || snapshot.preparedEvidence === undefined
3837
- || controlPlaneDigest(snapshot.sourceCheck) !== controlPlaneDigest(authoritative.sourceCheck)))) {
5992
+ || controlPlaneDigest(snapshot.sourceCheck) !== controlPlaneDigest(authoritative.sourceCheck)))
5993
+ || (snapshot.mode === 'prepared-create' && (snapshot.sourceCheck === undefined || snapshot.preparedEvidence === undefined
5994
+ || snapshot.creation === undefined || linked?.intent.mode !== 'create'
5995
+ || controlPlaneDigest(snapshot.creation) !== controlPlaneDigest(authoritative.creation)))
5996
+ || (snapshot.mode === 'prepared-revise' && (snapshot.sourceCheck === undefined || snapshot.preparedEvidence === undefined
5997
+ || snapshot.sourceRevision === undefined || linked?.intent.mode !== 'revise-created'
5998
+ || controlPlaneDigest(snapshot.sourceRevision) !== controlPlaneDigest(authoritative.sourceRevision)))) {
3838
5999
  throw new ControlPlaneStoreError('invalid-state', 'stored source operation receipt is not bound to authoritative state');
3839
6000
  }
3840
6001
  return { ...receipt, result: snapshot };
@@ -3867,7 +6028,7 @@ export class ControlPlaneStore {
3867
6028
  catalog: { digest: snapshot.dossier.catalogDigest, provenance: snapshot.dossier.catalogProvenance },
3868
6029
  matchedCapabilities: snapshot.dossier.matchedCapabilities, profile: snapshot.profile, target: snapshot.target,
3869
6030
  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 }) };
6031
+ 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
6032
  if (snapshot.digest !== authoritative.digest || snapshot.gapId !== authoritative.gapId || snapshot.revision !== 1
3872
6033
  || snapshot.status !== 'pending-approval' || snapshot.createdAt !== receipt.createdAt
3873
6034
  || snapshot.approval !== undefined || snapshot.activation !== undefined || controlPlaneDigest(replayBinding) !== inputDigest) {