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

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