@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/service.js CHANGED
@@ -1,15 +1,22 @@
1
1
  import { AdoptionCoordinatorRuntime, validateAdoptionCoordinatorConfig } from './adoption-coordinator.js';
2
- import { lstat, realpath } from 'node:fs/promises';
2
+ import { CreationCapabilityJournal, creationCapabilityPublicKey, validateCreationCapabilityConfig } from './creation-capability-journal.js';
3
+ import { CreationCapabilityRuntime } from './creation-capability-runtime.js';
4
+ import { inspectCreationCapabilityTaskAssociations } from './creation-capability-feedback.js';
5
+ import { captureCreationCapabilitySource } from './creation-capability-source.js';
6
+ import { lstat, readFile, realpath } from 'node:fs/promises';
3
7
  import { realpathSync } from 'node:fs';
4
8
  import { basename, dirname, isAbsolute, join, resolve } from 'node:path';
9
+ import { createHash, createPublicKey, randomBytes } from 'node:crypto';
10
+ import { isSourceOwnerContinuation, sourceGrowthRunDigest, validateCreationAcceptanceAuthorityRef, verifyPluginCreationVerificationCertificate, validateRevisionAcceptanceAuthorityRef, verifyPluginRevisionVerificationCertificate, validateRevisionRegressionAcceptanceAuthorityRef, verifyPluginRevisionRegressionCertificate, validateSourceGrowthRunBinding } from '@dsh-enhanced/assistant-growth-contract';
5
11
  import { TaskObservationRuntime, validateTaskObservationConfig } from './task-observation-runtime.js';
12
+ import { LiveQualificationRuntime, validateLiveQualificationConfig } from './live-qualification-runtime.js';
6
13
  import { rollbackPluginWatch } from './cli.js';
7
14
  import { OwnerTaskFailureGaps } from './owner-task-gaps.js';
8
15
  import { Context, Service } from '@deepseek-ai/cordis';
9
16
  import Schema from '@deepseek-ai/schemastery';
10
17
  import { discover, loadCatalogWithMetadata } from './catalog.js';
11
18
  import { ControlPlaneCliError } from './errors.js';
12
- import { assertPluginModificationAllowed, createIsolatedWorktree, gcPreparedModifyWorktrees, runLocalCommand, validateScopedPluginFiles, verifyPreparedSourceWorktree, writeScopedPluginFiles, } from './source-workspace.js';
19
+ import { assertPluginModificationAllowed, createIsolatedWorktree, gcPreparedModifyWorktrees, inspectPreparedCreationPatch, runLocalCommand, validateScopedPluginFiles, verifyPreparedSourceWorktree, writeScopedPluginFiles, } from './source-workspace.js';
13
20
  import { assertManagedVersionPaths, managedPatchVersionFiles, verifyManagedPatchVersion } from './source-versioning.js';
14
21
  import { requestSourceApproval, validateSourceApprovalClientConfig } from './source-approval-client.js';
15
22
  import { requestSourceReleaseAuthorization, validateSourceReleaseClientConfig } from './source-release-client.js';
@@ -19,19 +26,34 @@ import { adoptSourceRelease, validateSourceAdoptionConfig } from './source-adopt
19
26
  import { Ed25519ApprovalAuthority } from './approval.js';
20
27
  import { ControlPlaneStore, MODIFY_GENERATOR_DIGEST, controlPlaneDigest } from './store.js';
21
28
  import { runDockerPreparedChecks, validateSourceBuildConfig } from './source-build.js';
22
- import { awaitSourceSignal, inspectSourceContext } from './source-context.js';
29
+ import { runNativePreparedChecks } from './native-source-build.js';
30
+ import { NativeMacVerifierRunner } from '@dsh-enhanced/assistant-isolation';
31
+ import { NATIVE_SOURCE_COMPILER } from './native-source-build-worker.js';
32
+ import { awaitSourceSignal, inspectSourceContext, inspectSourceTargetsContext } from './source-context.js';
33
+ import { inspectSourceRevisionContext, prepareRevisedPluginWorkspace, verifyRevisedPluginWorkspace } from './source-revision.js';
34
+ import { inspectSourceCreationContext, prepareCreatedPluginWorkspace, validateSourceCreationFiles, verifyCreatedPluginWorkspace } from './source-creation.js';
35
+ import { resolveSourceBaseline } from './source-baseline.js';
23
36
  import { inheritedEnvironment, loadTrustConfig, resolveTrustKey } from './trust.js';
24
37
  import { registerPluginControlTools } from './tools.js';
25
- import { SourceJobRuntime, validateSourceJobsConfig } from './source-jobs.js';
38
+ import { SourceGrowthRunUnavailableError, SourceJobRuntime, validateSourceJobsConfig } from './source-jobs.js';
26
39
  import { installRuntimeObserver, validateRuntimeObserverConfig } from './runtime-observer.js';
27
40
  import { installReplayEndpoint, validateReplayEndpointConfig } from './replay-endpoint.js';
28
41
  import { readPrivateRuntimeObserverKey } from './runtime-observer-protocol.js';
29
- import { createForegroundDeploymentObserver, foregroundTrustSnapshot, validateForegroundDeploymentConfig } from './foreground-deployment-runtime.js';
42
+ import { createForegroundDeploymentObserver, foregroundTrustSnapshot, queueRuntimeEpoch, validateForegroundDeploymentConfig } from './foreground-deployment-runtime.js';
43
+ import { captureRetainedDeploymentReadiness } from './deployment-readiness.js';
44
+ import { installHostReadiness, onHostReady } from './host-readiness.js';
45
+ const CREATION_UNKNOWN_CODES = new Set(['schema-observation-unknown', 'case-observation-unknown', 'contract-insufficient',
46
+ 'interrupted', 'stale-source', 'verification-unknown', 'previous-unknown']);
47
+ const CREATION_REJECTED_CODES = new Set(['case-mismatch', 'source-review-rejected', 'request-invalid']);
30
48
  const schema = Schema.object({
31
49
  catalogPath: Schema.string().required(), statePath: Schema.string().required(), trustPath: Schema.string().required(),
32
50
  proposalTtlMs: Schema.number().step(1).min(60_000).max(86_400_000).default(900_000),
33
51
  sourceBuild: Schema.any(),
34
52
  sourceJobs: Schema.any(),
53
+ creationVerifications: Schema.any(),
54
+ revisionVerifications: Schema.any(),
55
+ revisionRegressions: Schema.any(),
56
+ creationCapabilities: Schema.any(),
35
57
  sourceApprovals: Schema.any(),
36
58
  sourceReleases: Schema.any(),
37
59
  sourceReleaseExecution: Schema.any(),
@@ -40,6 +62,7 @@ const schema = Schema.object({
40
62
  runtimeObserver: Schema.any(),
41
63
  foregroundDeployments: Schema.any(),
42
64
  taskObservations: Schema.any(),
65
+ liveQualification: Schema.any(),
43
66
  replayEndpoint: Schema.any(),
44
67
  });
45
68
  async function canonicalTarget(dshHome, profile) {
@@ -81,6 +104,17 @@ export function normalizeControlPlaneConfig(input) {
81
104
  throw new Error('plugin-control-plane: taskObservations requires foregroundDeployments on the same profile');
82
105
  }
83
106
  }
107
+ if (config.liveQualification !== undefined) {
108
+ validateLiveQualificationConfig(config.liveQualification);
109
+ if (!config.foregroundDeployments || config.liveQualification.profilePath !== config.runtimeObserver?.profilePath) {
110
+ throw new Error('plugin-control-plane: liveQualification requires foregroundDeployments on the same profile');
111
+ }
112
+ if (!config.sourceAdoptions?.liveQualification || !config.sourceReleaseExecution?.independentReview
113
+ || !config.taskObservations || config.taskObservations.profilePath !== config.liveQualification.profilePath
114
+ || config.sourceAdoptions.profile !== basename(config.liveQualification.profilePath)) {
115
+ throw new Error('plugin-control-plane: liveQualification requires independently reviewed sourceAdoptions');
116
+ }
117
+ }
84
118
  if (config.replayEndpoint !== undefined) {
85
119
  validateReplayEndpointConfig(config.replayEndpoint);
86
120
  if (config.runtimeObserver !== undefined) {
@@ -102,6 +136,13 @@ export function normalizeControlPlaneConfig(input) {
102
136
  validateSourceApprovalClientConfig(config.sourceApprovals);
103
137
  if (config.sourceBuild !== undefined)
104
138
  validateSourceBuildConfig(config.sourceBuild);
139
+ if (config.sourceBuild !== undefined && 'backend' in config.sourceBuild) {
140
+ if (!config.sourceJobs?.allowedModes || config.sourceJobs.allowedModes.some(mode => mode === 'modify')
141
+ || config.sourceReleases !== undefined || config.sourceReleaseExecution !== undefined
142
+ || config.sourceAdoptions !== undefined || config.sourceApprovals !== undefined) {
143
+ throw new Error('plugin-control-plane: native source build only supports Host-scaffolded create/revise-created');
144
+ }
145
+ }
105
146
  if (config.sourceReleases !== undefined) {
106
147
  validateSourceReleaseClientConfig(config.sourceReleases);
107
148
  if (!config.sourceApprovals || config.sourceBuild?.versioning !== 'patch')
@@ -122,12 +163,84 @@ export function normalizeControlPlaneConfig(input) {
122
163
  validateSourceAdoptionConfig(config.sourceAdoptions);
123
164
  if (!config.sourceReleaseExecution)
124
165
  throw new Error('plugin-control-plane: sourceAdoptions requires sourceReleaseExecution');
166
+ if (config.sourceAdoptions.liveQualification && !config.liveQualification) {
167
+ throw new Error('plugin-control-plane: bounded-live sourceAdoptions requires liveQualification runtime');
168
+ }
125
169
  }
126
170
  if (config.sourceJobs !== undefined) {
127
171
  validateSourceJobsConfig(config.sourceJobs, config.sourceBuild);
128
172
  if (realpathSync(config.sourceJobs.repository) !== config.sourceJobs.repository)
129
173
  throw new Error('plugin-control-plane: sourceJobs.repository must be canonical');
130
174
  }
175
+ if (config.creationVerifications !== undefined) {
176
+ const policy = config.creationVerifications;
177
+ if (!policy || typeof policy !== 'object' || Array.isArray(policy)
178
+ || Object.keys(policy).sort().join(',') !== 'authority,publicKey'
179
+ || typeof policy.publicKey !== 'string' || policy.publicKey.length > 4096) {
180
+ throw new Error('plugin-control-plane: invalid creation verification configuration');
181
+ }
182
+ validateCreationAcceptanceAuthorityRef(policy.authority);
183
+ let key;
184
+ try {
185
+ key = createPublicKey(policy.publicKey);
186
+ }
187
+ catch {
188
+ throw new Error('plugin-control-plane: creation verification public key is invalid');
189
+ }
190
+ if (!policy.publicKey.startsWith('-----BEGIN PUBLIC KEY-----\n') || key.asymmetricKeyType !== 'ed25519' || key.type !== 'public'
191
+ || key.export({ format: 'pem', type: 'spki' }).toString() !== policy.publicKey
192
+ || (policy.authority.expiresAt <= Date.now() && config.creationCapabilities?.retention === undefined)
193
+ || !config.sourceJobs?.creation || policy.authority.namePrefix !== config.sourceJobs.creation.namePrefix
194
+ || policy.authority.expiresAt > Math.min(config.sourceJobs.creation.expiresAt, config.sourceJobs.expiresAt)) {
195
+ throw new Error('plugin-control-plane: creation verification authority does not match finite source creation grant');
196
+ }
197
+ }
198
+ if (config.revisionVerifications !== undefined || config.sourceJobs?.revision !== undefined) {
199
+ const policy = config.revisionVerifications, grant = config.sourceJobs?.revision;
200
+ if (!policy || !grant || !config.creationCapabilities || !config.sourceJobs
201
+ || Object.keys(policy).sort().join(',') !== 'authority,publicKey' || typeof policy.publicKey !== 'string') {
202
+ throw new Error('plugin-control-plane: revision verification requires its separate finite grant and retained adopted source');
203
+ }
204
+ validateRevisionAcceptanceAuthorityRef(policy.authority);
205
+ const key = createPublicKey(policy.publicKey);
206
+ if (key.asymmetricKeyType !== 'ed25519' || policy.authority.namePrefix !== grant.namePrefix
207
+ || policy.authority.expiresAt > Math.min(grant.expiresAt, config.sourceJobs.expiresAt)
208
+ || createPublicKey(creationCapabilityPublicKey(config.creationCapabilities)).export({ format: 'der', type: 'spki' })
209
+ .equals(key.export({ format: 'der', type: 'spki' }))) {
210
+ throw new Error('plugin-control-plane: invalid independent revision verification authority');
211
+ }
212
+ }
213
+ if (config.revisionRegressions !== undefined) {
214
+ const policy = config.revisionRegressions, jobs = config.sourceJobs, adoption = config.creationCapabilities;
215
+ if (!jobs?.revision || !config.revisionVerifications || !adoption
216
+ || Object.keys(policy).sort().join(',') !== 'authority,publicKey' || typeof policy.publicKey !== 'string') {
217
+ throw new Error('plugin-control-plane: regression requires independently configured review and retained parent source');
218
+ }
219
+ validateRevisionRegressionAcceptanceAuthorityRef(policy.authority);
220
+ const key = createPublicKey(policy.publicKey);
221
+ if (key.asymmetricKeyType !== 'ed25519' || policy.authority.namePrefix !== jobs.revision.namePrefix
222
+ || policy.authority.expiresAt > Math.min(jobs.expiresAt, jobs.revision.expiresAt, config.revisionVerifications.authority.expiresAt)
223
+ || createPublicKey(creationCapabilityPublicKey(adoption)).export({ format: 'der', type: 'spki' })
224
+ .equals(key.export({ format: 'der', type: 'spki' }))) {
225
+ throw new Error('plugin-control-plane: invalid independent revision regression authority');
226
+ }
227
+ }
228
+ if (config.creationCapabilities !== undefined) {
229
+ validateCreationCapabilityConfig(config.creationCapabilities);
230
+ const capability = config.creationCapabilities;
231
+ const verification = config.creationVerifications;
232
+ const jobs = config.sourceJobs;
233
+ if (!verification || !jobs?.creation || capability.namePrefix !== jobs.creation.namePrefix
234
+ || capability.owner.authorityId !== jobs.ownerRouteId || capability.owner.principalId !== jobs.principalId
235
+ || capability.owner.workspace !== jobs.workspace || capability.owner.agentPreset !== jobs.preset
236
+ || capability.keyId === verification.authority.keyId) {
237
+ throw new Error('plugin-control-plane: created capability authority does not match its authenticated creation lane');
238
+ }
239
+ const adoptionKey = createPublicKey(creationCapabilityPublicKey(capability)).export({ format: 'der', type: 'spki' });
240
+ const verificationKey = createPublicKey(verification.publicKey).export({ format: 'der', type: 'spki' });
241
+ if (adoptionKey.equals(verificationKey))
242
+ throw new Error('plugin-control-plane: adoption and verification signing keys must be separate');
243
+ }
131
244
  if (![config.catalogPath, config.statePath, config.trustPath].every(isAbsolute))
132
245
  throw new Error('plugin-control-plane: catalogPath, statePath and trustPath must be absolute');
133
246
  return config;
@@ -145,8 +258,17 @@ export class PluginControlPlaneService extends Service {
145
258
  sourceReleaseAdvances = new Map();
146
259
  sourceAdoptionFlights = new Map();
147
260
  foregroundObservers = new Set();
261
+ liveRuntime;
262
+ assertLiveRuntime;
148
263
  sourceRuntime;
264
+ nativeSourceRunner;
149
265
  sourceRuntimes = new Set();
266
+ creationCapabilityRuntime;
267
+ creationCapabilityRuntimes = new Set();
268
+ creationCapabilityFlights = new Set();
269
+ creationCapabilityReconcileRequested = false;
270
+ growthRunProducer;
271
+ growthProviderDraining = false;
150
272
  constructor(ctx, input) {
151
273
  super(ctx, 'pluginControlPlane');
152
274
  this.config = normalizeControlPlaneConfig(input);
@@ -164,8 +286,20 @@ export class PluginControlPlaneService extends Service {
164
286
  this.abort.abort();
165
287
  await Promise.allSettled([...this.sourceRuntimes].map(runtime => runtime.close()));
166
288
  await Promise.allSettled([...this.sourceBuilds, ...this.sourceInspections, ...this.sourceApprovalFlights, ...this.sourceReleaseFlights, ...this.sourceAdoptionFlights.values()]);
167
- this.store.close();
289
+ await Promise.allSettled(this.creationCapabilityFlights);
290
+ const closed = await Promise.allSettled([...this.creationCapabilityRuntimes].map(runtime => runtime.close()));
291
+ const failures = closed.filter((result) => result.status === 'rejected')
292
+ .map(result => result.reason);
293
+ try {
294
+ this.store.close();
295
+ }
296
+ catch (error) {
297
+ failures.push(error);
298
+ }
299
+ if (failures.length)
300
+ throw new AggregateError(failures, 'created capability resource release unconfirmed');
168
301
  }, 'plugin-control-plane.store');
302
+ installHostReadiness(ctx);
169
303
  ctx.inject(['tools'], toolsCtx => registerPluginControlTools(toolsCtx, this));
170
304
  if (this.config.runtimeObserver !== undefined)
171
305
  installRuntimeObserver(ctx, this.config.runtimeObserver, this.config.foregroundDeployments ? (observerCtx, sample) => {
@@ -180,16 +314,54 @@ export class PluginControlPlaneService extends Service {
180
314
  throw new Error('foreground deployment trust changed');
181
315
  };
182
316
  assertCurrent();
317
+ const assertLiveRuntime = (planId) => {
318
+ assertCurrent();
319
+ const plan = this.store.getPlan(planId);
320
+ const operation = this.store.getLiveQualificationReadiness(planId);
321
+ if (!operation.receipt)
322
+ throw new Error('live qualification has no retained signed readiness');
323
+ const binding = captureRetainedDeploymentReadiness({ plan, operation, receipt: operation.receipt, trust,
324
+ journalPath: this.config.foregroundDeployments.attestorJournalPath,
325
+ runtime: sample(randomBytes(32).toString('hex')) });
326
+ const window = this.store.getLiveQualificationWindow(planId);
327
+ if (binding.receiptDigest !== window.readinessDigest || binding.hostGeneration !== window.hostGeneration) {
328
+ throw new Error('live qualification runtime differs from retained readiness');
329
+ }
330
+ assertCurrent();
331
+ };
332
+ const requestRuntimeEpoch = (runtime) => {
333
+ assertCurrent();
334
+ queueRuntimeEpoch(this.store, trust, runtime, (gapId, owner, callback) => this.taskGaps.withCurrent(gapId, owner, callback));
335
+ };
183
336
  const registration = createForegroundDeploymentObserver({ config: this.config.foregroundDeployments,
184
- profilePath: this.config.runtimeObserver.profilePath, store: this.store, trust, sample, assertCurrent, owner: this });
337
+ profilePath: this.config.runtimeObserver.profilePath, store: this.store, trust, sample, assertCurrent, owner: this, requestRuntimeEpoch });
338
+ onHostReady(deliveryCtx, () => {
339
+ try {
340
+ requestRuntimeEpoch(sample(randomBytes(32).toString('hex')));
341
+ }
342
+ catch { /* Initial deployment and inactive/retracted sources have no continuity work. */ }
343
+ });
185
344
  this.foregroundObservers.add(registration);
186
345
  try {
187
346
  const delivery = deliveryCtx.get('assistantDelivery');
188
347
  const remove = delivery.registerForegroundTaskObserver(registration);
189
- return () => { live = false; this.foregroundObservers.delete(registration); remove(); };
348
+ if (this.config.liveQualification) {
349
+ this.assertLiveRuntime = assertLiveRuntime;
350
+ this.liveRuntime?.scan();
351
+ }
352
+ return () => {
353
+ live = false;
354
+ if (this.assertLiveRuntime === assertLiveRuntime)
355
+ this.assertLiveRuntime = undefined;
356
+ this.liveRuntime?.scan();
357
+ this.foregroundObservers.delete(registration);
358
+ remove();
359
+ };
190
360
  }
191
361
  catch (error) {
192
362
  live = false;
363
+ if (this.assertLiveRuntime === assertLiveRuntime)
364
+ this.assertLiveRuntime = undefined;
193
365
  this.foregroundObservers.delete(registration);
194
366
  throw error;
195
367
  }
@@ -220,7 +392,16 @@ export class PluginControlPlaneService extends Service {
220
392
  inspectSystemOwnedActivation: input => automations().inspectSystemOwnedActivation(input),
221
393
  }, });
222
394
  runtime.start();
223
- return () => runtime.close();
395
+ const owner = 'plugin-control-plane-adoption-coordinator';
396
+ const automationId = `adoption-coordinator-${controlPlaneDigest({
397
+ coordinatorId: this.config.adoptionCoordinator.coordinatorId,
398
+ scope: this.config.adoptionCoordinator.scope,
399
+ }).slice(0, 40)}`;
400
+ const activationNonce = automations().inspectSystemOwnedActivation({ owner, automationId })?.activationNonce;
401
+ if (activationNonce === undefined)
402
+ throw new Error('plugin-control-plane: adoption coordinator registration is missing');
403
+ const stop = automations().registerHostShutdown(coordinatorCtx, { owner, automationId, activationNonce }, () => runtime.close({ skipPause: true }));
404
+ return stop;
224
405
  }
225
406
  catch (error) {
226
407
  if (runtime)
@@ -276,61 +457,281 @@ export class PluginControlPlaneService extends Service {
276
457
  }
277
458
  }, 'plugin-control-plane.task-observations');
278
459
  });
460
+ if (this.config.liveQualification !== undefined)
461
+ ctx.inject(['assistantAutomations', 'assistantDelivery', 'assistantEvaluation'], observerCtx => {
462
+ observerCtx.effect(async () => {
463
+ const snapshot = foregroundTrustSnapshot(this.config.trustPath), trust = await this.boundTrust();
464
+ this.abort.signal.throwIfAborted();
465
+ const store = new ControlPlaneStore({ path: trust.ledger.path });
466
+ const evaluation = () => observerCtx.get('assistantEvaluation');
467
+ const delivery = () => observerCtx.get('assistantDelivery');
468
+ const automations = () => observerCtx.get('assistantAutomations');
469
+ let runtime;
470
+ try {
471
+ runtime = new LiveQualificationRuntime({ config: this.config.liveQualification, store, trust,
472
+ assertCurrent: () => {
473
+ this.abort.signal.throwIfAborted();
474
+ if (foregroundTrustSnapshot(this.config.trustPath) !== snapshot)
475
+ throw new Error('live qualification trust changed');
476
+ },
477
+ assertRuntime: planId => {
478
+ if (!this.assertLiveRuntime)
479
+ throw new Error('live qualification Host observer unavailable');
480
+ this.assertLiveRuntime(planId);
481
+ },
482
+ runtimeAvailable: () => this.assertLiveRuntime !== undefined,
483
+ qualificationSource: planId => {
484
+ const plan = this.store.getPlan(planId);
485
+ const reference = this.store.getOwnerTaskFailureReference(plan.gapId);
486
+ if (!reference)
487
+ throw new Error('live qualification original owner failure unavailable');
488
+ return this.taskGaps.inspectCurrent(plan.gapId, reference.owner);
489
+ },
490
+ evaluation: {
491
+ canonicalHostScope: input => evaluation().canonicalHostScope(input),
492
+ getTrustedForegroundLearningProjection: input => evaluation().getTrustedForegroundLearningProjection(input),
493
+ withTrustedCanonicalTaskWriterFence: (input, callback) => evaluation().withTrustedCanonicalTaskWriterFence(input, callback),
494
+ onTrustedTaskChange: callback => evaluation().onTrustedTaskChange(callback),
495
+ },
496
+ delivery: {
497
+ validateOwnerRoute: input => delivery().validateOwnerRoute(input),
498
+ inspectOwnerForegroundLearningTask: input => delivery().inspectOwnerForegroundLearningTask(input),
499
+ },
500
+ automations: {
501
+ registerHostExecutor: input => automations().registerHostExecutor(input),
502
+ reconcileSystem: input => automations().reconcileSystem(input),
503
+ inspectSystemOwnedActivation: input => automations().inspectSystemOwnedActivation(input),
504
+ }, });
505
+ runtime.start();
506
+ this.liveRuntime = runtime;
507
+ return async () => {
508
+ if (this.liveRuntime === runtime)
509
+ this.liveRuntime = undefined;
510
+ await runtime.close();
511
+ };
512
+ }
513
+ catch (error) {
514
+ if (runtime)
515
+ await runtime.close();
516
+ else
517
+ store.close();
518
+ throw error;
519
+ }
520
+ }, 'plugin-control-plane.live-qualification');
521
+ });
279
522
  if (this.config.sourceJobs !== undefined)
280
523
  ctx.inject(['assistantAutomations', 'assistantDelivery',
281
524
  ...(this.config.sourceApprovals ? ['assistantEvaluation'] : []),
282
- ...(this.config.sourceReleaseExecution?.independentReview ? ['assistantVerifier', 'agents', 'sessions', 'tools', 'llm', 'systemPrompt', 'assistantPolicy'] : [])], jobsCtx => {
525
+ ...(this.config.sourceReleaseExecution?.independentReview ? ['assistantVerifier', 'agents', 'sessions', 'tools', 'llm', 'systemPrompt', 'assistantPolicy'] : []),
526
+ ...(this.config.creationVerifications || this.config.revisionVerifications || this.config.revisionRegressions ? ['assistantVerifier'] : []),
527
+ ...(this.config.creationCapabilities ? ['tools', 'assistantEvaluation', 'assistantPolicy'] : [])], jobsCtx => {
283
528
  jobsCtx.effect(async () => {
284
529
  this.abort.signal.throwIfAborted();
285
530
  const current = (key) => jobsCtx.get((key === 'automations' ? 'assistantAutomations' : 'assistantDelivery'));
286
- for (const method of ['registerHostExecutor', 'reconcileSystem', 'inspectSystemOwnedActivation', 'inspectSystemOwned']) {
531
+ for (const method of ['registerHostExecutor', 'reconcileSystem', 'reconcileSystemExact', 'pauseSystemOwned',
532
+ 'inspectSystemOwnedActivation', 'inspectSystemOwned']) {
287
533
  if (typeof current('automations')[method] !== 'function')
288
534
  throw new Error(`plugin-control-plane: durable source jobs require assistantAutomations.${method}`);
289
535
  }
290
536
  if (typeof current('delivery').validateOwnerRoute !== 'function')
291
537
  throw new Error('plugin-control-plane: durable source jobs require Delivery v2 owner validation');
292
- const runtime = new SourceJobRuntime({ config: this.config.sourceJobs, build: this.config.sourceBuild, statePath: this.config.statePath, store: this.store,
293
- ports: {
294
- automations: {
295
- registerHostExecutor: executor => current('automations').registerHostExecutor(executor),
296
- reconcileSystem: request => current('automations').reconcileSystem(request),
297
- inspectSystemOwnedActivation: request => current('automations').inspectSystemOwnedActivation(request),
298
- inspectSystemOwned: request => current('automations').inspectSystemOwned(request),
299
- },
300
- delivery: { validateOwnerRoute: request => current('delivery').validateOwnerRoute(request) },
301
- }, withGapSourceFence: (gapId, owner, callback) => this.taskGaps.withCurrent(gapId, owner, callback),
302
- ...(this.config.sourceApprovals ? { approvePrepared: async (job, signal) => {
303
- if (!job.planId)
304
- throw new Error('source job has no prepared plan');
305
- await this.requestOwnerSourceApproval({ planId: job.planId, signal, expectedTrustDigest: job.intent.trustDigest });
306
- } } : {}),
307
- ...(this.config.sourceReleases ? { releasePrepared: async (job, signal) => {
308
- if (!job.planId)
309
- throw new Error('source job has no prepared plan');
310
- await this.requestOwnerSourceRelease({ planId: job.planId, signal, expectedTrustDigest: job.intent.trustDigest });
311
- } } : {}),
312
- ...(this.config.sourceReleaseExecution ? { releaseTimeoutMs: this.config.sourceReleaseExecution.timeoutMs,
313
- advanceReleased: async (job, signal) => {
314
- if (!job.planId)
315
- throw new Error('source job has no release plan');
316
- await this.advanceOwnerSourceRelease({ planId: job.planId, signal, expectedTrustDigest: job.intent.trustDigest });
317
- } } : {}),
318
- ...(this.config.sourceAdoptions ? { adoptionTimeoutMs: this.config.sourceAdoptions.timeoutMs,
319
- adoptReleased: async (job, signal, assertCurrent) => {
320
- if (!job.planId)
321
- throw new Error('source job has no released plan');
322
- await this.adoptOwnerSourceRelease({ sourcePlanId: job.planId, signal, expectedTrustDigest: job.intent.trustDigest, assertCurrent });
323
- } } : {}),
324
- trust: () => this.boundTrust(), prepare: (job, signal, assertCurrent) => this.prepareSourceJob(job, signal, assertCurrent), });
538
+ let capabilities;
539
+ let unsubscribeTaskChanges;
540
+ if (this.config.creationCapabilities) {
541
+ if (typeof current('delivery').validateOwnerAgentForRoute !== 'function') {
542
+ throw new Error('plugin-control-plane: created capabilities require authenticated live owner Agent admission');
543
+ }
544
+ const evaluation = jobsCtx.get('assistantEvaluation');
545
+ if (typeof evaluation.onTrustedTaskChange !== 'function') {
546
+ throw new Error('created capabilities require canonical task change notifications');
547
+ }
548
+ const config = this.config.creationCapabilities;
549
+ const journal = new CreationCapabilityJournal({ path: join(this.config.statePath, 'creation-adoptions.sqlite'), config });
550
+ try {
551
+ capabilities = new CreationCapabilityRuntime({ ctx: jobsCtx, config, journal, ports: {
552
+ inspect: planId => {
553
+ const certificate = this.inspectVerifiedCreation(planId);
554
+ if (!certificate)
555
+ throw new Error('created capability requires a fresh independent certificate');
556
+ const prepared = this.inspectPreparedCreation(planId);
557
+ const owner = config.owner;
558
+ if (Object.keys(owner).some(key => prepared.reference.owner[key] !== owner[key])) {
559
+ throw new Error('created capability owner differs from the finite grant');
560
+ }
561
+ return { certificate, artifact: prepared.artifact, owner };
562
+ },
563
+ recheck: async (planId, signal) => {
564
+ await this.inspectPreparedCreationReviewContext(planId, signal);
565
+ if (!this.inspectVerifiedCreation(planId))
566
+ throw new Error('created capability independent certificate changed');
567
+ },
568
+ captureSource: async (planId, signal) => {
569
+ const combined = AbortSignal.any([this.abort.signal, signal, AbortSignal.timeout(120_000)]);
570
+ const prepared = this.inspectPreparedCreation(planId);
571
+ const certificate = this.inspectVerifiedCreation(planId);
572
+ if (!certificate)
573
+ throw new Error('created capability source lacks a fresh certificate');
574
+ const trust = await this.boundTrust();
575
+ combined.throwIfAborted();
576
+ const source = await captureCreationCapabilitySource({ worktree: prepared.plan.worktree,
577
+ baseCommit: prepared.plan.baseCommit, scope: [...prepared.plan.scope].sort(), certificate,
578
+ environment: inheritedEnvironment(trust), signal: combined });
579
+ combined.throwIfAborted();
580
+ const latest = this.inspectVerifiedCreation(planId);
581
+ if (!latest || controlPlaneDigest(latest) !== controlPlaneDigest(certificate)) {
582
+ throw new Error('created capability source changed during capture');
583
+ }
584
+ return source;
585
+ },
586
+ inspectRetained: record => this.withRetainedCreatedCapabilityCurrent(record, () => ({
587
+ certificate: record.certificate, artifact: record.artifact, owner: config.owner
588
+ })),
589
+ recheckRetained: async (record, signal) => {
590
+ signal.throwIfAborted();
591
+ this.withRetainedCreatedCapabilityCurrent(record, () => undefined);
592
+ signal.throwIfAborted();
593
+ },
594
+ withCurrent: (record, callback) => this.withCreatedCapabilityCurrent(record, callback),
595
+ inspectCall: (_record, execution, toolAlias, argumentsJson) => {
596
+ if (!execution.agent)
597
+ return undefined;
598
+ const delivery = current('delivery');
599
+ if (typeof delivery.inspectOwnerForegroundToolCall !== 'function')
600
+ return undefined;
601
+ return delivery.inspectOwnerForegroundToolCall({ agent: execution.agent,
602
+ authorityId: config.owner.authorityId, callId: String(execution.callId),
603
+ toolName: toolAlias, argumentsJson });
604
+ },
605
+ assertCaller: (_record, execution) => {
606
+ if (!execution.agent)
607
+ throw new Error('created capability requires an authenticated owner Agent');
608
+ const delivery = current('delivery');
609
+ const receipt = delivery.validateOwnerAgentForRoute(execution.agent, config.owner.authorityId);
610
+ if (!receipt || Object.keys(config.owner).some(key => receipt[key] !== config.owner[key])) {
611
+ throw new Error('created capability caller is not the current authorized owner');
612
+ }
613
+ },
614
+ } });
615
+ this.creationCapabilityRuntimes.add(capabilities);
616
+ await capabilities.start();
617
+ this.abort.signal.throwIfAborted();
618
+ this.creationCapabilityRuntime = capabilities;
619
+ unsubscribeTaskChanges = evaluation.onTrustedTaskChange(() => this.reconcileCreatedCapabilities());
620
+ }
621
+ catch (error) {
622
+ unsubscribeTaskChanges?.();
623
+ if (capabilities) {
624
+ await capabilities.close();
625
+ this.creationCapabilityRuntimes.delete(capabilities);
626
+ }
627
+ else
628
+ journal.close();
629
+ throw error;
630
+ }
631
+ }
632
+ const sourceBuild = this.config.sourceBuild;
633
+ let nativeRunner;
634
+ try {
635
+ nativeRunner = 'backend' in sourceBuild ? new NativeMacVerifierRunner({ backend: 'macos-seatbelt',
636
+ stateRoot: sourceBuild.stateRoot, authorityDigest: sourceBuild.authorityDigest,
637
+ runtime: sourceBuild.runtime, source: NATIVE_SOURCE_COMPILER, expiresAt: sourceBuild.expiresAt,
638
+ maxRuns: sourceBuild.maxRuns, maxTotalDurationMs: sourceBuild.maxTotalDurationMs,
639
+ maxDurationMs: sourceBuild.timeoutMs, maxOutputBytes: sourceBuild.outputBytes }) : undefined;
640
+ }
641
+ catch (error) {
642
+ unsubscribeTaskChanges?.();
643
+ if (capabilities) {
644
+ await capabilities.close();
645
+ this.creationCapabilityRuntimes.delete(capabilities);
646
+ }
647
+ throw error;
648
+ }
649
+ this.nativeSourceRunner = nativeRunner;
650
+ let runtime;
651
+ try {
652
+ runtime = new SourceJobRuntime({ config: this.config.sourceJobs, build: sourceBuild, statePath: this.config.statePath, store: this.store,
653
+ ports: {
654
+ automations: {
655
+ registerHostExecutor: executor => current('automations').registerHostExecutor(executor),
656
+ reconcileSystem: request => current('automations').reconcileSystem(request),
657
+ reconcileSystemExact: request => current('automations').reconcileSystemExact(request),
658
+ pauseSystemOwned: request => current('automations').pauseSystemOwned(request),
659
+ inspectSystemOwnedActivation: request => current('automations').inspectSystemOwnedActivation(request),
660
+ inspectSystemOwned: request => current('automations').inspectSystemOwned(request),
661
+ },
662
+ delivery: { validateOwnerRoute: request => current('delivery').validateOwnerRoute(request) },
663
+ }, withGapSourceFence: (gapId, owner, callback) => this.taskGaps.withCurrent(gapId, owner, callback),
664
+ assertGrowthRun: (run, gapId, owner, generation) => this.assertSourceGrowthRun(run, gapId, owner, generation),
665
+ ...(this.config.revisionVerifications ? { inspectRevisionParent: this.inspectRevisionParent,
666
+ verifyPreparedRevision: (job, signal) => this.verifyPreparedRevision(job, signal, () => jobsCtx.get('assistantVerifier')) } : {}),
667
+ ...(this.config.revisionRegressions ? { verifyPreparedRevisionRegression: (job, signal) => this.verifyPreparedRevisionRegression(job, signal, () => jobsCtx.get('assistantVerifier')) } : {}),
668
+ ...(this.config.creationVerifications ? { verifyPreparedCreation: (job, signal) => this.verifyPreparedCreation(job, signal, () => jobsCtx.get('assistantVerifier')) } : {}),
669
+ ...(capabilities ? { creationAdoptionEligible: (planId) => capabilities.eligible(planId),
670
+ adoptVerifiedCreation: async (job, signal) => {
671
+ if (!job.planId)
672
+ throw new Error('created capability has no prepared source plan');
673
+ await capabilities.adopt(job.planId, signal);
674
+ } } : {}),
675
+ ...(this.config.sourceApprovals ? { approvePrepared: async (job, signal) => {
676
+ if (!job.planId)
677
+ throw new Error('source job has no prepared plan');
678
+ await this.requestOwnerSourceApproval({ planId: job.planId, signal, expectedTrustDigest: job.intent.trustDigest });
679
+ } } : {}),
680
+ ...(this.config.sourceReleases ? { releasePrepared: async (job, signal) => {
681
+ if (!job.planId)
682
+ throw new Error('source job has no prepared plan');
683
+ await this.requestOwnerSourceRelease({ planId: job.planId, signal, expectedTrustDigest: job.intent.trustDigest });
684
+ } } : {}),
685
+ ...(this.config.sourceReleaseExecution ? { releaseTimeoutMs: this.config.sourceReleaseExecution.timeoutMs,
686
+ advanceReleased: async (job, signal) => {
687
+ if (!job.planId)
688
+ throw new Error('source job has no release plan');
689
+ await this.advanceOwnerSourceRelease({ planId: job.planId, signal, expectedTrustDigest: job.intent.trustDigest });
690
+ } } : {}),
691
+ ...(this.config.sourceAdoptions ? { adoptionTimeoutMs: this.config.sourceAdoptions.timeoutMs,
692
+ adoptReleased: async (job, signal, assertCurrent) => {
693
+ if (!job.planId)
694
+ throw new Error('source job has no released plan');
695
+ await this.adoptOwnerSourceRelease({ sourcePlanId: job.planId, signal, expectedTrustDigest: job.intent.trustDigest, assertCurrent });
696
+ } } : {}),
697
+ trust: () => this.boundTrust(), prepare: (job, signal, assertCurrent) => this.prepareSourceJob(job, signal, assertCurrent),
698
+ ...(nativeRunner ? { inspectNativeQuiescence: (key) => nativeRunner.inspectQuiescence(key) } : {}),
699
+ });
700
+ }
701
+ catch (error) {
702
+ if (this.nativeSourceRunner === nativeRunner)
703
+ this.nativeSourceRunner = undefined;
704
+ await nativeRunner?.close();
705
+ unsubscribeTaskChanges?.();
706
+ if (capabilities) {
707
+ await capabilities.close();
708
+ this.creationCapabilityRuntimes.delete(capabilities);
709
+ }
710
+ throw error;
711
+ }
325
712
  this.sourceRuntimes.add(runtime);
326
713
  const close = async () => {
714
+ unsubscribeTaskChanges?.();
327
715
  if (this.sourceRuntime === runtime)
328
716
  this.sourceRuntime = undefined;
717
+ let closeError;
329
718
  try {
330
719
  await runtime.close();
331
720
  }
332
- finally {
333
- this.sourceRuntimes.delete(runtime);
721
+ catch (error) {
722
+ closeError = error;
723
+ }
724
+ if (this.nativeSourceRunner === nativeRunner)
725
+ this.nativeSourceRunner = undefined;
726
+ this.sourceRuntimes.delete(runtime);
727
+ if (this.creationCapabilityRuntime === capabilities)
728
+ this.creationCapabilityRuntime = undefined;
729
+ const results = await Promise.allSettled([nativeRunner?.close(), capabilities?.close()]);
730
+ if (capabilities)
731
+ this.creationCapabilityRuntimes.delete(capabilities);
732
+ const failures = results.filter((result) => result.status === 'rejected');
733
+ if (closeError !== undefined || failures.length) {
734
+ throw new AggregateError([...(closeError === undefined ? [] : [closeError]), ...failures.map(result => result.reason)], 'source runtime cleanup failed');
334
735
  }
335
736
  };
336
737
  try {
@@ -367,7 +768,9 @@ export class PluginControlPlaneService extends Service {
367
768
  };
368
769
  async boundTrust() {
369
770
  const trust = await loadTrustConfig(this.config.trustPath);
370
- if (resolve(join(this.config.statePath, 'control.sqlite')) !== trust.ledger.path
771
+ const localLedgerPath = resolve(join(this.config.statePath, 'control.sqlite'));
772
+ if ((this.config.adoptionCoordinator === undefined
773
+ ? localLedgerPath !== trust.ledger.path : localLedgerPath === trust.ledger.path)
371
774
  || resolve(this.config.catalogPath) !== trust.catalog.path) {
372
775
  throw new Error('plugin-control-plane: configured ledger/catalog do not match the owner trust binding');
373
776
  }
@@ -474,7 +877,7 @@ export class PluginControlPlaneService extends Service {
474
877
  const key = resolveTrustKey(trust, 'release-authorization', authorization.authority, authorization.keyId);
475
878
  return (await this.store.startSourceRelease({ planId: plan.id, expectedRevision: plan.revision, authorization,
476
879
  resolveAuthority: () => new Ed25519SourceReleaseAuthorizationAuthority(key.publicKeyPem, key.authority, key.keyId),
477
- idempotencyKey: `source-release-authorization:${authorization.authorizationId}`, withSourceFence })).result;
880
+ idempotencyKey: `source-release-authorization:${authorization.authorizationId}`, withSourceFence, trust })).result;
478
881
  })();
479
882
  this.sourceReleaseFlights.add(operation);
480
883
  try {
@@ -581,6 +984,11 @@ export class PluginControlPlaneService extends Service {
581
984
  if (gapId !== plan.gapId)
582
985
  throw new Error('source adoption escaped its owner task');
583
986
  return current(callback);
987
+ },
988
+ withLiveQualificationFence: (planId, callback) => {
989
+ if (!this.liveRuntime)
990
+ throw new Error('live qualification runtime unavailable');
991
+ return this.liveRuntime.withQualificationFence(planId, callback);
584
992
  } });
585
993
  const withSourceFence = (callback) => current(() => store.withOwnerTaskFailureGapAdmission(plan.gapId, callback));
586
994
  try {
@@ -610,6 +1018,652 @@ export class PluginControlPlaneService extends Service {
610
1018
  health() { return this.store.health(); }
611
1019
  canPrepareSource() { return this.config.sourceBuild !== undefined; }
612
1020
  canEnqueueSource() { return this.sourceRuntime?.available() === true && !this.abort.signal.aborted; }
1021
+ /** Host-only durable Usage reader; the caller's binding is always an expectation. */
1022
+ registerSourceGrowthRunProducer = (producer) => {
1023
+ this.abort.signal.throwIfAborted();
1024
+ if (producer?.protocol !== 'assistant-growth-source-run-producer/v1' || typeof producer.inspect !== 'function'
1025
+ || this.growthRunProducer !== undefined || this.growthProviderDraining)
1026
+ throw new Error('plugin-control-plane: source growth run producer is invalid or already registered');
1027
+ this.growthRunProducer = producer;
1028
+ try {
1029
+ this.sourceRuntime?.reconcileQueued();
1030
+ this.reconcileCreatedCapabilities();
1031
+ }
1032
+ catch (error) {
1033
+ this.growthRunProducer = undefined;
1034
+ throw error;
1035
+ }
1036
+ return async () => {
1037
+ if (this.growthRunProducer !== producer)
1038
+ return;
1039
+ this.growthRunProducer = undefined;
1040
+ this.growthProviderDraining = true;
1041
+ try {
1042
+ await this.sourceRuntime?.growthProviderDisposed();
1043
+ await this.creationCapabilityRuntime?.reconcile();
1044
+ }
1045
+ finally {
1046
+ this.growthProviderDraining = false;
1047
+ }
1048
+ };
1049
+ };
1050
+ /** Host readiness nudge after durable Usage becomes available. */
1051
+ reconcileSourceGrowthRuns = () => {
1052
+ this.abort.signal.throwIfAborted();
1053
+ if (this.growthRunProducer !== undefined)
1054
+ this.sourceRuntime?.reconcileQueued();
1055
+ this.reconcileCreatedCapabilities();
1056
+ };
1057
+ reconcileCreatedCapabilities() {
1058
+ const runtime = this.creationCapabilityRuntime;
1059
+ if (!runtime || this.abort.signal.aborted)
1060
+ return;
1061
+ this.creationCapabilityReconcileRequested = true;
1062
+ if (this.creationCapabilityFlights.size)
1063
+ return;
1064
+ this.creationCapabilityReconcileRequested = false;
1065
+ const operation = runtime.reconcile();
1066
+ this.creationCapabilityFlights.add(operation);
1067
+ void operation.catch(error => this.ctx.logger.warn('created capability reconciliation failed', error))
1068
+ .finally(() => {
1069
+ this.creationCapabilityFlights.delete(operation);
1070
+ if (this.creationCapabilityReconcileRequested)
1071
+ this.reconcileCreatedCapabilities();
1072
+ });
1073
+ }
1074
+ /** Content-free Host diagnostics; never register adoption as a model tool. */
1075
+ inspectCreatedCapability = (planId) => {
1076
+ this.abort.signal.throwIfAborted();
1077
+ return this.creationCapabilityRuntime?.inspectStatus(planId);
1078
+ };
1079
+ /** Exact immutable adoption and actual call evidence; no arguments, results or quality rating. */
1080
+ inspectCreatedCapabilityCalls = (planId) => {
1081
+ this.abort.signal.throwIfAborted();
1082
+ return this.creationCapabilityRuntime?.inspectCallEvidence(planId) ?? [];
1083
+ };
1084
+ /** Host-only historical source data. It grants no continuation, activation or replacement. */
1085
+ inspectCreatedCapabilitySource = (planId) => {
1086
+ this.abort.signal.throwIfAborted();
1087
+ const owner = this.config.creationCapabilities?.owner;
1088
+ const delivery = this.ctx.get('assistantDelivery', false);
1089
+ if (!owner || !delivery || typeof delivery.validateOwnerRoute !== 'function')
1090
+ return undefined;
1091
+ try {
1092
+ const before = delivery.validateOwnerRoute(owner);
1093
+ const snapshot = this.creationCapabilityRuntime?.inspectSourceEvidence(planId);
1094
+ if (!snapshot)
1095
+ return undefined;
1096
+ this.assertCreatedCapabilityAssociationSource(snapshot.record);
1097
+ const { plan, reference } = this.store.getRetainedPreparedCreation(planId);
1098
+ if (!isSourceOwnerContinuation(before, reference.owner)
1099
+ || snapshot.archive.source.baseCommit !== plan.baseCommit
1100
+ || controlPlaneDigest(snapshot.archive.source.scope) !== controlPlaneDigest([...plan.scope].sort())
1101
+ || controlPlaneDigest(before) !== controlPlaneDigest(delivery.validateOwnerRoute(owner)))
1102
+ return undefined;
1103
+ const source = snapshot.archive.source;
1104
+ return Object.freeze({ ...snapshot.archive, source: Object.freeze({ ...source,
1105
+ scope: Object.freeze([...source.scope]), entries: Object.freeze(source.entries.map(entry => Object.freeze({ ...entry }))),
1106
+ files: Object.freeze(source.files.map(file => Object.freeze({ ...file }))),
1107
+ }) });
1108
+ }
1109
+ catch {
1110
+ return undefined;
1111
+ }
1112
+ };
1113
+ inspectRevisionParent = (planId) => {
1114
+ const archive = this.inspectCreatedCapabilitySource(planId);
1115
+ const certificate = this.store.getCreationVerification(planId);
1116
+ const grant = this.config.sourceJobs?.revision;
1117
+ if (!archive || !certificate || !grant || !certificate.plan.name.startsWith(grant.namePrefix)
1118
+ || archive.certificateDigest !== controlPlaneDigest(certificate) || archive.artifactSha256 !== certificate.plan.artifactSha256) {
1119
+ throw new Error('adopted revision parent source unavailable or changed');
1120
+ }
1121
+ return { name: certificate.plan.name, certificate, source: archive.source,
1122
+ parent: { planId, certificateDigest: archive.certificateDigest, artifactSha256: archive.artifactSha256,
1123
+ sourceArchiveDigest: controlPlaneDigest(archive), sourceDigest: archive.source.digest } };
1124
+ };
1125
+ /** Bounded current-owner inventory. These identities are hints, never causal evidence or use grants. */
1126
+ inspectCreatedCapabilityRevisionTargets = () => {
1127
+ if (!this.inspectSourceRevisionAcceptanceAuthority())
1128
+ return [];
1129
+ const targets = [];
1130
+ for (const plan of this.store.listPreparedSourcePlans().filter(plan => plan.mode === 'prepared-create').slice(-64)) {
1131
+ try {
1132
+ const parent = this.inspectRevisionParent(plan.id);
1133
+ targets.push({ parentPlanId: plan.id, name: parent.name, parentSourceDigest: parent.source.digest });
1134
+ }
1135
+ catch { /* an unavailable archive is not a revision target */ }
1136
+ }
1137
+ return Object.freeze(targets.map(target => Object.freeze(target)));
1138
+ };
1139
+ getSourceRevisionNamespace = () => {
1140
+ const policy = this.inspectSourceRevisionAcceptanceAuthority();
1141
+ return policy ? { namePrefix: policy.namePrefix } : undefined;
1142
+ };
1143
+ /** Host-only current association of exact adopted call versions with authenticated task heads. */
1144
+ inspectCreatedCapabilityTaskAssociations = (planId) => {
1145
+ this.abort.signal.throwIfAborted();
1146
+ const snapshot = this.creationCapabilityRuntime?.inspectAssociationEvidence(planId);
1147
+ if (!snapshot || !this.config.creationCapabilities)
1148
+ return [];
1149
+ const owner = this.config.creationCapabilities.owner;
1150
+ const evaluation = this.ctx.get('assistantEvaluation', false);
1151
+ const delivery = this.ctx.get('assistantDelivery', false);
1152
+ if (!evaluation || !delivery)
1153
+ return [];
1154
+ try {
1155
+ const historical = this.store.getRetainedPreparedCreation(planId);
1156
+ return inspectCreationCapabilityTaskAssociations({ ...snapshot, owner, evaluation, delivery,
1157
+ triggerInboxId: historical.reference.projection.subjectRef,
1158
+ readSourceCurrent: () => {
1159
+ this.assertCreatedCapabilityAssociationSource(snapshot.record);
1160
+ const current = this.taskGaps.inspectCurrent(historical.plan.gapId, historical.reference.owner);
1161
+ return { projection: current.canonical.projection, scopeWatermark: current.canonical.scopeWatermark };
1162
+ } });
1163
+ }
1164
+ catch {
1165
+ return [];
1166
+ }
1167
+ };
1168
+ /** Exact historical certificate and source check without nesting an Evaluation writer fence. */
1169
+ assertCreatedCapabilityAssociationSource(record) {
1170
+ this.abort.signal.throwIfAborted();
1171
+ const receipt = record.receipt;
1172
+ const config = this.config.creationVerifications;
1173
+ const ownerGrant = this.config.creationCapabilities?.owner;
1174
+ if (!receipt || !config || !ownerGrant
1175
+ || !verifyPluginCreationVerificationCertificate(record.certificate, config.authority, config.publicKey, receipt.adoptedAt))
1176
+ throw new Error('created capability association certificate invalid');
1177
+ const { plan, job, reference } = this.store.getRetainedPreparedCreation(record.planId);
1178
+ const certificate = this.store.getCreationVerification(record.planId);
1179
+ const growth = plan.creation?.growthRun;
1180
+ const artifactSha256 = createHash('sha256').update(record.artifact).digest('hex');
1181
+ if (!certificate || controlPlaneDigest(certificate) !== controlPlaneDigest(record.certificate)
1182
+ || !growth || !plan.creation || !job.intent.creation?.growthRun || !plan.sourceCheck || !plan.preparedEvidence
1183
+ || plan.id !== receipt.planId || plan.digest !== receipt.planDigest
1184
+ || plan.name !== record.certificate.plan.name || plan.generatorDigest !== record.certificate.plan.generatorDigest
1185
+ || plan.sourceCheck.treeDigest !== record.certificate.plan.sourceTreeDigest
1186
+ || plan.sourceCheck.patchDigest !== record.certificate.plan.sourcePatchDigest
1187
+ || plan.preparedEvidence.pack.sha256 !== record.certificate.plan.artifactSha256
1188
+ || plan.preparedEvidence.pack.sizeBytes !== record.certificate.plan.artifactBytes
1189
+ || artifactSha256 !== record.certificate.plan.artifactSha256
1190
+ || record.artifact.length !== record.certificate.plan.artifactBytes
1191
+ || controlPlaneDigest(reference) !== record.certificate.source.referenceDigest
1192
+ || controlPlaneDigest(reference.owner) !== record.certificate.source.ownerDigest
1193
+ || sourceGrowthRunDigest(growth) !== record.certificate.source.growthRunDigest
1194
+ || sourceGrowthRunDigest(job.intent.creation.growthRun) !== record.certificate.source.growthRunDigest
1195
+ || controlPlaneDigest(growth.model) !== controlPlaneDigest(record.certificate.model)
1196
+ || controlPlaneDigest(growth.creationAcceptance) !== controlPlaneDigest(config.authority)
1197
+ || controlPlaneDigest(plan.creation.grant) !== controlPlaneDigest(this.config.sourceJobs?.creation)
1198
+ || Object.keys(ownerGrant).some(key => reference.owner[key]
1199
+ !== ownerGrant[key]))
1200
+ throw new Error('created capability association source changed');
1201
+ }
1202
+ withCreatedCapabilityCurrent(record, callback) {
1203
+ this.abort.signal.throwIfAborted();
1204
+ if (record.status === 'active' && record.receipt?.protocol === 'dsh-created-capability-adoption/v2') {
1205
+ return this.withRetainedCreatedCapabilityCurrent(record, callback);
1206
+ }
1207
+ const certificate = this.inspectVerifiedCreation(record.planId);
1208
+ if (!certificate || controlPlaneDigest(certificate) !== controlPlaneDigest(record.certificate)) {
1209
+ throw new Error('created capability certificate or current source changed');
1210
+ }
1211
+ return this.withPreparedCreationFence({ planId: record.planId, planDigest: certificate.plan.digest,
1212
+ artifactSha256: certificate.plan.artifactSha256, growthRunDigest: certificate.source.growthRunDigest,
1213
+ referenceDigest: certificate.source.referenceDigest }, callback);
1214
+ }
1215
+ /** Historical signed evidence and immutable job are checked under the current canonical owner writer fence. */
1216
+ withRetainedCreatedCapabilityCurrent(record, callback) {
1217
+ this.abort.signal.throwIfAborted();
1218
+ const receipt = record.receipt;
1219
+ const config = this.config.creationVerifications;
1220
+ if (!receipt || receipt.protocol !== 'dsh-created-capability-adoption/v2' || !config
1221
+ || !verifyPluginCreationVerificationCertificate(record.certificate, config.authority, config.publicKey, receipt.adoptedAt)) {
1222
+ throw new Error('created capability retained certificate is invalid');
1223
+ }
1224
+ const historical = this.store.getRetainedPreparedCreation(record.planId);
1225
+ return this.taskGaps.withCurrent(historical.plan.gapId, historical.reference.owner, () => {
1226
+ this.abort.signal.throwIfAborted();
1227
+ const { plan, job, reference } = this.store.getRetainedPreparedCreation(record.planId);
1228
+ const certificate = this.store.getCreationVerification(record.planId);
1229
+ const growth = plan.creation?.growthRun;
1230
+ const creation = plan.creation;
1231
+ const ownerGrant = this.config.creationCapabilities?.owner;
1232
+ const artifactSha256 = createHash('sha256').update(record.artifact).digest('hex');
1233
+ if (!certificate || controlPlaneDigest(certificate) !== controlPlaneDigest(record.certificate)
1234
+ || !growth || !creation || !ownerGrant || !job.intent.creation?.growthRun
1235
+ || !plan.sourceCheck || !plan.preparedEvidence
1236
+ || plan.id !== receipt.planId || plan.digest !== receipt.planDigest
1237
+ || plan.name !== record.certificate.plan.name || plan.generatorDigest !== record.certificate.plan.generatorDigest
1238
+ || plan.sourceCheck.treeDigest !== record.certificate.plan.sourceTreeDigest
1239
+ || plan.sourceCheck.patchDigest !== record.certificate.plan.sourcePatchDigest
1240
+ || plan.preparedEvidence.pack.sha256 !== record.certificate.plan.artifactSha256
1241
+ || plan.preparedEvidence.pack.sizeBytes !== record.certificate.plan.artifactBytes
1242
+ || artifactSha256 !== record.certificate.plan.artifactSha256
1243
+ || record.artifact.length !== record.certificate.plan.artifactBytes
1244
+ || controlPlaneDigest(reference) !== record.certificate.source.referenceDigest
1245
+ || controlPlaneDigest(reference.owner) !== record.certificate.source.ownerDigest
1246
+ || sourceGrowthRunDigest(growth) !== record.certificate.source.growthRunDigest
1247
+ || sourceGrowthRunDigest(job.intent.creation.growthRun) !== record.certificate.source.growthRunDigest
1248
+ || controlPlaneDigest(growth.model) !== controlPlaneDigest(record.certificate.model)
1249
+ || controlPlaneDigest(growth.creationAcceptance) !== controlPlaneDigest(config.authority)
1250
+ || controlPlaneDigest(creation.grant) !== controlPlaneDigest(this.config.sourceJobs?.creation)
1251
+ || Object.keys(ownerGrant).some(key => reference.owner[key]
1252
+ !== ownerGrant[key])) {
1253
+ throw new Error('created capability retained source binding changed');
1254
+ }
1255
+ return callback();
1256
+ });
1257
+ }
1258
+ assertSourceGrowthRun(expected, gapId, owner, generation) {
1259
+ validateSourceGrowthRunBinding(expected);
1260
+ const producer = this.growthRunProducer;
1261
+ if (!producer)
1262
+ throw new SourceGrowthRunUnavailableError('source growth run producer unavailable');
1263
+ const actual = producer.inspect({ runId: expected.runId, intentDigest: expected.intentDigest });
1264
+ if (!actual)
1265
+ throw new Error('source growth run is absent or no longer current');
1266
+ validateSourceGrowthRunBinding(actual);
1267
+ if (sourceGrowthRunDigest(actual) !== sourceGrowthRunDigest(expected)
1268
+ || expected.ownerDigest !== controlPlaneDigest(owner))
1269
+ throw new Error('source growth run immutable binding changed');
1270
+ const policy = this.config.creationVerifications?.authority;
1271
+ if (expected.revisionAcceptance !== undefined && (this.config.revisionVerifications === undefined
1272
+ || controlPlaneDigest(expected.revisionAcceptance) !== controlPlaneDigest(this.config.revisionVerifications.authority))) {
1273
+ throw new Error('source growth run revision acceptance policy changed or was not pinned before authoring');
1274
+ }
1275
+ if ((expected.revisionRegressionAcceptance !== undefined || (this.config.revisionRegressions && expected.revisionAcceptance))
1276
+ && (this.config.revisionRegressions === undefined || expected.revisionRegressionAcceptance === undefined
1277
+ || controlPlaneDigest(expected.revisionRegressionAcceptance) !== controlPlaneDigest(this.config.revisionRegressions.authority))) {
1278
+ throw new Error('source growth run regression policy changed or was not pinned before authoring');
1279
+ }
1280
+ if (expected.creationAcceptance !== undefined
1281
+ && (policy === undefined || controlPlaneDigest(policy) !== controlPlaneDigest(expected.creationAcceptance))) {
1282
+ throw new Error('source growth run creation acceptance policy changed or was not pinned before authoring');
1283
+ }
1284
+ const reference = this.store.getOwnerTaskFailureReference(gapId);
1285
+ if (!reference || expected.source.outcomeId !== reference.outcomeId
1286
+ || expected.source.sourceDigest !== reference.sourceDigest
1287
+ || controlPlaneDigest(expected.source.projection) !== controlPlaneDigest(reference.projection)
1288
+ || expected.ownerDigest !== controlPlaneDigest(reference.owner))
1289
+ throw new Error('source growth run task reference changed');
1290
+ const current = this.taskGaps.inspectCurrent(gapId, owner);
1291
+ if (expected.modelOrigin === 'inherited-owner-task'
1292
+ && (current.source.modelSelectionState !== 'frozen'
1293
+ || controlPlaneDigest(current.source.modelSelection) !== controlPlaneDigest(expected.model))) {
1294
+ throw new Error('source growth run inherited model changed');
1295
+ }
1296
+ if (Date.now() >= expected.expiresAt || (generation && Date.now() >= expected.generationDeadlineAt)) {
1297
+ throw new Error('source growth run window expired');
1298
+ }
1299
+ }
1300
+ /** Private Host handoff. Never register this package reader as a model tool. */
1301
+ inspectPreparedCreation = (planId) => {
1302
+ this.abort.signal.throwIfAborted();
1303
+ const plan = this.store.getSourcePlan(planId);
1304
+ const reference = this.store.getOwnerTaskFailureReference(plan.gapId);
1305
+ if (!reference || plan.mode !== 'prepared-create' || plan.status !== 'pending-approval'
1306
+ || Date.now() >= plan.expiresAt || !plan.creation || Date.now() >= plan.creation.grant.expiresAt) {
1307
+ throw new Error('prepared creation package is unavailable for independent verification');
1308
+ }
1309
+ return this.taskGaps.withCurrent(plan.gapId, reference.owner, () => {
1310
+ this.abort.signal.throwIfAborted();
1311
+ const job = this.store.getPreparedSourceJob(plan.id);
1312
+ if (!plan.creation?.growthRun || !job.intent.creation?.growthRun)
1313
+ throw new Error('prepared creation lacks a frozen source growth run');
1314
+ this.assertSourceGrowthRun(plan.creation.growthRun, plan.gapId, reference.owner, false);
1315
+ return {
1316
+ protocol: 'dsh-prepared-creation/v1',
1317
+ plan: structuredClone(plan), job: structuredClone(job), reference: structuredClone(reference),
1318
+ source: this.taskGaps.inspectCurrent(plan.gapId, reference.owner),
1319
+ artifact: this.store.readPreparedSourceArtifact(plan.id),
1320
+ };
1321
+ });
1322
+ };
1323
+ /** Host-only pre-author policy reference; never grants candidate or model authority. */
1324
+ inspectSourceCreationAcceptanceAuthority = () => {
1325
+ const policy = this.config.creationVerifications?.authority;
1326
+ if (this.abort.signal.aborted || !policy || Date.now() >= policy.expiresAt
1327
+ || Date.now() >= (this.config.sourceJobs?.creation?.expiresAt ?? 0)
1328
+ || Date.now() >= (this.config.sourceJobs?.expiresAt ?? 0))
1329
+ return undefined;
1330
+ return structuredClone(policy);
1331
+ };
1332
+ /** Synchronous Evaluation writer fence for an exact independently reviewed creation. */
1333
+ withPreparedCreationFence = (input, callback) => {
1334
+ this.abort.signal.throwIfAborted();
1335
+ const policy = this.inspectSourceCreationAcceptanceAuthority();
1336
+ if (!policy)
1337
+ throw new Error('creation verification authority unavailable');
1338
+ const plan = this.store.getSourcePlan(input.planId);
1339
+ const reference = this.store.getOwnerTaskFailureReference(plan.gapId);
1340
+ if (!reference)
1341
+ throw new Error('creation verification source unavailable');
1342
+ return this.taskGaps.withCurrent(plan.gapId, reference.owner, () => {
1343
+ this.abort.signal.throwIfAborted();
1344
+ const current = this.store.getSourcePlan(input.planId);
1345
+ const job = this.store.getPreparedSourceJob(input.planId);
1346
+ if (current.digest !== input.planDigest || current.mode !== 'prepared-create' || !current.creation?.growthRun
1347
+ || !job.intent.creation?.growthRun || !current.sourceCheck || !current.preparedEvidence
1348
+ || current.preparedEvidence.pack.sha256 !== input.artifactSha256
1349
+ || controlPlaneDigest(reference) !== input.referenceDigest
1350
+ || sourceGrowthRunDigest(current.creation.growthRun) !== input.growthRunDigest
1351
+ || sourceGrowthRunDigest(job.intent.creation.growthRun) !== input.growthRunDigest
1352
+ || !current.creation.growthRun.creationAcceptance
1353
+ || controlPlaneDigest(current.creation.growthRun.creationAcceptance) !== controlPlaneDigest(policy)
1354
+ || controlPlaneDigest(current.creation.grant) !== controlPlaneDigest(this.config.sourceJobs?.creation)) {
1355
+ throw new Error('creation verification exact source binding changed');
1356
+ }
1357
+ this.assertSourceGrowthRun(current.creation.growthRun, current.gapId, reference.owner, false);
1358
+ const artifact = this.store.readPreparedSourceArtifact(input.planId);
1359
+ if (createHash('sha256').update(artifact).digest('hex') !== input.artifactSha256) {
1360
+ throw new Error('creation verification artifact changed');
1361
+ }
1362
+ return callback();
1363
+ });
1364
+ };
1365
+ /** Independent reviewer reads the actual checked tree through a disposable Git index. */
1366
+ inspectPreparedCreationReviewContext = async (planId, signal) => {
1367
+ const combined = AbortSignal.any([this.abort.signal, signal, AbortSignal.timeout(120_000)]);
1368
+ combined.throwIfAborted();
1369
+ const prepared = this.inspectPreparedCreation(planId);
1370
+ const current = () => this.withPreparedCreationFence({ planId, planDigest: prepared.plan.digest,
1371
+ artifactSha256: prepared.plan.preparedEvidence.pack.sha256,
1372
+ growthRunDigest: sourceGrowthRunDigest(prepared.plan.creation.growthRun),
1373
+ referenceDigest: controlPlaneDigest(prepared.reference) }, () => undefined);
1374
+ current();
1375
+ const trust = await this.boundTrust();
1376
+ const environment = inheritedEnvironment(trust);
1377
+ const job = prepared.job;
1378
+ await verifyCreatedPluginWorkspace({ worktree: prepared.plan.worktree, baseCommit: prepared.plan.baseCommit,
1379
+ name: prepared.plan.name, environment, signal: combined, assertCurrent: current,
1380
+ creation: prepared.plan.creation, files: job.intent.files });
1381
+ const review = await inspectPreparedCreationPatch(prepared.plan, environment, combined);
1382
+ current();
1383
+ return review;
1384
+ };
1385
+ /** Signed behavior evidence over stored pack/checks. Adoption must await async review-context recheck. */
1386
+ inspectVerifiedCreation = (planId) => {
1387
+ const certificate = this.store.getCreationVerification(planId);
1388
+ if (!certificate)
1389
+ return undefined;
1390
+ const config = this.config.creationVerifications;
1391
+ if (!config || !verifyPluginCreationVerificationCertificate(certificate, config.authority, config.publicKey))
1392
+ return undefined;
1393
+ this.withPreparedCreationFence({ planId, planDigest: certificate.plan.digest,
1394
+ artifactSha256: certificate.plan.artifactSha256, growthRunDigest: certificate.source.growthRunDigest,
1395
+ referenceDigest: certificate.source.referenceDigest }, () => {
1396
+ const plan = this.store.getSourcePlan(planId);
1397
+ if (!plan.sourceCheck || !plan.preparedEvidence || certificate.plan.name !== plan.name
1398
+ || certificate.plan.generatorDigest !== plan.generatorDigest
1399
+ || certificate.plan.sourceTreeDigest !== plan.sourceCheck.treeDigest
1400
+ || certificate.plan.sourcePatchDigest !== plan.sourceCheck.patchDigest
1401
+ || certificate.plan.artifactBytes !== plan.preparedEvidence.pack.sizeBytes) {
1402
+ throw new Error('creation verification exact plan evidence changed');
1403
+ }
1404
+ });
1405
+ return structuredClone(certificate);
1406
+ };
1407
+ /** Private Host diagnostic; no cases, prompts, source bytes or verifier output. */
1408
+ inspectCreationVerification = (planId) => {
1409
+ this.abort.signal.throwIfAborted();
1410
+ const plan = this.store.getSourcePlan(planId);
1411
+ const reference = this.store.getOwnerTaskFailureReference(plan.gapId);
1412
+ if (!reference || plan.mode !== 'prepared-create')
1413
+ throw new Error('creation verification owner source unavailable');
1414
+ return this.taskGaps.withCurrent(plan.gapId, reference.owner, () => {
1415
+ const current = this.store.getSourcePlan(planId);
1416
+ if (current.mode !== 'prepared-create' || current.digest !== plan.digest) {
1417
+ throw new Error('creation verification diagnostic plan changed');
1418
+ }
1419
+ return this.store.inspectCreationVerificationRecord(planId);
1420
+ });
1421
+ };
1422
+ /** Private Host handoff. Never register this package reader as a model tool. */
1423
+ inspectPreparedRevision = (planId) => {
1424
+ this.abort.signal.throwIfAborted();
1425
+ const plan = this.store.getSourcePlan(planId);
1426
+ const reference = this.store.getOwnerTaskFailureReference(plan.gapId);
1427
+ if (!reference || plan.mode !== 'prepared-revise' || plan.status !== 'pending-approval'
1428
+ || Date.now() >= plan.expiresAt || !plan.sourceRevision || Date.now() >= plan.sourceRevision.grant.expiresAt) {
1429
+ throw new Error('prepared revision package is unavailable for independent verification');
1430
+ }
1431
+ return this.taskGaps.withCurrent(plan.gapId, reference.owner, () => {
1432
+ this.abort.signal.throwIfAborted();
1433
+ const job = this.store.getPreparedRevisionJob(plan.id);
1434
+ if (!plan.sourceRevision?.growthRun || !job.intent.revision?.growthRun)
1435
+ throw new Error('prepared revision lacks a frozen source growth run');
1436
+ this.assertSourceGrowthRun(plan.sourceRevision.growthRun, plan.gapId, reference.owner, false);
1437
+ this.inspectRevisionParent(plan.sourceRevision.parent.planId);
1438
+ return {
1439
+ protocol: 'dsh-prepared-revision/v1',
1440
+ plan: structuredClone(plan), job: structuredClone(job), reference: structuredClone(reference),
1441
+ source: this.taskGaps.inspectCurrent(plan.gapId, reference.owner),
1442
+ artifact: this.store.readPreparedSourceArtifact(plan.id),
1443
+ };
1444
+ });
1445
+ };
1446
+ /** Host-only pre-author policy reference; never grants candidate or model authority. */
1447
+ inspectSourceRevisionAcceptanceAuthority = () => {
1448
+ const policy = this.config.revisionVerifications?.authority;
1449
+ if (this.abort.signal.aborted || !policy || Date.now() >= policy.expiresAt
1450
+ || Date.now() >= (this.config.sourceJobs?.revision?.expiresAt ?? 0)
1451
+ || Date.now() >= (this.config.sourceJobs?.expiresAt ?? 0))
1452
+ return undefined;
1453
+ return structuredClone(policy);
1454
+ };
1455
+ /** Host-only pre-author reference, separate from candidate verification and execution grants. */
1456
+ inspectSourceRevisionRegressionAcceptanceAuthority = () => {
1457
+ const policy = this.config.revisionRegressions?.authority;
1458
+ if (!policy)
1459
+ return undefined;
1460
+ if (this.abort.signal.aborted || Date.now() >= policy.expiresAt
1461
+ || !this.inspectSourceRevisionAcceptanceAuthority()) {
1462
+ throw new Error('configured revision regression authority unavailable');
1463
+ }
1464
+ return structuredClone(policy);
1465
+ };
1466
+ /** Immutable artifacts only. Historical parent permission never grants a new invocation. */
1467
+ inspectPreparedRevisionRegression = (planId) => {
1468
+ const prepared = this.inspectPreparedRevision(planId);
1469
+ const certificate = this.inspectVerifiedRevision(planId);
1470
+ const policy = this.inspectSourceRevisionRegressionAcceptanceAuthority();
1471
+ const run = prepared.plan.sourceRevision.growthRun;
1472
+ const source = this.store.readRevisionSource(planId);
1473
+ if (!certificate || !source || !policy || !run.revisionRegressionAcceptance
1474
+ || controlPlaneDigest(run.revisionRegressionAcceptance) !== controlPlaneDigest(policy)) {
1475
+ throw new Error('regression requires fresh independently verified candidate and pre-author policy');
1476
+ }
1477
+ const parent = this.inspectRevisionParent(certificate.parent.planId);
1478
+ const evidence = this.creationCapabilityRuntime?.inspectSourceEvidence(parent.parent.planId);
1479
+ if (!evidence || !evidence.record.receipt
1480
+ || controlPlaneDigest(evidence.archive) !== parent.parent.sourceArchiveDigest
1481
+ || controlPlaneDigest(evidence.record.certificate) !== parent.parent.certificateDigest
1482
+ || createHash('sha256').update(evidence.record.artifact).digest('hex') !== parent.parent.artifactSha256
1483
+ || evidence.record.artifact.length !== parent.certificate.plan.artifactBytes) {
1484
+ throw new Error('regression parent immutable artifact unavailable or changed');
1485
+ }
1486
+ this.inspectRevisionParent(parent.parent.planId);
1487
+ return { protocol: 'dsh-prepared-revision-regression/v1',
1488
+ plan: prepared.plan, job: prepared.job, reference: prepared.reference,
1489
+ candidate: { certificate, artifact: Buffer.from(prepared.artifact), sourceDigest: source.digest },
1490
+ parent: { binding: parent.parent, certificate: parent.certificate, artifact: Buffer.from(evidence.record.artifact) } };
1491
+ };
1492
+ withPreparedRevisionRegressionFence = (input, callback) => {
1493
+ // Resolve read-only snapshots before acquiring Evaluation's non-reentrant
1494
+ // writer transaction; inside it, compare durable data without another fence.
1495
+ const snapshot = this.inspectPreparedRevisionRegression(input.planId);
1496
+ return this.withPreparedRevisionFence(input, () => {
1497
+ const certificate = this.store.getRevisionVerification(input.planId);
1498
+ const source = this.store.readRevisionSource(input.planId);
1499
+ const policy = this.inspectSourceRevisionRegressionAcceptanceAuthority();
1500
+ const candidatePolicy = this.config.revisionVerifications;
1501
+ if (!certificate || !source || !policy || !candidatePolicy
1502
+ || !verifyPluginRevisionVerificationCertificate(certificate, candidatePolicy.authority, candidatePolicy.publicKey)
1503
+ || controlPlaneDigest(certificate) !== input.candidateVerificationDigest
1504
+ || controlPlaneDigest(snapshot.candidate.certificate) !== input.candidateVerificationDigest
1505
+ || source.digest !== input.sourceDigest || snapshot.candidate.sourceDigest !== input.sourceDigest
1506
+ || controlPlaneDigest(policy) !== input.regressionAuthorityDigest
1507
+ || controlPlaneDigest(snapshot.plan.sourceRevision.growthRun.revisionRegressionAcceptance) !== input.regressionAuthorityDigest
1508
+ || controlPlaneDigest(snapshot.parent.binding) !== input.parentDigest) {
1509
+ throw new Error('regression parent, candidate or policy changed');
1510
+ }
1511
+ return callback();
1512
+ });
1513
+ };
1514
+ inspectVerifiedRevisionRegression = (planId) => {
1515
+ const certificate = this.store.getRevisionRegression(planId), policy = this.config.revisionRegressions;
1516
+ if (!certificate || !policy || !verifyPluginRevisionRegressionCertificate(certificate, policy.authority, policy.publicKey))
1517
+ return undefined;
1518
+ const prepared = this.inspectPreparedRevisionRegression(planId);
1519
+ this.withPreparedRevisionRegressionFence({ planId, planDigest: certificate.plan.digest, artifactSha256: certificate.plan.artifactSha256,
1520
+ growthRunDigest: certificate.source.growthRunDigest, referenceDigest: certificate.source.referenceDigest,
1521
+ parentDigest: controlPlaneDigest(certificate.parent), candidateVerificationDigest: certificate.candidateVerificationDigest,
1522
+ sourceDigest: certificate.sourceDigest, regressionAuthorityDigest: controlPlaneDigest(certificate.authority) }, () => {
1523
+ if (certificate.contractDigest !== prepared.parent.certificate.contractDigest
1524
+ || controlPlaneDigest(certificate.plan) !== controlPlaneDigest(prepared.candidate.certificate.plan)
1525
+ || controlPlaneDigest(certificate.model) !== controlPlaneDigest(prepared.candidate.certificate.model)) {
1526
+ throw new Error('regression result does not match original held-out and exact candidate');
1527
+ }
1528
+ });
1529
+ return structuredClone(certificate);
1530
+ };
1531
+ /** Synchronous Evaluation writer fence for an exact independently reviewed creation. */
1532
+ withPreparedRevisionFence = (input, callback) => {
1533
+ this.abort.signal.throwIfAborted();
1534
+ const policy = this.inspectSourceRevisionAcceptanceAuthority();
1535
+ if (!policy)
1536
+ throw new Error('revision verification authority unavailable');
1537
+ const plan = this.store.getSourcePlan(input.planId);
1538
+ const reference = this.store.getOwnerTaskFailureReference(plan.gapId);
1539
+ if (!reference)
1540
+ throw new Error('revision verification source unavailable');
1541
+ return this.taskGaps.withCurrent(plan.gapId, reference.owner, () => {
1542
+ this.abort.signal.throwIfAborted();
1543
+ const current = this.store.getSourcePlan(input.planId);
1544
+ const job = this.store.getPreparedRevisionJob(input.planId);
1545
+ if (current.digest !== input.planDigest || current.mode !== 'prepared-revise' || !current.sourceRevision?.growthRun
1546
+ || !job.intent.revision?.growthRun || !current.sourceCheck || !current.preparedEvidence
1547
+ || current.preparedEvidence.pack.sha256 !== input.artifactSha256
1548
+ || controlPlaneDigest(reference) !== input.referenceDigest
1549
+ || sourceGrowthRunDigest(current.sourceRevision.growthRun) !== input.growthRunDigest
1550
+ || sourceGrowthRunDigest(job.intent.revision.growthRun) !== input.growthRunDigest
1551
+ || !current.sourceRevision.growthRun.revisionAcceptance
1552
+ || controlPlaneDigest(current.sourceRevision.growthRun.revisionAcceptance) !== controlPlaneDigest(policy)
1553
+ || controlPlaneDigest(current.sourceRevision.grant) !== controlPlaneDigest(this.config.sourceJobs?.revision)
1554
+ || controlPlaneDigest(current.sourceRevision.parent) !== input.parentDigest
1555
+ || controlPlaneDigest(this.inspectRevisionParent(current.sourceRevision.parent.planId).parent) !== input.parentDigest) {
1556
+ throw new Error('revision verification exact source binding changed');
1557
+ }
1558
+ this.assertSourceGrowthRun(current.sourceRevision.growthRun, current.gapId, reference.owner, false);
1559
+ const artifact = this.store.readPreparedSourceArtifact(input.planId);
1560
+ if (createHash('sha256').update(artifact).digest('hex') !== input.artifactSha256) {
1561
+ throw new Error('revision verification artifact changed');
1562
+ }
1563
+ return callback();
1564
+ });
1565
+ };
1566
+ /** Independent reviewer reads the actual checked tree through a disposable Git index. */
1567
+ inspectPreparedRevisionReviewContext = async (planId, signal) => {
1568
+ const combined = AbortSignal.any([this.abort.signal, signal, AbortSignal.timeout(120_000)]);
1569
+ combined.throwIfAborted();
1570
+ const prepared = this.inspectPreparedRevision(planId);
1571
+ const current = () => this.withPreparedRevisionFence({ planId, planDigest: prepared.plan.digest,
1572
+ artifactSha256: prepared.plan.preparedEvidence.pack.sha256,
1573
+ growthRunDigest: sourceGrowthRunDigest(prepared.plan.sourceRevision.growthRun),
1574
+ referenceDigest: controlPlaneDigest(prepared.reference), parentDigest: controlPlaneDigest(prepared.plan.sourceRevision.parent) }, () => undefined);
1575
+ current();
1576
+ const trust = await this.boundTrust();
1577
+ const environment = inheritedEnvironment(trust);
1578
+ const job = prepared.job;
1579
+ const parent = this.inspectRevisionParent(prepared.plan.sourceRevision.parent.planId);
1580
+ await verifyRevisedPluginWorkspace({ worktree: prepared.plan.worktree, baseCommit: prepared.plan.baseCommit,
1581
+ name: prepared.plan.name, environment, signal: combined, assertCurrent: current,
1582
+ revision: prepared.plan.sourceRevision, parentSource: parent.source, parentCertificate: parent.certificate, files: job.intent.files });
1583
+ const review = await inspectPreparedCreationPatch(prepared.plan, environment, combined);
1584
+ current();
1585
+ return review;
1586
+ };
1587
+ /** Signed behavior evidence over stored pack/checks. Adoption must await async review-context recheck. */
1588
+ inspectVerifiedRevision = (planId) => {
1589
+ const certificate = this.store.getRevisionVerification(planId);
1590
+ if (!certificate)
1591
+ return undefined;
1592
+ const config = this.config.revisionVerifications;
1593
+ if (!config || !verifyPluginRevisionVerificationCertificate(certificate, config.authority, config.publicKey))
1594
+ return undefined;
1595
+ this.withPreparedRevisionFence({ planId, planDigest: certificate.plan.digest,
1596
+ artifactSha256: certificate.plan.artifactSha256, growthRunDigest: certificate.source.growthRunDigest,
1597
+ referenceDigest: certificate.source.referenceDigest, parentDigest: controlPlaneDigest(certificate.parent) }, () => {
1598
+ const plan = this.store.getSourcePlan(planId);
1599
+ if (!plan.sourceCheck || !plan.preparedEvidence || certificate.plan.name !== plan.name
1600
+ || certificate.plan.generatorDigest !== plan.generatorDigest
1601
+ || certificate.plan.sourceTreeDigest !== plan.sourceCheck.treeDigest
1602
+ || certificate.plan.sourcePatchDigest !== plan.sourceCheck.patchDigest
1603
+ || certificate.plan.artifactBytes !== plan.preparedEvidence.pack.sizeBytes) {
1604
+ throw new Error('revision verification exact plan evidence changed');
1605
+ }
1606
+ });
1607
+ return structuredClone(certificate);
1608
+ };
1609
+ /** Private Host diagnostic; no cases, prompts, source bytes or verifier output. */
1610
+ inspectRevisionVerification = (planId) => {
1611
+ this.abort.signal.throwIfAborted();
1612
+ const plan = this.store.getSourcePlan(planId);
1613
+ const reference = this.store.getOwnerTaskFailureReference(plan.gapId);
1614
+ if (!reference || plan.mode !== 'prepared-revise')
1615
+ throw new Error('revision verification owner source unavailable');
1616
+ return this.taskGaps.withCurrent(plan.gapId, reference.owner, () => {
1617
+ const current = this.store.getSourcePlan(planId);
1618
+ if (current.mode !== 'prepared-revise' || current.digest !== plan.digest) {
1619
+ throw new Error('revision verification diagnostic plan changed');
1620
+ }
1621
+ return this.store.inspectRevisionVerificationRecord(planId);
1622
+ });
1623
+ };
1624
+ /** Public naming rule only; never disclose grant identity or mutation authority. */
1625
+ getSourceCreationNamespace() {
1626
+ const config = this.config.sourceJobs;
1627
+ if (this.abort.signal.aborted || config?.creation === undefined
1628
+ || Date.now() >= Math.min(config.expiresAt, config.creation.expiresAt))
1629
+ return undefined;
1630
+ return { namePrefix: config.creation.namePrefix };
1631
+ }
1632
+ inspectCreatedCapabilityRevisionCandidate = (planId) => {
1633
+ this.abort.signal.throwIfAborted();
1634
+ const policy = this.config.revisionVerifications;
1635
+ const owner = this.config.creationCapabilities?.owner;
1636
+ const delivery = this.ctx.get('assistantDelivery', false);
1637
+ if (!policy || !owner || !delivery)
1638
+ return undefined;
1639
+ try {
1640
+ const before = delivery.validateOwnerRoute(owner);
1641
+ const { plan, reference, job } = this.store.getRetainedPreparedRevision(planId);
1642
+ const certificate = this.store.getRevisionVerification(planId);
1643
+ if (!certificate || !plan.sourceRevision || !isSourceOwnerContinuation(before, reference.owner)
1644
+ || !verifyPluginRevisionVerificationCertificate(certificate, policy.authority, policy.publicKey, certificate.verifiedAt)
1645
+ || certificate.plan.digest !== plan.digest || certificate.source.referenceDigest !== controlPlaneDigest(reference)
1646
+ || certificate.source.ownerDigest !== job.intent.ownerDigest
1647
+ || certificate.source.growthRunDigest !== sourceGrowthRunDigest(plan.sourceRevision.growthRun)
1648
+ || controlPlaneDigest(certificate.parent) !== controlPlaneDigest(plan.sourceRevision.parent)
1649
+ || controlPlaneDigest(certificate.model) !== controlPlaneDigest(plan.sourceRevision.growthRun.model)
1650
+ || controlPlaneDigest(certificate.authority) !== controlPlaneDigest(plan.sourceRevision.growthRun.revisionAcceptance))
1651
+ return undefined;
1652
+ const parent = this.inspectRevisionParent(certificate.parent.planId);
1653
+ if (controlPlaneDigest(parent.parent) !== controlPlaneDigest(certificate.parent))
1654
+ return undefined;
1655
+ const source = this.store.readRevisionSource(planId);
1656
+ if (!source || controlPlaneDigest(before) !== controlPlaneDigest(delivery.validateOwnerRoute(owner)))
1657
+ return undefined;
1658
+ return Object.freeze({ protocol: 'dsh-created-capability-revision-candidate/v1',
1659
+ certificate: Object.freeze(structuredClone(certificate)), source: Object.freeze({ ...source,
1660
+ scope: Object.freeze([...source.scope]), entries: Object.freeze(source.entries.map(entry => Object.freeze({ ...entry }))),
1661
+ files: Object.freeze(source.files.map(file => Object.freeze({ ...file }))) }) });
1662
+ }
1663
+ catch {
1664
+ return undefined;
1665
+ }
1666
+ };
613
1667
  // Bind Host entry points: Cordis service proxies must not become resource owners.
614
1668
  enqueueSourceJob = async (input) => {
615
1669
  this.abort.signal.throwIfAborted();
@@ -642,7 +1696,7 @@ export class PluginControlPlaneService extends Service {
642
1696
  if (this.sourceBuilds.size !== 0 || this.sourceInspections.size !== 0)
643
1697
  throw new Error('plugin-control-plane: another source operation is draining');
644
1698
  const intent = job.intent;
645
- const operation = this.prepareModifySourcePlanOwned({ gapId: intent.gapId, name: intent.name, repository: intent.repository, files: intent.files,
1699
+ const operation = this.prepareSourcePlanOwned({ gapId: intent.gapId, name: intent.name, repository: intent.repository, files: intent.files,
646
1700
  idempotencyKey: `source-job-plan:${job.id}`, expectedBaseCommit: intent.baseCommit, ttlMs: intent.ttlMs, timeoutMs: intent.build.timeoutMs, offline: true, signal, assertCurrent }, job);
647
1701
  this.sourceBuilds.add(operation);
648
1702
  try {
@@ -652,6 +1706,190 @@ export class PluginControlPlaneService extends Service {
652
1706
  this.sourceBuilds.delete(operation);
653
1707
  }
654
1708
  }
1709
+ async verifyPreparedCreation(job, signal, verifier) {
1710
+ signal.throwIfAborted();
1711
+ if (!job.planId || !this.config.creationVerifications || this.store.getCreationVerificationStatus(job.planId) !== undefined)
1712
+ return;
1713
+ const prepared = this.inspectPreparedCreation(job.planId);
1714
+ if (prepared.job.id !== job.id || !prepared.plan.creation?.growthRun || !prepared.plan.preparedEvidence) {
1715
+ throw new Error('creation verification job is not the exact prepared source');
1716
+ }
1717
+ const binding = { planId: prepared.plan.id, planDigest: prepared.plan.digest,
1718
+ artifactSha256: prepared.plan.preparedEvidence.pack.sha256,
1719
+ growthRunDigest: sourceGrowthRunDigest(prepared.plan.creation.growthRun),
1720
+ referenceDigest: controlPlaneDigest(prepared.reference) };
1721
+ this.withPreparedCreationFence(binding, () => this.store.claimCreationVerification(job.planId));
1722
+ try {
1723
+ const current = verifier();
1724
+ if (typeof current?.verifyPluginCreation !== 'function')
1725
+ throw new Error('independent creation verifier unavailable');
1726
+ const result = await current.verifyPluginCreation({ protocol: 'assistant-growth/creation-verification-request/v1', planId: job.planId }, signal);
1727
+ signal.throwIfAborted();
1728
+ if (result.status === 'verified' && !verifyPluginCreationVerificationCertificate(result.certificate, this.config.creationVerifications.authority, this.config.creationVerifications.publicKey)) {
1729
+ throw new Error('creation verification certificate signature or policy invalid');
1730
+ }
1731
+ if (controlPlaneDigest(await this.boundTrust()) !== job.intent.trustDigest)
1732
+ throw new Error('creation verification trust changed');
1733
+ await this.inspectPreparedCreationReviewContext(job.planId, signal);
1734
+ this.withPreparedCreationFence(binding, () => {
1735
+ if (result.status === 'verified') {
1736
+ this.store.recordCreationVerification(result.certificate);
1737
+ }
1738
+ else
1739
+ this.store.settleCreationVerification(job.planId, result.status, typeof result.reason === 'string' && (result.status === 'unknown' ? CREATION_UNKNOWN_CODES : CREATION_REJECTED_CODES).has(result.reason)
1740
+ ? result.reason : `independent-verifier-${result.status}`);
1741
+ });
1742
+ }
1743
+ catch (error) {
1744
+ // The claimed row is a durable no-replay fence even if the process dies
1745
+ // before settlement. A later native tick cannot dispatch another review.
1746
+ try {
1747
+ this.withPreparedCreationFence(binding, () => {
1748
+ if (this.store.getCreationVerificationStatus(job.planId) === 'claimed') {
1749
+ this.store.settleCreationVerification(job.planId, 'unknown', 'independent-verification-unsettled');
1750
+ }
1751
+ });
1752
+ }
1753
+ catch { /* stale owner/source remains permanently claimed */ }
1754
+ throw error;
1755
+ }
1756
+ }
1757
+ async verifyPreparedRevision(job, signal, verifier) {
1758
+ signal.throwIfAborted();
1759
+ if (!job.planId || !this.config.revisionVerifications || this.store.getRevisionVerificationStatus(job.planId) !== undefined)
1760
+ return;
1761
+ const prepared = this.inspectPreparedRevision(job.planId);
1762
+ if (prepared.job.id !== job.id || !prepared.plan.sourceRevision?.growthRun || !prepared.plan.preparedEvidence) {
1763
+ throw new Error('revision verification job is not the exact prepared source');
1764
+ }
1765
+ const binding = { planId: prepared.plan.id, planDigest: prepared.plan.digest,
1766
+ artifactSha256: prepared.plan.preparedEvidence.pack.sha256,
1767
+ growthRunDigest: sourceGrowthRunDigest(prepared.plan.sourceRevision.growthRun),
1768
+ referenceDigest: controlPlaneDigest(prepared.reference), parentDigest: controlPlaneDigest(prepared.plan.sourceRevision.parent) };
1769
+ this.withPreparedRevisionFence(binding, () => this.store.claimRevisionVerification(job.planId));
1770
+ try {
1771
+ const current = verifier();
1772
+ if (typeof current?.verifyPluginRevision !== 'function')
1773
+ throw new Error('independent revision verifier unavailable');
1774
+ const result = await current.verifyPluginRevision({ protocol: 'assistant-growth/revision-verification-request/v1', planId: job.planId }, signal);
1775
+ signal.throwIfAborted();
1776
+ if (result.status === 'verified' && !verifyPluginRevisionVerificationCertificate(result.certificate, this.config.revisionVerifications.authority, this.config.revisionVerifications.publicKey)) {
1777
+ throw new Error('revision verification certificate signature or policy invalid');
1778
+ }
1779
+ if (controlPlaneDigest(await this.boundTrust()) !== job.intent.trustDigest)
1780
+ throw new Error('revision verification trust changed');
1781
+ await this.inspectPreparedRevisionReviewContext(job.planId, signal);
1782
+ const source = result.status === 'verified' ? await captureCreationCapabilitySource({ worktree: prepared.plan.worktree,
1783
+ baseCommit: prepared.plan.baseCommit, scope: [...prepared.plan.scope].sort(), certificate: result.certificate,
1784
+ environment: inheritedEnvironment(await this.boundTrust()), signal }) : undefined;
1785
+ this.withPreparedRevisionFence(binding, () => {
1786
+ if (result.status === 'verified') {
1787
+ if (!source)
1788
+ throw new Error('verified revision source archive unavailable');
1789
+ this.store.recordRevisionVerification(result.certificate, source);
1790
+ }
1791
+ else
1792
+ this.store.settleRevisionVerification(job.planId, result.status, typeof result.reason === 'string' && (result.status === 'unknown' ? CREATION_UNKNOWN_CODES : CREATION_REJECTED_CODES).has(result.reason)
1793
+ ? result.reason : `independent-verifier-${result.status}`);
1794
+ });
1795
+ }
1796
+ catch (error) {
1797
+ // The claimed row is a durable no-replay fence even if the process dies
1798
+ // before settlement. A later native tick cannot dispatch another review.
1799
+ try {
1800
+ this.withPreparedRevisionFence(binding, () => {
1801
+ if (this.store.getRevisionVerificationStatus(job.planId) === 'claimed') {
1802
+ this.store.settleRevisionVerification(job.planId, 'unknown', 'independent-verification-unsettled');
1803
+ }
1804
+ });
1805
+ }
1806
+ catch { /* stale owner/source remains permanently claimed */ }
1807
+ throw error;
1808
+ }
1809
+ }
1810
+ async verifyPreparedRevisionRegression(job, signal, verifier) {
1811
+ signal.throwIfAborted();
1812
+ if (!job.planId || !this.config.revisionRegressions || this.store.getRevisionRegressionStatus(job.planId) !== undefined)
1813
+ return;
1814
+ const prepared = this.inspectPreparedRevisionRegression(job.planId);
1815
+ if (prepared.job.id !== job.id)
1816
+ throw new Error('regression job is not exact prepared revision');
1817
+ const certificate = prepared.candidate.certificate;
1818
+ const binding = { planId: prepared.plan.id, planDigest: prepared.plan.digest, artifactSha256: certificate.plan.artifactSha256,
1819
+ growthRunDigest: certificate.source.growthRunDigest, referenceDigest: certificate.source.referenceDigest,
1820
+ parentDigest: controlPlaneDigest(certificate.parent), candidateVerificationDigest: controlPlaneDigest(certificate),
1821
+ sourceDigest: prepared.candidate.sourceDigest,
1822
+ regressionAuthorityDigest: controlPlaneDigest(prepared.plan.sourceRevision.growthRun.revisionRegressionAcceptance) };
1823
+ this.withPreparedRevisionRegressionFence(binding, () => this.store.claimRevisionRegression(job.planId));
1824
+ try {
1825
+ const current = verifier();
1826
+ if (typeof current?.verifyPluginRevisionRegression !== 'function')
1827
+ throw new Error('independent regression verifier unavailable');
1828
+ const result = await current.verifyPluginRevisionRegression({ protocol: 'assistant-growth/revision-regression-request/v1', planId: job.planId }, signal);
1829
+ signal.throwIfAborted();
1830
+ if (result.status === 'verified' && !verifyPluginRevisionRegressionCertificate(result.certificate, this.config.revisionRegressions.authority, this.config.revisionRegressions.publicKey)) {
1831
+ throw new Error('regression signature or independent policy invalid');
1832
+ }
1833
+ if (controlPlaneDigest(await this.boundTrust()) !== job.intent.trustDigest)
1834
+ throw new Error('regression trust changed');
1835
+ await this.inspectPreparedRevisionReviewContext(job.planId, signal);
1836
+ this.withPreparedRevisionRegressionFence(binding, () => {
1837
+ if (result.status === 'verified') {
1838
+ if (!verifyPluginRevisionRegressionCertificate(result.certificate, this.config.revisionRegressions.authority, this.config.revisionRegressions.publicKey))
1839
+ throw new Error('regression certificate expired before settlement');
1840
+ this.store.recordRevisionRegression(result.certificate);
1841
+ }
1842
+ else
1843
+ this.store.settleRevisionRegression(job.planId, result.status, typeof result.reason === 'string' && /^[a-z][a-z0-9-]{0,79}$/u.test(result.reason)
1844
+ ? result.reason : `independent-regression-${result.status}`);
1845
+ });
1846
+ }
1847
+ catch (error) {
1848
+ try {
1849
+ this.withPreparedRevisionRegressionFence(binding, () => {
1850
+ if (this.store.getRevisionRegressionStatus(job.planId) === 'claimed') {
1851
+ this.store.settleRevisionRegression(job.planId, 'unknown', 'independent-regression-unsettled');
1852
+ }
1853
+ });
1854
+ }
1855
+ catch { /* changed source retains its durable no-replay claim */ }
1856
+ throw error;
1857
+ }
1858
+ }
1859
+ async inspectRevisionSource(input) {
1860
+ this.abort.signal.throwIfAborted();
1861
+ const config = this.config.sourceJobs;
1862
+ if (!config?.revision || !this.inspectSourceRevisionAcceptanceAuthority() || input.repository !== config.repository
1863
+ || this.sourceBuilds.size !== 0 || this.sourceInspections.size !== 0)
1864
+ throw new Error('revision source inspection unavailable');
1865
+ const signal = AbortSignal.any([this.abort.signal, ...(input.signal ? [input.signal] : []), AbortSignal.timeout(15_000)]);
1866
+ const parent = this.inspectRevisionParent(input.parentPlanId);
1867
+ if (parent.name !== input.name)
1868
+ throw new Error('revision source name differs from adopted parent');
1869
+ const assertCurrent = async () => {
1870
+ signal.throwIfAborted();
1871
+ await input.assertCurrent?.();
1872
+ signal.throwIfAborted();
1873
+ if (!this.inspectSourceRevisionAcceptanceAuthority() || controlPlaneDigest(this.inspectRevisionParent(input.parentPlanId).parent) !== controlPlaneDigest(parent.parent))
1874
+ throw new Error('revision source authority or parent changed');
1875
+ };
1876
+ const operation = (async () => {
1877
+ const trust = await this.boundTrust(), environment = inheritedEnvironment(trust);
1878
+ const baselineCommit = config.baseline === undefined ? undefined : await resolveSourceBaseline({ repository: config.repository,
1879
+ config: config.baseline, environment, signal, assertCurrent, trust,
1880
+ readHistory: () => this.store.getSourceBaselineHistory(config.repository), readMaintenance: () => this.store.getSourceMaintenanceRecords(config.repository) });
1881
+ return inspectSourceRevisionContext({ repository: config.repository, name: input.name, paths: input.paths,
1882
+ ...(input.baseCommit === undefined ? {} : { baseCommit: input.baseCommit }), ...(baselineCommit === undefined ? {} : { baselineCommit }),
1883
+ environment, signal, assertCurrent, grant: config.revision, parent: parent.parent, parentSource: parent.source, parentCertificate: parent.certificate });
1884
+ })();
1885
+ this.sourceInspections.add(operation);
1886
+ try {
1887
+ return await operation;
1888
+ }
1889
+ finally {
1890
+ this.sourceInspections.delete(operation);
1891
+ }
1892
+ }
655
1893
  async inspectSource(input) {
656
1894
  this.abort.signal.throwIfAborted();
657
1895
  if (this.sourceBuilds.size !== 0 || this.sourceInspections.size !== 0)
@@ -662,12 +1900,83 @@ export class PluginControlPlaneService extends Service {
662
1900
  void operation.then(() => this.sourceInspections.delete(operation), () => this.sourceInspections.delete(operation));
663
1901
  return operation;
664
1902
  }
1903
+ async inspectSourceTargets(input) {
1904
+ this.abort.signal.throwIfAborted();
1905
+ if (this.sourceBuilds.size !== 0 || this.sourceInspections.size !== 0)
1906
+ throw new ControlPlaneCliError('SOURCE_BOUNDARY', 'another source operation is still draining');
1907
+ const signal = AbortSignal.any([this.abort.signal, ...(input.signal === undefined ? [] : [input.signal]), AbortSignal.timeout(15_000)]);
1908
+ const operation = this.inspectSourceTargetsOwned({ ...input, signal });
1909
+ this.sourceInspections.add(operation);
1910
+ void operation.then(() => this.sourceInspections.delete(operation), () => this.sourceInspections.delete(operation));
1911
+ return operation;
1912
+ }
1913
+ async inspectSourceTargetsOwned(input) {
1914
+ const signal = input.signal === undefined ? this.abort.signal : AbortSignal.any([this.abort.signal, input.signal]);
1915
+ const assertCurrent = async () => { signal.throwIfAborted(); await awaitSourceSignal(signal, () => input.assertCurrent?.()); signal.throwIfAborted(); };
1916
+ await assertCurrent();
1917
+ const trust = await awaitSourceSignal(signal, () => this.boundTrust());
1918
+ const environment = inheritedEnvironment(trust);
1919
+ const baseline = this.config.sourceJobs?.repository === input.repository ? this.config.sourceJobs.baseline : undefined;
1920
+ const baselineCommit = baseline === undefined ? undefined : await resolveSourceBaseline({ repository: input.repository,
1921
+ config: baseline, environment, signal, assertCurrent, trust,
1922
+ readHistory: () => this.store.getSourceBaselineHistory(input.repository),
1923
+ readMaintenance: () => this.store.getSourceMaintenanceRecords(input.repository) });
1924
+ return inspectSourceTargetsContext({ repository: input.repository,
1925
+ ...(input.baseCommit === undefined ? {} : { baseCommit: input.baseCommit }),
1926
+ ...(baselineCommit === undefined ? {} : { baselineCommit }), environment, signal, assertCurrent });
1927
+ }
665
1928
  async inspectSourceOwned(input) {
666
1929
  const signal = input.signal === undefined ? this.abort.signal : AbortSignal.any([this.abort.signal, input.signal]);
667
1930
  const assertCurrent = async () => { signal.throwIfAborted(); await awaitSourceSignal(signal, () => input.assertCurrent?.()); signal.throwIfAborted(); };
668
1931
  await assertCurrent();
669
1932
  const trust = await awaitSourceSignal(signal, () => this.boundTrust());
670
- return inspectSourceContext({ ...input, environment: inheritedEnvironment(trust), signal, assertCurrent });
1933
+ const environment = inheritedEnvironment(trust);
1934
+ const baseline = this.config.sourceJobs?.repository === input.repository ? this.config.sourceJobs.baseline : undefined;
1935
+ const baselineCommit = baseline === undefined ? undefined : await resolveSourceBaseline({ repository: input.repository,
1936
+ config: baseline, environment, signal, assertCurrent, trust,
1937
+ readHistory: () => this.store.getSourceBaselineHistory(input.repository),
1938
+ readMaintenance: () => this.store.getSourceMaintenanceRecords(input.repository) });
1939
+ return inspectSourceContext({ repository: input.repository, name: input.name, paths: input.paths,
1940
+ ...(input.baseCommit === undefined ? {} : { baseCommit: input.baseCommit }),
1941
+ ...(baselineCommit === undefined ? {} : { baselineCommit }), environment, signal, assertCurrent });
1942
+ }
1943
+ async inspectCreateSource(input) {
1944
+ this.abort.signal.throwIfAborted();
1945
+ if (this.sourceBuilds.size !== 0 || this.sourceInspections.size !== 0)
1946
+ throw new ControlPlaneCliError('SOURCE_BOUNDARY', 'another source operation is still draining');
1947
+ const signal = AbortSignal.any([this.abort.signal, ...(input.signal === undefined ? [] : [input.signal]), AbortSignal.timeout(15_000)]);
1948
+ const operation = this.inspectCreateSourceOwned({ ...input, signal });
1949
+ this.sourceInspections.add(operation);
1950
+ try {
1951
+ return await operation;
1952
+ }
1953
+ finally {
1954
+ this.sourceInspections.delete(operation);
1955
+ }
1956
+ }
1957
+ async inspectCreateSourceOwned(input) {
1958
+ const config = this.config.sourceJobs;
1959
+ if (!config?.creation || config.repository !== input.repository)
1960
+ throw new ControlPlaneCliError('SOURCE_BOUNDARY', 'plugin creation authority is not configured for this repository');
1961
+ const signal = input.signal === undefined ? this.abort.signal : AbortSignal.any([this.abort.signal, input.signal]);
1962
+ const assertCurrent = async () => {
1963
+ signal.throwIfAborted();
1964
+ await awaitSourceSignal(signal, () => input.assertCurrent?.());
1965
+ signal.throwIfAborted();
1966
+ if (Date.now() >= config.creation.expiresAt || Date.now() >= config.expiresAt)
1967
+ throw new ControlPlaneCliError('SOURCE_BOUNDARY', 'plugin creation authority expired');
1968
+ };
1969
+ await assertCurrent();
1970
+ const trust = await awaitSourceSignal(signal, () => this.boundTrust());
1971
+ const environment = inheritedEnvironment(trust);
1972
+ const baselineCommit = config.baseline === undefined ? undefined : await resolveSourceBaseline({ repository: input.repository,
1973
+ config: config.baseline, environment, signal, assertCurrent, trust,
1974
+ readHistory: () => this.store.getSourceBaselineHistory(input.repository),
1975
+ readMaintenance: () => this.store.getSourceMaintenanceRecords(input.repository) });
1976
+ const inspected = await inspectSourceCreationContext({ repository: input.repository, name: input.name, paths: input.paths,
1977
+ ...(input.baseCommit === undefined ? {} : { baseCommit: input.baseCommit }),
1978
+ ...(baselineCommit === undefined ? {} : { baselineCommit }), environment, signal, assertCurrent, grant: config.creation });
1979
+ return { name: inspected.name, baseCommit: inspected.baseCommit, files: inspected.files, contents: inspected.contents };
671
1980
  }
672
1981
  async plan(candidateId, profile, idempotencyKey, gapId) {
673
1982
  const gap = this.store.getGap(gapId);
@@ -702,19 +2011,32 @@ export class PluginControlPlaneService extends Service {
702
2011
  this.abort.signal.throwIfAborted();
703
2012
  if (this.sourceBuilds.size !== 0 || this.sourceInspections.size !== 0)
704
2013
  throw new ControlPlaneCliError('SOURCE_BOUNDARY', 'another source operation is still draining');
705
- const operation = this.prepareModifySourcePlanOwned(input);
2014
+ const operation = this.prepareSourcePlanOwned(input);
706
2015
  this.sourceBuilds.add(operation);
707
2016
  void operation.then(() => this.sourceBuilds.delete(operation), () => this.sourceBuilds.delete(operation));
708
2017
  return operation;
709
2018
  }
710
- async prepareModifySourcePlanOwned(input, sourceJob) {
2019
+ async prepareSourcePlanOwned(input, sourceJob) {
711
2020
  const signal = input.signal === undefined ? this.abort.signal : AbortSignal.any([this.abort.signal, input.signal]);
712
2021
  const gapOwner = sourceJob?.intent.owner ?? input.owner;
2022
+ const creating = sourceJob?.intent.mode === 'create';
2023
+ const creation = creating ? sourceJob.intent.creation : undefined;
2024
+ const revising = sourceJob?.intent.mode === 'revise-created';
2025
+ const revision = revising ? sourceJob.intent.revision : undefined;
2026
+ const parent = revision === undefined ? undefined : this.inspectRevisionParent(revision.parent.planId);
713
2027
  const assertCurrent = async () => {
714
2028
  signal.throwIfAborted();
715
2029
  await input.assertCurrent?.();
716
2030
  signal.throwIfAborted();
717
2031
  this.taskGaps.withCurrent(input.gapId, gapOwner, () => { });
2032
+ if (revising && (!revision || !this.config.sourceJobs?.revision || Date.now() >= revision.grant.expiresAt
2033
+ || controlPlaneDigest(revision.grant) !== controlPlaneDigest(this.config.sourceJobs.revision)
2034
+ || controlPlaneDigest(this.inspectRevisionParent(revision.parent.planId).parent) !== controlPlaneDigest(revision.parent)))
2035
+ throw new ControlPlaneCliError('SOURCE_BOUNDARY', 'plugin revision authority or parent changed');
2036
+ if (creating && (!creation || !this.config.sourceJobs?.creation || Date.now() >= creation.grant.expiresAt
2037
+ || controlPlaneDigest(creation.grant) !== controlPlaneDigest(this.config.sourceJobs.creation))) {
2038
+ throw new ControlPlaneCliError('SOURCE_BOUNDARY', 'plugin creation authority changed or expired');
2039
+ }
718
2040
  };
719
2041
  await assertCurrent();
720
2042
  const trust = await this.boundTrust();
@@ -722,15 +2044,19 @@ export class PluginControlPlaneService extends Service {
722
2044
  if (!/^(?=.{1,64}$)[a-z][a-z0-9]*(?:-[a-z0-9]+)*$/u.test(name))
723
2045
  throw new ControlPlaneCliError('INVALID_ARGUMENT', 'plugin name is invalid');
724
2046
  assertPluginModificationAllowed(name);
725
- validateScopedPluginFiles(input.files);
726
- if (this.config.sourceBuild?.versioning === 'patch')
727
- assertManagedVersionPaths(input.files);
2047
+ if (creating || revising)
2048
+ validateSourceCreationFiles(input.files);
2049
+ else {
2050
+ validateScopedPluginFiles(input.files);
2051
+ if (this.config.sourceBuild?.versioning === 'patch')
2052
+ assertManagedVersionPaths(input.files);
2053
+ }
728
2054
  // Re-validate the gap reservation immediately before doing the work: the
729
2055
  // store re-checks under BEGIN IMMEDIATE, but failing early avoids building
730
2056
  // a patch against a gap that is already matched or closed.
731
2057
  const gap = this.store.getGap(input.gapId);
732
2058
  if (gap.status !== 'open' || gap.candidateId !== undefined) {
733
- throw new ControlPlaneCliError('SOURCE_BOUNDARY', 'only an unreserved pre-existing control-plane open gap can receive a modify proposal');
2059
+ throw new ControlPlaneCliError('SOURCE_BOUNDARY', 'only an unreserved current control-plane open gap can receive a source proposal');
734
2060
  }
735
2061
  const repository = await realpath(resolve(input.repository));
736
2062
  if (resolve(repository) !== repository)
@@ -739,14 +2065,20 @@ export class PluginControlPlaneService extends Service {
739
2065
  if (!Number.isSafeInteger(ttlMs) || ttlMs < 900_000 || ttlMs > 86_400_000) {
740
2066
  throw new ControlPlaneCliError('INVALID_ARGUMENT', 'ttlMs must be an integer within 900000..86400000');
741
2067
  }
742
- const maximumTimeoutMs = this.config.sourceBuild?.profile === 'repository' ? 1_800_000 : 240_000;
2068
+ const maximumTimeoutMs = this.config.sourceBuild !== undefined && !('backend' in this.config.sourceBuild)
2069
+ && ['repository', 'portable-repository'].includes(this.config.sourceBuild.profile ?? '') ? 1_800_000 : 240_000;
743
2070
  const timeoutMs = input.timeoutMs ?? 180_000;
744
2071
  if (!Number.isSafeInteger(timeoutMs) || timeoutMs < 60_000 || timeoutMs > maximumTimeoutMs) {
745
2072
  throw new ControlPlaneCliError('INVALID_ARGUMENT', `timeoutMs must be an integer within 60000..${maximumTimeoutMs}`);
746
2073
  }
747
2074
  const offline = input.offline ?? true;
748
2075
  const environment = inheritedEnvironment(trust);
749
- const baseCommit = (await runLocalCommand('git', ['rev-parse', 'HEAD'], repository, environment, { capture: true })).trim();
2076
+ const baseline = this.config.sourceJobs?.repository === repository ? this.config.sourceJobs.baseline : undefined;
2077
+ const baseCommit = baseline === undefined
2078
+ ? (await runLocalCommand('git', ['rev-parse', 'HEAD'], repository, environment, { capture: true })).trim()
2079
+ : await resolveSourceBaseline({ repository, config: baseline, environment, signal, assertCurrent, trust,
2080
+ readHistory: () => this.store.getSourceBaselineHistory(repository),
2081
+ readMaintenance: () => this.store.getSourceMaintenanceRecords(repository) });
750
2082
  await assertCurrent();
751
2083
  if (!/^[a-f0-9]{40}$/u.test(baseCommit))
752
2084
  throw new ControlPlaneCliError('SOURCE_BOUNDARY', 'repository HEAD is not a 40-hex commit id');
@@ -756,7 +2088,12 @@ export class PluginControlPlaneService extends Service {
756
2088
  const isolated = await createIsolatedWorktree({ stateRoot, repository, baseCommit, environment,
757
2089
  ...(sourceJob === undefined ? {} : { worktreeName: basename(sourceJob.intent.worktree) }) });
758
2090
  try {
759
- await writeScopedPluginFiles({ worktree: isolated.worktree, name, files: input.files });
2091
+ const generated = revision !== undefined ? await prepareRevisedPluginWorkspace({ worktree: isolated.worktree,
2092
+ baseCommit, name, files: input.files, environment, signal, assertCurrent, revision, parentSource: parent.source, parentCertificate: parent.certificate })
2093
+ : creation === undefined ? undefined : await prepareCreatedPluginWorkspace({ worktree: isolated.worktree,
2094
+ baseCommit, name, files: input.files, environment, signal, assertCurrent, creation });
2095
+ if (generated === undefined)
2096
+ await writeScopedPluginFiles({ worktree: isolated.worktree, name, files: input.files });
760
2097
  await assertCurrent();
761
2098
  if (this.config.sourceBuild === undefined)
762
2099
  throw new ControlPlaneCliError('SOURCE_BOUNDARY', 'isolated source build runner is not configured');
@@ -764,16 +2101,43 @@ export class PluginControlPlaneService extends Service {
764
2101
  throw new ControlPlaneCliError('SOURCE_BOUNDARY', 'isolated source builds must remain offline');
765
2102
  const configured = this.config.sourceBuild;
766
2103
  const versionInput = { worktree: isolated.worktree, baseCommit, name, environment, signal, assertCurrent };
767
- const managed = configured.versioning === 'patch' ? await managedPatchVersionFiles(versionInput) : undefined;
2104
+ const managed = !creating && !revising && configured.versioning === 'patch' ? await managedPatchVersionFiles(versionInput) : undefined;
768
2105
  if (managed !== undefined) {
769
2106
  await assertCurrent();
770
2107
  await writeScopedPluginFiles({ worktree: isolated.worktree, name, files: managed.files });
771
2108
  await verifyManagedPatchVersion(versionInput);
772
2109
  }
773
- const checked = await runDockerPreparedChecks({ config: { ...configured, timeoutMs: Math.min(timeoutMs, configured.timeoutMs) },
774
- worktree: isolated.worktree, baseCommit, name, scope: [`plugins/${name}`], environment, signal,
775
- assertCurrent, preparedAt: Date.now(), ...(sourceJob === undefined ? {} : { sourceJob: { id: sourceJob.id, containerName: sourceJob.intent.containerName } }) });
2110
+ // Only owner-bound creation jobs retain the exact package for the
2111
+ // independent verifier. Ordinary source tools never receive these bytes.
2112
+ const capturePack = (creating || revising) && this.store.getOwnerTaskFailureReference(input.gapId) !== undefined;
2113
+ const common = { worktree: isolated.worktree, baseCommit, name, scope: generated?.scope ?? [`plugins/${name}`],
2114
+ environment, signal, assertCurrent, preparedAt: Date.now(), ...(creation === undefined ? {} : { creation }),
2115
+ ...(revision === undefined ? {} : { revision: { binding: revision, parentSource: parent.source,
2116
+ parentCertificate: parent.certificate, files: input.files } }), ...(capturePack ? { capturePack: true } : {}) };
2117
+ const checked = 'backend' in configured
2118
+ ? await (async () => {
2119
+ if ((!creating && !revising) || sourceJob === undefined || this.nativeSourceRunner === undefined
2120
+ || sourceJob.intent.nativeResource?.key !== sourceJob.id) {
2121
+ throw new ControlPlaneCliError('SOURCE_BOUNDARY', 'native source preparation requires a durable Host-scaffolded creation job');
2122
+ }
2123
+ return runNativePreparedChecks({ ...common, config: { ...configured, timeoutMs: Math.min(timeoutMs, configured.timeoutMs) },
2124
+ runner: this.nativeSourceRunner, sourceJob: { id: sourceJob.id } });
2125
+ })()
2126
+ : await runDockerPreparedChecks({ ...common, config: { ...configured, timeoutMs: Math.min(timeoutMs, configured.timeoutMs) },
2127
+ ...(sourceJob === undefined ? {} : { sourceJob: { id: sourceJob.id, containerName: sourceJob.intent.containerName } }) });
776
2128
  await assertCurrent();
2129
+ if (capturePack && checked.packArtifact === undefined)
2130
+ throw new ControlPlaneCliError('SOURCE_BOUNDARY', 'prepared creation artifact was not captured');
2131
+ if (revision !== undefined) {
2132
+ await verifyRevisedPluginWorkspace({ worktree: isolated.worktree, baseCommit, name, files: input.files, environment, signal, assertCurrent, revision, parentSource: parent.source, parentCertificate: parent.certificate });
2133
+ }
2134
+ if (creation !== undefined) {
2135
+ await verifyCreatedPluginWorkspace({ worktree: isolated.worktree, baseCommit, name,
2136
+ files: input.files, environment, signal, assertCurrent, creation });
2137
+ const manifest = JSON.parse(await readFile(join(isolated.worktree, 'plugins', name, 'package.json'), 'utf8'));
2138
+ if (checked.evidence.pack.version !== manifest.version)
2139
+ throw new ControlPlaneCliError('SOURCE_BOUNDARY', 'new plugin artifact does not carry the generated version');
2140
+ }
777
2141
  if (managed !== undefined) {
778
2142
  await verifyManagedPatchVersion(versionInput);
779
2143
  if (checked.evidence.pack.version !== managed.version)
@@ -782,7 +2146,10 @@ export class PluginControlPlaneService extends Service {
782
2146
  // The worktree survives success: the owner recomputes its digests on this
783
2147
  // exact directory during `source verify-prepared`.
784
2148
  return this.taskGaps.withCurrent(input.gapId, gapOwner, () => this.store.createSourcePlan({ gapId: input.gapId, repository, worktree: isolated.worktree, baseCommit,
785
- name, generatorDigest: MODIFY_GENERATOR_DIGEST, scope: [`plugins/${name}`], mode: 'modify', ttlMs,
2149
+ name, generatorDigest: generated?.generatorDigest ?? MODIFY_GENERATOR_DIGEST,
2150
+ scope: generated?.scope ?? [`plugins/${name}`], mode: creating ? 'prepared-create' : revising ? 'prepared-revise' : 'modify', ttlMs,
2151
+ ...(creation === undefined ? {} : { creation }), ...(revision === undefined ? {} : { revision }),
2152
+ ...(capturePack && checked.packArtifact !== undefined ? { preparedArtifact: checked.packArtifact } : {}),
786
2153
  idempotencyKey: input.idempotencyKey,
787
2154
  ...(sourceJob === undefined ? {} : { sourceJob: { jobId: sourceJob.id, jobRevision: sourceJob.revision, occurrenceId: sourceJob.occurrenceId } }),
788
2155
  prepared: { treeDigest: checked.treeDigest, patchDigest: checked.patchDigest, checkedAt: checked.checkedAt, evidence: checked.evidence } }).result);