@dsh-enhanced/plugin-control-plane 0.1.65 → 0.1.67

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 (136) hide show
  1. package/README.md +123 -7
  2. package/lib/attestation.d.ts.map +1 -1
  3. package/lib/attestation.js +33 -1
  4. package/lib/attestation.js.map +1 -1
  5. package/lib/creation-capability-empty-handoff.d.ts +57 -0
  6. package/lib/creation-capability-empty-handoff.d.ts.map +1 -0
  7. package/lib/creation-capability-empty-handoff.js +147 -0
  8. package/lib/creation-capability-empty-handoff.js.map +1 -0
  9. package/lib/creation-capability-execution.d.ts +13 -0
  10. package/lib/creation-capability-execution.d.ts.map +1 -0
  11. package/lib/creation-capability-execution.js +112 -0
  12. package/lib/creation-capability-execution.js.map +1 -0
  13. package/lib/creation-capability-feedback.d.ts +58 -1
  14. package/lib/creation-capability-feedback.d.ts.map +1 -1
  15. package/lib/creation-capability-feedback.js +532 -50
  16. package/lib/creation-capability-feedback.js.map +1 -1
  17. package/lib/creation-capability-handoff-common.d.ts +60 -0
  18. package/lib/creation-capability-handoff-common.d.ts.map +1 -0
  19. package/lib/creation-capability-handoff-common.js +195 -0
  20. package/lib/creation-capability-handoff-common.js.map +1 -0
  21. package/lib/creation-capability-journal.d.ts +19 -0
  22. package/lib/creation-capability-journal.d.ts.map +1 -1
  23. package/lib/creation-capability-journal.js +62 -21
  24. package/lib/creation-capability-journal.js.map +1 -1
  25. package/lib/creation-capability-runtime.d.ts +65 -1
  26. package/lib/creation-capability-runtime.d.ts.map +1 -1
  27. package/lib/creation-capability-runtime.js +178 -18
  28. package/lib/creation-capability-runtime.js.map +1 -1
  29. package/lib/creation-capability-sealed-audit.d.ts +19 -0
  30. package/lib/creation-capability-sealed-audit.d.ts.map +1 -0
  31. package/lib/creation-capability-sealed-audit.js +114 -0
  32. package/lib/creation-capability-sealed-audit.js.map +1 -0
  33. package/lib/creation-capability-sealed-handoff.d.ts +160 -0
  34. package/lib/creation-capability-sealed-handoff.d.ts.map +1 -0
  35. package/lib/creation-capability-sealed-handoff.js +695 -0
  36. package/lib/creation-capability-sealed-handoff.js.map +1 -0
  37. package/lib/creation-capability-trend-snapshot.d.ts +70 -0
  38. package/lib/creation-capability-trend-snapshot.d.ts.map +1 -0
  39. package/lib/creation-capability-trend-snapshot.js +117 -0
  40. package/lib/creation-capability-trend-snapshot.js.map +1 -0
  41. package/lib/creation-capability-types.d.ts +254 -4
  42. package/lib/creation-capability-types.d.ts.map +1 -1
  43. package/lib/in-process-reload.d.ts +191 -0
  44. package/lib/in-process-reload.d.ts.map +1 -0
  45. package/lib/in-process-reload.js +284 -0
  46. package/lib/in-process-reload.js.map +1 -0
  47. package/lib/index.d.ts +15 -4
  48. package/lib/index.d.ts.map +1 -1
  49. package/lib/index.js +10 -2
  50. package/lib/index.js.map +1 -1
  51. package/lib/linux-source-build.d.ts +16 -0
  52. package/lib/linux-source-build.d.ts.map +1 -0
  53. package/lib/linux-source-build.js +268 -0
  54. package/lib/linux-source-build.js.map +1 -0
  55. package/lib/linux-source-preflight.d.ts +31 -0
  56. package/lib/linux-source-preflight.d.ts.map +1 -0
  57. package/lib/linux-source-preflight.js +101 -0
  58. package/lib/linux-source-preflight.js.map +1 -0
  59. package/lib/live-qualification-runtime.d.ts +6 -2
  60. package/lib/live-qualification-runtime.d.ts.map +1 -1
  61. package/lib/live-qualification-runtime.js +65 -11
  62. package/lib/live-qualification-runtime.js.map +1 -1
  63. package/lib/owner-task-gap-types.d.ts +23 -0
  64. package/lib/owner-task-gap-types.d.ts.map +1 -1
  65. package/lib/owner-task-gaps.d.ts +4 -0
  66. package/lib/owner-task-gaps.d.ts.map +1 -1
  67. package/lib/owner-task-gaps.js +35 -1
  68. package/lib/owner-task-gaps.js.map +1 -1
  69. package/lib/revision-capability-journal.d.ts +103 -0
  70. package/lib/revision-capability-journal.d.ts.map +1 -0
  71. package/lib/revision-capability-journal.js +872 -0
  72. package/lib/revision-capability-journal.js.map +1 -0
  73. package/lib/revision-capability-runtime.d.ts +74 -0
  74. package/lib/revision-capability-runtime.d.ts.map +1 -0
  75. package/lib/revision-capability-runtime.js +562 -0
  76. package/lib/revision-capability-runtime.js.map +1 -0
  77. package/lib/revision-capability-types.d.ts +289 -0
  78. package/lib/revision-capability-types.d.ts.map +1 -0
  79. package/lib/revision-capability-types.js +2 -0
  80. package/lib/revision-capability-types.js.map +1 -0
  81. package/lib/scanner-owner-runtime.d.ts +34 -0
  82. package/lib/scanner-owner-runtime.d.ts.map +1 -0
  83. package/lib/scanner-owner-runtime.js +99 -0
  84. package/lib/scanner-owner-runtime.js.map +1 -0
  85. package/lib/service.d.ts +107 -6
  86. package/lib/service.d.ts.map +1 -1
  87. package/lib/service.js +643 -47
  88. package/lib/service.js.map +1 -1
  89. package/lib/source-approval-authority.d.ts.map +1 -1
  90. package/lib/source-approval-authority.js +25 -7
  91. package/lib/source-approval-authority.js.map +1 -1
  92. package/lib/source-build-diagnostics.d.ts +31 -0
  93. package/lib/source-build-diagnostics.d.ts.map +1 -0
  94. package/lib/source-build-diagnostics.js +110 -0
  95. package/lib/source-build-diagnostics.js.map +1 -0
  96. package/lib/source-build.d.ts +25 -2
  97. package/lib/source-build.d.ts.map +1 -1
  98. package/lib/source-build.js +145 -26
  99. package/lib/source-build.js.map +1 -1
  100. package/lib/source-creation.d.ts +6 -2
  101. package/lib/source-creation.d.ts.map +1 -1
  102. package/lib/source-creation.js +11 -2
  103. package/lib/source-creation.js.map +1 -1
  104. package/lib/source-job-types.d.ts +3 -1
  105. package/lib/source-job-types.d.ts.map +1 -1
  106. package/lib/source-jobs.d.ts +33 -1
  107. package/lib/source-jobs.d.ts.map +1 -1
  108. package/lib/source-jobs.js +141 -29
  109. package/lib/source-jobs.js.map +1 -1
  110. package/lib/source-workspace.d.ts.map +1 -1
  111. package/lib/source-workspace.js +0 -1
  112. package/lib/source-workspace.js.map +1 -1
  113. package/lib/sqlite.d.ts +1 -1
  114. package/lib/sqlite.d.ts.map +1 -1
  115. package/lib/sqlite.js +36 -1
  116. package/lib/sqlite.js.map +1 -1
  117. package/lib/store.d.ts +22 -2
  118. package/lib/store.d.ts.map +1 -1
  119. package/lib/store.js +477 -177
  120. package/lib/store.js.map +1 -1
  121. package/lib/systemd-host-authority.d.ts.map +1 -1
  122. package/lib/systemd-host-authority.js +4 -0
  123. package/lib/systemd-host-authority.js.map +1 -1
  124. package/lib/task-observation-runtime.d.ts +6 -2
  125. package/lib/task-observation-runtime.d.ts.map +1 -1
  126. package/lib/task-observation-runtime.js +69 -11
  127. package/lib/task-observation-runtime.js.map +1 -1
  128. package/lib/types.d.ts +47 -1
  129. package/lib/types.d.ts.map +1 -1
  130. package/lib/version.d.ts +1 -1
  131. package/lib/version.js +1 -1
  132. package/lib/work-item-creation-contract.d.ts +17 -0
  133. package/lib/work-item-creation-contract.d.ts.map +1 -0
  134. package/lib/work-item-creation-contract.js +61 -0
  135. package/lib/work-item-creation-contract.js.map +1 -0
  136. package/package.json +10 -10
package/lib/service.js CHANGED
@@ -1,16 +1,20 @@
1
+ import { cleanedLinuxSourceBuildFailure, cleanedLinuxSourcePreflight, consumeCleanedLinuxSourceBuildFailure, consumeCleanedLinuxSourcePreflight, consumeLinuxSourceBuildFailed, consumeLinuxSourceNotStarted, isLinuxSourceBuildFailed } from './linux-source-preflight.js';
1
2
  import { AdoptionCoordinatorRuntime, validateAdoptionCoordinatorConfig } from './adoption-coordinator.js';
2
- import { CreationCapabilityJournal, creationCapabilityPublicKey, validateCreationCapabilityConfig } from './creation-capability-journal.js';
3
+ import { CreationCapabilityJournal, creationCapabilityAuthorityDigest, creationCapabilityPublicKey, validateCreationCapabilityConfig } from './creation-capability-journal.js';
3
4
  import { CreationCapabilityRuntime } from './creation-capability-runtime.js';
4
- import { inspectCreationCapabilityTaskAssociations } from './creation-capability-feedback.js';
5
+ import { RevisionCapabilityJournal, revisionCapabilityPublicKey, validateRevisionCapabilityConfig } from './revision-capability-journal.js';
6
+ import { RevisionCapabilityRuntime } from './revision-capability-runtime.js';
7
+ import { inspectCreationCapabilityAvailabilityAttribution, inspectCreationCapabilityOrdinaryUseReport, inspectCreationCapabilityOrdinaryUseTrend, inspectCreationCapabilityTaskAssociations } from './creation-capability-feedback.js';
5
8
  import { captureCreationCapabilitySource } from './creation-capability-source.js';
6
9
  import { lstat, readFile, realpath } from 'node:fs/promises';
7
10
  import { realpathSync } from 'node:fs';
8
11
  import { basename, dirname, isAbsolute, join, resolve } from 'node:path';
9
12
  import { createHash, createPublicKey, randomBytes } from 'node:crypto';
10
- import { isSourceOwnerContinuation, sourceGrowthRunDigest, validateCreationAcceptanceAuthorityRef, verifyPluginCreationVerificationCertificate, validateRevisionAcceptanceAuthorityRef, verifyPluginRevisionVerificationCertificate, validateRevisionRegressionAcceptanceAuthorityRef, verifyPluginRevisionRegressionCertificate, validateSourceGrowthRunBinding } from '@dsh-enhanced/assistant-growth-contract';
13
+ import { isSourceOwnerContinuation, anySourceGrowthRunDigest as sourceGrowthRunDigest, validateAnySourceGrowthRunBinding as validateSourceGrowthRunBinding, validateWorkItemGrowthAuthorityPins, validateCreationAcceptanceAuthorityRef, verifyPluginCreationVerificationCertificate, validateRevisionAcceptanceAuthorityRef, verifyPluginRevisionVerificationCertificate, validateRevisionRegressionAcceptanceAuthorityRef, verifyPluginRevisionRegressionCertificate } from '@dsh-enhanced/assistant-growth-contract';
11
14
  import { TaskObservationRuntime, validateTaskObservationConfig } from './task-observation-runtime.js';
12
15
  import { LiveQualificationRuntime, validateLiveQualificationConfig } from './live-qualification-runtime.js';
13
16
  import { rollbackPluginWatch } from './cli.js';
17
+ import { assertWorkItemRunReference, inertCreationData, WORK_ITEM_ENGINEERING_PROTOCOL } from './work-item-creation-contract.js';
14
18
  import { OwnerTaskFailureGaps } from './owner-task-gaps.js';
15
19
  import { Context, Service } from '@deepseek-ai/cordis';
16
20
  import Schema from '@deepseek-ai/schemastery';
@@ -27,7 +31,8 @@ import { Ed25519ApprovalAuthority } from './approval.js';
27
31
  import { ControlPlaneStore, MODIFY_GENERATOR_DIGEST, controlPlaneDigest } from './store.js';
28
32
  import { runDockerPreparedChecks, validateSourceBuildConfig } from './source-build.js';
29
33
  import { runNativePreparedChecks } from './native-source-build.js';
30
- import { NativeMacVerifierRunner } from '@dsh-enhanced/assistant-isolation';
34
+ import { LINUX_PREPARED_SOURCE_BUILD_SCRIPT, LINUX_SOURCE_PACK_PATH, runLinuxPreparedChecks } from './linux-source-build.js';
35
+ import { LinuxRepositoryRunner, NativeMacVerifierRunner, linuxRuntimeDigest, verifyLinuxRuntime } from '@dsh-enhanced/assistant-isolation';
31
36
  import { NATIVE_SOURCE_COMPILER } from './native-source-build-worker.js';
32
37
  import { awaitSourceSignal, inspectSourceContext, inspectSourceTargetsContext } from './source-context.js';
33
38
  import { inspectSourceRevisionContext, prepareRevisedPluginWorkspace, verifyRevisedPluginWorkspace } from './source-revision.js';
@@ -35,7 +40,7 @@ import { inspectSourceCreationContext, prepareCreatedPluginWorkspace, validateSo
35
40
  import { resolveSourceBaseline } from './source-baseline.js';
36
41
  import { inheritedEnvironment, loadTrustConfig, resolveTrustKey } from './trust.js';
37
42
  import { registerPluginControlTools } from './tools.js';
38
- import { SourceGrowthRunUnavailableError, SourceJobRuntime, validateSourceJobsConfig } from './source-jobs.js';
43
+ import { SourceGrowthRunUnavailableError, SourceJobRuntime, normalizeSourceJobsConfig } from './source-jobs.js';
39
44
  import { installRuntimeObserver, validateRuntimeObserverConfig } from './runtime-observer.js';
40
45
  import { installReplayEndpoint, validateReplayEndpointConfig } from './replay-endpoint.js';
41
46
  import { readPrivateRuntimeObserverKey } from './runtime-observer-protocol.js';
@@ -54,6 +59,7 @@ const schema = Schema.object({
54
59
  revisionVerifications: Schema.any(),
55
60
  revisionRegressions: Schema.any(),
56
61
  creationCapabilities: Schema.any(),
62
+ revisionCapabilities: Schema.any(),
57
63
  sourceApprovals: Schema.any(),
58
64
  sourceReleases: Schema.any(),
59
65
  sourceReleaseExecution: Schema.any(),
@@ -136,13 +142,22 @@ export function normalizeControlPlaneConfig(input) {
136
142
  validateSourceApprovalClientConfig(config.sourceApprovals);
137
143
  if (config.sourceBuild !== undefined)
138
144
  validateSourceBuildConfig(config.sourceBuild);
139
- if (config.sourceBuild !== undefined && 'backend' in config.sourceBuild) {
145
+ if (config.sourceBuild !== undefined && 'backend' in config.sourceBuild && config.sourceBuild.backend === 'macos-seatbelt') {
140
146
  if (!config.sourceJobs?.allowedModes || config.sourceJobs.allowedModes.some(mode => mode === 'modify')
141
147
  || config.sourceReleases !== undefined || config.sourceReleaseExecution !== undefined
142
148
  || config.sourceAdoptions !== undefined || config.sourceApprovals !== undefined) {
143
149
  throw new Error('plugin-control-plane: native source build only supports Host-scaffolded create/revise-created');
144
150
  }
145
151
  }
152
+ if (config.sourceBuild !== undefined && 'backend' in config.sourceBuild && config.sourceBuild.backend === 'linux-native') {
153
+ if (!config.sourceJobs)
154
+ throw new Error('plugin-control-plane: Linux repository preparation requires durable sourceJobs');
155
+ if ((config.creationVerifications || config.creationCapabilities || config.revisionVerifications || config.revisionRegressions
156
+ || config.revisionCapabilities)
157
+ && (!config.sourceBuild.userNamespace || !config.sourceBuild.systemSnapshot)) {
158
+ throw new Error('plugin-control-plane: Linux independent verification requires the dual-UID repository boundary');
159
+ }
160
+ }
146
161
  if (config.sourceReleases !== undefined) {
147
162
  validateSourceReleaseClientConfig(config.sourceReleases);
148
163
  if (!config.sourceApprovals || config.sourceBuild?.versioning !== 'patch')
@@ -168,7 +183,7 @@ export function normalizeControlPlaneConfig(input) {
168
183
  }
169
184
  }
170
185
  if (config.sourceJobs !== undefined) {
171
- validateSourceJobsConfig(config.sourceJobs, config.sourceBuild);
186
+ config.sourceJobs = normalizeSourceJobsConfig(config.sourceJobs, config.sourceBuild);
172
187
  if (realpathSync(config.sourceJobs.repository) !== config.sourceJobs.repository)
173
188
  throw new Error('plugin-control-plane: sourceJobs.repository must be canonical');
174
189
  }
@@ -227,9 +242,19 @@ export function normalizeControlPlaneConfig(input) {
227
242
  }
228
243
  if (config.creationCapabilities !== undefined) {
229
244
  validateCreationCapabilityConfig(config.creationCapabilities);
245
+ if (config.creationCapabilities.workItemFailures === false) {
246
+ config.creationCapabilities = { ...config.creationCapabilities };
247
+ delete config.creationCapabilities.workItemFailures;
248
+ }
230
249
  const capability = config.creationCapabilities;
231
250
  const verification = config.creationVerifications;
232
251
  const jobs = config.sourceJobs;
252
+ const build = config.sourceBuild;
253
+ const buildBackend = build && 'backend' in build ? build.backend : 'docker';
254
+ const adoptionBackend = 'backend' in capability.runner ? capability.runner.backend : 'docker';
255
+ if (!build || buildBackend !== adoptionBackend) {
256
+ throw new Error('plugin-control-plane: creation build, verification and adoption require matching execution backends');
257
+ }
233
258
  if (!verification || !jobs?.creation || capability.namePrefix !== jobs.creation.namePrefix
234
259
  || capability.owner.authorityId !== jobs.ownerRouteId || capability.owner.principalId !== jobs.principalId
235
260
  || capability.owner.workspace !== jobs.workspace || capability.owner.agentPreset !== jobs.preset
@@ -241,6 +266,39 @@ export function normalizeControlPlaneConfig(input) {
241
266
  if (adoptionKey.equals(verificationKey))
242
267
  throw new Error('plugin-control-plane: adoption and verification signing keys must be separate');
243
268
  }
269
+ if (config.revisionCapabilities !== undefined) {
270
+ validateRevisionCapabilityConfig(config.revisionCapabilities);
271
+ const capability = config.revisionCapabilities;
272
+ const creation = config.creationCapabilities;
273
+ const verification = config.revisionVerifications;
274
+ const jobs = config.sourceJobs;
275
+ const build = config.sourceBuild;
276
+ if (!creation || !verification || !jobs?.revision || !build) {
277
+ throw new Error('plugin-control-plane: revision adoption requires creation capabilities, revision verification and finite revision source');
278
+ }
279
+ const buildBackend = 'backend' in build ? build.backend : 'docker';
280
+ const runnerBackend = 'backend' in creation.runner ? creation.runner.backend : 'docker';
281
+ if (buildBackend !== runnerBackend
282
+ || capability.namePrefix !== creation.namePrefix || capability.namePrefix !== jobs.revision.namePrefix
283
+ || controlPlaneDigest(capability.owner) !== controlPlaneDigest(creation.owner)
284
+ || capability.expiresAt > creation.expiresAt) {
285
+ throw new Error('plugin-control-plane: revision adoption authority does not match its authenticated creation lane');
286
+ }
287
+ if (capability.authorityId === creation.authorityId || capability.authorityId === verification.authority.authorityId
288
+ || capability.rollback.authorityId === capability.authorityId || capability.rollback.authorityId === creation.authorityId
289
+ || capability.rollback.authorityId === verification.authority.authorityId) {
290
+ throw new Error('plugin-control-plane: revision adoption, rollback, verification and creation authorities must be separate');
291
+ }
292
+ if (capability.keyId === creation.keyId || capability.keyId === verification.authority.keyId) {
293
+ throw new Error('plugin-control-plane: revision adoption signing key must differ from acceptance and creation keys');
294
+ }
295
+ const revisionKey = createPublicKey(revisionCapabilityPublicKey(capability)).export({ format: 'der', type: 'spki' });
296
+ const creationKey = createPublicKey(creationCapabilityPublicKey(creation)).export({ format: 'der', type: 'spki' });
297
+ const verificationKey = createPublicKey(verification.publicKey).export({ format: 'der', type: 'spki' });
298
+ if (revisionKey.equals(creationKey) || revisionKey.equals(verificationKey)) {
299
+ throw new Error('plugin-control-plane: revision adoption signing key material must be separate');
300
+ }
301
+ }
244
302
  if (![config.catalogPath, config.statePath, config.trustPath].every(isAbsolute))
245
303
  throw new Error('plugin-control-plane: catalogPath, statePath and trustPath must be absolute');
246
304
  return config;
@@ -264,11 +322,13 @@ export class PluginControlPlaneService extends Service {
264
322
  nativeSourceRunner;
265
323
  sourceRuntimes = new Set();
266
324
  creationCapabilityRuntime;
325
+ revisionCapabilityRuntime;
267
326
  creationCapabilityRuntimes = new Set();
268
327
  creationCapabilityFlights = new Set();
269
328
  creationCapabilityReconcileRequested = false;
270
329
  growthRunProducer;
271
330
  growthProviderDraining = false;
331
+ workItemCreationFrame;
272
332
  constructor(ctx, input) {
273
333
  super(ctx, 'pluginControlPlane');
274
334
  this.config = normalizeControlPlaneConfig(input);
@@ -437,9 +497,14 @@ export class PluginControlPlaneService extends Service {
437
497
  },
438
498
  delivery: {
439
499
  validateOwnerRoute: input => delivery().validateOwnerRoute(input),
500
+ inspectOwnerRouteState: input => delivery().inspectOwnerRouteState(input),
440
501
  inspectOwnerForegroundLearningTask: input => delivery().inspectOwnerForegroundLearningTask(input),
441
502
  },
442
503
  automations: {
504
+ inspectSystemOwnedRetirement: input => automations().inspectSystemOwnedRetirement(input),
505
+ onSchedulerWake: listener => automations().onSchedulerWake(listener),
506
+ retireSystemOwned: input => automations().retireSystemOwned(input),
507
+ inspectSystemOwned: input => automations().inspectSystemOwned(input),
443
508
  registerHostExecutor: input => automations().registerHostExecutor(input),
444
509
  reconcileSystem: input => automations().reconcileSystem(input),
445
510
  inspectSystemOwnedActivation: input => automations().inspectSystemOwnedActivation(input),
@@ -495,9 +560,14 @@ export class PluginControlPlaneService extends Service {
495
560
  },
496
561
  delivery: {
497
562
  validateOwnerRoute: input => delivery().validateOwnerRoute(input),
563
+ inspectOwnerRouteState: input => delivery().inspectOwnerRouteState(input),
498
564
  inspectOwnerForegroundLearningTask: input => delivery().inspectOwnerForegroundLearningTask(input),
499
565
  },
500
566
  automations: {
567
+ inspectSystemOwnedRetirement: input => automations().inspectSystemOwnedRetirement(input),
568
+ onSchedulerWake: listener => automations().onSchedulerWake(listener),
569
+ retireSystemOwned: input => automations().retireSystemOwned(input),
570
+ inspectSystemOwned: input => automations().inspectSystemOwned(input),
501
571
  registerHostExecutor: input => automations().registerHostExecutor(input),
502
572
  reconcileSystem: input => automations().reconcileSystem(input),
503
573
  inspectSystemOwnedActivation: input => automations().inspectSystemOwnedActivation(input),
@@ -558,10 +628,14 @@ export class PluginControlPlaneService extends Service {
558
628
  if (Object.keys(owner).some(key => prepared.reference.owner[key] !== owner[key])) {
559
629
  throw new Error('created capability owner differs from the finite grant');
560
630
  }
561
- return { certificate, artifact: prepared.artifact, owner };
631
+ return { certificate, artifact: prepared.artifact, owner, ...(prepared.protocol === 'dsh-prepared-creation/v2' ? { sourceRun: prepared.plan.creation.growthRun } : {}) };
562
632
  },
563
633
  recheck: async (planId, signal) => {
564
634
  await this.inspectPreparedCreationReviewContext(planId, signal);
635
+ if (this.config.sourceBuild && 'backend' in this.config.sourceBuild
636
+ && this.config.sourceBuild.backend === 'linux-native') {
637
+ await this.inspectPreparedCreationLinuxBuild(planId, signal);
638
+ }
565
639
  if (!this.inspectVerifiedCreation(planId))
566
640
  throw new Error('created capability independent certificate changed');
567
641
  },
@@ -584,7 +658,7 @@ export class PluginControlPlaneService extends Service {
584
658
  return source;
585
659
  },
586
660
  inspectRetained: record => this.withRetainedCreatedCapabilityCurrent(record, () => ({
587
- certificate: record.certificate, artifact: record.artifact, owner: config.owner
661
+ certificate: record.certificate, artifact: record.artifact, owner: config.owner, ...(this.store.getRetainedPreparedCreation(record.planId).plan.creation?.growthRun?.protocol === 'assistant-growth/source-run/v2' ? { sourceRun: this.store.getRetainedPreparedCreation(record.planId).plan.creation.growthRun } : {})
588
662
  })),
589
663
  recheckRetained: async (record, signal) => {
590
664
  signal.throwIfAborted();
@@ -613,9 +687,6 @@ export class PluginControlPlaneService extends Service {
613
687
  },
614
688
  } });
615
689
  this.creationCapabilityRuntimes.add(capabilities);
616
- await capabilities.start();
617
- this.abort.signal.throwIfAborted();
618
- this.creationCapabilityRuntime = capabilities;
619
690
  unsubscribeTaskChanges = evaluation.onTrustedTaskChange(() => this.reconcileCreatedCapabilities());
620
691
  }
621
692
  catch (error) {
@@ -629,17 +700,125 @@ export class PluginControlPlaneService extends Service {
629
700
  throw error;
630
701
  }
631
702
  }
703
+ let revisionCapabilities;
704
+ if (this.config.revisionCapabilities) {
705
+ const config = this.config.revisionCapabilities;
706
+ const delivery = current('delivery');
707
+ if (typeof delivery.validateOwnerAgentForRoute !== 'function') {
708
+ throw new Error('plugin-control-plane: revision adoption requires authenticated live owner Agent admission');
709
+ }
710
+ const journal = new RevisionCapabilityJournal({ path: join(this.config.statePath, 'revision-adoptions.sqlite'), config });
711
+ try {
712
+ revisionCapabilities = new RevisionCapabilityRuntime({ ctx: jobsCtx, config, journal,
713
+ regressionRequired: this.config.revisionRegressions !== undefined,
714
+ ports: {
715
+ inspect: planId => {
716
+ const certificate = this.inspectVerifiedRevision(planId);
717
+ if (!certificate)
718
+ throw new Error('revision adoption requires a fresh independent certificate');
719
+ const prepared = this.inspectPreparedRevision(planId);
720
+ const source = this.store.readRevisionSource(planId);
721
+ if (!source)
722
+ throw new Error('revision adoption source archive unavailable');
723
+ const regression = this.config.revisionRegressions === undefined
724
+ ? undefined : this.inspectVerifiedRevisionRegression(planId);
725
+ return { certificate, artifact: Buffer.from(prepared.artifact), source,
726
+ plan: { id: prepared.plan.id, digest: prepared.plan.digest, name: prepared.plan.name },
727
+ ...(regression === undefined ? {} : { regression }), owner: config.owner };
728
+ },
729
+ recheck: async (planId, signal) => {
730
+ await this.inspectPreparedRevisionReviewContext(planId, signal);
731
+ if (this.config.sourceBuild && 'backend' in this.config.sourceBuild
732
+ && this.config.sourceBuild.backend === 'linux-native') {
733
+ await this.inspectPreparedRevisionLinuxBuild(planId, signal);
734
+ }
735
+ if (!this.inspectVerifiedRevision(planId))
736
+ throw new Error('revision adoption independent certificate changed');
737
+ },
738
+ parent: planId => {
739
+ const adoption = capabilities?.inspectAdoptionVersion(planId);
740
+ if (!adoption)
741
+ throw new Error('revision adoption parent adoption unavailable');
742
+ return adoption;
743
+ },
744
+ recheckParent: async (planId, signal) => {
745
+ if (!capabilities)
746
+ throw new Error('revision adoption parent adoption unavailable');
747
+ await capabilities.recheckAdoptionVersion(planId, signal);
748
+ },
749
+ inFlightParentCalls: planId => capabilities?.inspectInFlightCalls(planId) ?? 0,
750
+ suspendParent: async (planId) => {
751
+ if (!capabilities)
752
+ throw new Error('revision adoption parent adoption unavailable');
753
+ await capabilities.suspendEntry(planId);
754
+ },
755
+ resumeParent: async (planId) => {
756
+ if (!capabilities)
757
+ throw new Error('revision adoption parent adoption unavailable');
758
+ await capabilities.resumeEntry(planId);
759
+ },
760
+ runVersion: input => {
761
+ if (!capabilities)
762
+ throw new Error('revision adoption runner unavailable');
763
+ return capabilities.runRevisionArtifact(input);
764
+ },
765
+ recheckExecution: async (certificate, signal) => {
766
+ if (!capabilities)
767
+ throw new Error('revision adoption runner unavailable');
768
+ await capabilities.verifyArtifactExecution(certificate, signal);
769
+ },
770
+ matchesExecution: certificate => capabilities?.matchesArtifactExecution(certificate) === true,
771
+ assertCaller: execution => {
772
+ if (!execution.agent)
773
+ throw new Error('revision capability requires an authenticated owner Agent');
774
+ const receipt = delivery.validateOwnerAgentForRoute(execution.agent, config.owner.authorityId);
775
+ if (!receipt || Object.keys(config.owner).some(key => receipt[key] !== config.owner[key])) {
776
+ throw new Error('revision capability caller is not the current authorized owner');
777
+ }
778
+ },
779
+ } });
780
+ }
781
+ catch (error) {
782
+ journal.close();
783
+ throw error;
784
+ }
785
+ }
786
+ const closeRevision = async () => {
787
+ const currentRuntime = revisionCapabilities;
788
+ revisionCapabilities = undefined;
789
+ if (this.revisionCapabilityRuntime === currentRuntime)
790
+ this.revisionCapabilityRuntime = undefined;
791
+ if (currentRuntime)
792
+ await currentRuntime.close();
793
+ };
632
794
  const sourceBuild = this.config.sourceBuild;
633
795
  let nativeRunner;
634
796
  try {
635
- nativeRunner = 'backend' in sourceBuild ? new NativeMacVerifierRunner({ backend: 'macos-seatbelt',
636
- stateRoot: sourceBuild.stateRoot, authorityDigest: sourceBuild.authorityDigest,
637
- runtime: sourceBuild.runtime, source: NATIVE_SOURCE_COMPILER, expiresAt: sourceBuild.expiresAt,
638
- maxRuns: sourceBuild.maxRuns, maxTotalDurationMs: sourceBuild.maxTotalDurationMs,
639
- maxDurationMs: sourceBuild.timeoutMs, maxOutputBytes: sourceBuild.outputBytes }) : undefined;
797
+ nativeRunner = !('backend' in sourceBuild) ? undefined : sourceBuild.backend === 'macos-seatbelt'
798
+ ? { backend: 'macos-seatbelt', runner: new NativeMacVerifierRunner({ backend: 'macos-seatbelt',
799
+ stateRoot: sourceBuild.stateRoot, authorityDigest: sourceBuild.authorityDigest,
800
+ runtime: sourceBuild.runtime, source: NATIVE_SOURCE_COMPILER, expiresAt: sourceBuild.expiresAt,
801
+ maxRuns: sourceBuild.maxRuns, maxTotalDurationMs: sourceBuild.maxTotalDurationMs,
802
+ maxDurationMs: sourceBuild.timeoutMs, maxOutputBytes: sourceBuild.outputBytes }) }
803
+ : { backend: 'linux-native', runner: new LinuxRepositoryRunner({ backend: 'linux-native',
804
+ stateRoot: sourceBuild.stateRoot, authorityDigest: sourceBuild.authorityDigest,
805
+ runtime: sourceBuild.runtime, cgroupRoots: sourceBuild.cgroupRoots, expiresAt: sourceBuild.expiresAt,
806
+ maxRuns: sourceBuild.maxRuns, maxTotalDurationMs: sourceBuild.maxTotalDurationMs,
807
+ ...(sourceBuild.stateSeal === undefined ? {} : { stateSeal: sourceBuild.stateSeal }),
808
+ ...(sourceBuild.userNamespace === undefined ? {} : { userNamespace: sourceBuild.userNamespace,
809
+ systemSnapshot: sourceBuild.systemSnapshot }),
810
+ limits: { maxDurationMs: sourceBuild.timeoutMs, maxInputBytes: 64 * 1024 ** 2,
811
+ maxOutputBytes: sourceBuild.outputBytes, maxArtifactBytes: 32 * 1024 ** 2, maxFiles: 128,
812
+ memoryMiB: sourceBuild.memoryMiB, cpus: sourceBuild.cpus, pidsLimit: sourceBuild.pidsLimit,
813
+ workspaceMiB: sourceBuild.workspaceMiB, workspaceInodes: sourceBuild.workspaceInodes,
814
+ tmpMiB: sourceBuild.temporaryMiB, tmpInodes: sourceBuild.temporaryInodes, inputMiB: 65, inputInodes: 16 } }) };
640
815
  }
641
816
  catch (error) {
642
817
  unsubscribeTaskChanges?.();
818
+ try {
819
+ await closeRevision();
820
+ }
821
+ catch { /* original error wins */ }
643
822
  if (capabilities) {
644
823
  await capabilities.close();
645
824
  this.creationCapabilityRuntimes.delete(capabilities);
@@ -648,7 +827,13 @@ export class PluginControlPlaneService extends Service {
648
827
  }
649
828
  this.nativeSourceRunner = nativeRunner;
650
829
  let runtime;
830
+ const preparationGeneration = {};
651
831
  try {
832
+ // Restored fresh adoptions read their original Linux build receipt.
833
+ // Their source runner must exist before capability reconciliation.
834
+ await capabilities?.start();
835
+ await revisionCapabilities?.start();
836
+ this.abort.signal.throwIfAborted();
652
837
  runtime = new SourceJobRuntime({ config: this.config.sourceJobs, build: sourceBuild, statePath: this.config.statePath, store: this.store,
653
838
  ports: {
654
839
  automations: {
@@ -661,6 +846,7 @@ export class PluginControlPlaneService extends Service {
661
846
  },
662
847
  delivery: { validateOwnerRoute: request => current('delivery').validateOwnerRoute(request) },
663
848
  }, withGapSourceFence: (gapId, owner, callback) => this.taskGaps.withCurrent(gapId, owner, callback),
849
+ withWorkItemGrowthRunFence: (run, gapId, owner, generation, callback) => this.withWorkItemGrowthFrame(run, gapId, owner, generation, callback),
664
850
  assertGrowthRun: (run, gapId, owner, generation) => this.assertSourceGrowthRun(run, gapId, owner, generation),
665
851
  ...(this.config.revisionVerifications ? { inspectRevisionParent: this.inspectRevisionParent,
666
852
  verifyPreparedRevision: (job, signal) => this.verifyPreparedRevision(job, signal, () => jobsCtx.get('assistantVerifier')) } : {}),
@@ -672,6 +858,12 @@ export class PluginControlPlaneService extends Service {
672
858
  throw new Error('created capability has no prepared source plan');
673
859
  await capabilities.adopt(job.planId, signal);
674
860
  } } : {}),
861
+ ...(revisionCapabilities ? { revisionAdoptionEligible: (planId) => revisionCapabilities.eligible(planId),
862
+ adoptVerifiedRevision: async (job, signal) => {
863
+ if (!job.planId)
864
+ throw new Error('revision adoption has no prepared source plan');
865
+ await revisionCapabilities.adopt(job.planId, signal);
866
+ } } : {}),
675
867
  ...(this.config.sourceApprovals ? { approvePrepared: async (job, signal) => {
676
868
  if (!job.planId)
677
869
  throw new Error('source job has no prepared plan');
@@ -694,15 +886,21 @@ export class PluginControlPlaneService extends Service {
694
886
  throw new Error('source job has no released plan');
695
887
  await this.adoptOwnerSourceRelease({ sourcePlanId: job.planId, signal, expectedTrustDigest: job.intent.trustDigest, assertCurrent });
696
888
  } } : {}),
697
- trust: () => this.boundTrust(), prepare: (job, signal, assertCurrent) => this.prepareSourceJob(job, signal, assertCurrent),
698
- ...(nativeRunner ? { inspectNativeQuiescence: (key) => nativeRunner.inspectQuiescence(key) } : {}),
889
+ trust: () => this.boundTrust(), prepare: (job, signal, assertCurrent) => this.prepareSourceJob(job, signal, assertCurrent, nativeRunner?.backend === 'linux-native' ? { runner: nativeRunner.runner, generation: preparationGeneration } : undefined),
890
+ ...(nativeRunner?.backend === 'linux-native' ? { isPreparationNotStarted: (error, job) => consumeCleanedLinuxSourcePreflight(error, job, preparationGeneration),
891
+ deterministicBuildFailure: (error, job) => consumeCleanedLinuxSourceBuildFailure(error, job, preparationGeneration) } : {}),
892
+ ...(nativeRunner ? { inspectNativeQuiescence: (key) => nativeRunner.runner.inspectQuiescence(key) } : {}),
699
893
  });
700
894
  }
701
895
  catch (error) {
702
896
  if (this.nativeSourceRunner === nativeRunner)
703
897
  this.nativeSourceRunner = undefined;
704
- await nativeRunner?.close();
898
+ await nativeRunner?.runner.close();
705
899
  unsubscribeTaskChanges?.();
900
+ try {
901
+ await closeRevision();
902
+ }
903
+ catch { /* original error wins */ }
706
904
  if (capabilities) {
707
905
  await capabilities.close();
708
906
  this.creationCapabilityRuntimes.delete(capabilities);
@@ -710,6 +908,8 @@ export class PluginControlPlaneService extends Service {
710
908
  throw error;
711
909
  }
712
910
  this.sourceRuntimes.add(runtime);
911
+ this.creationCapabilityRuntime = capabilities;
912
+ this.revisionCapabilityRuntime = revisionCapabilities;
713
913
  const close = async () => {
714
914
  unsubscribeTaskChanges?.();
715
915
  if (this.sourceRuntime === runtime)
@@ -726,18 +926,30 @@ export class PluginControlPlaneService extends Service {
726
926
  this.sourceRuntimes.delete(runtime);
727
927
  if (this.creationCapabilityRuntime === capabilities)
728
928
  this.creationCapabilityRuntime = undefined;
729
- const results = await Promise.allSettled([nativeRunner?.close(), capabilities?.close()]);
929
+ let revisionCloseError;
930
+ try {
931
+ await closeRevision();
932
+ }
933
+ catch (error) {
934
+ revisionCloseError = error;
935
+ }
936
+ const results = await Promise.allSettled([nativeRunner?.runner.close(), capabilities?.close()]);
730
937
  if (capabilities)
731
938
  this.creationCapabilityRuntimes.delete(capabilities);
732
939
  const failures = results.filter((result) => result.status === 'rejected');
733
- if (closeError !== undefined || failures.length) {
734
- throw new AggregateError([...(closeError === undefined ? [] : [closeError]), ...failures.map(result => result.reason)], 'source runtime cleanup failed');
940
+ if (closeError !== undefined || revisionCloseError !== undefined || failures.length) {
941
+ throw new AggregateError([...(closeError === undefined ? [] : [closeError]),
942
+ ...(revisionCloseError === undefined ? [] : [revisionCloseError]), ...failures.map(result => result.reason)], 'source runtime cleanup failed');
735
943
  }
736
944
  };
737
945
  try {
738
946
  runtime.start();
739
947
  this.abort.signal.throwIfAborted();
740
948
  this.sourceRuntime = runtime;
949
+ // Producer/binding readiness can arrive while capabilities.start() is
950
+ // awaiting its source checks, before these runtime fields are public.
951
+ // Reconcile once after publication so that notification is not lost.
952
+ this.reconcileCreatedCapabilities();
741
953
  return close;
742
954
  }
743
955
  catch (error) {
@@ -782,6 +994,10 @@ export class PluginControlPlaneService extends Service {
782
994
  return discover(loaded.catalog, capability);
783
995
  }
784
996
  /** Host only: reread the exact source; no caller text or ratings are admitted. */
997
+ recordOwnerWorkItemFailureGap = (source) => {
998
+ this.abort.signal.throwIfAborted();
999
+ return this.taskGaps.recordWorkItem(source);
1000
+ };
785
1001
  recordOwnerTaskFailureGap = (source) => {
786
1002
  this.abort.signal.throwIfAborted();
787
1003
  return this.taskGaps.record(source);
@@ -1021,7 +1237,8 @@ export class PluginControlPlaneService extends Service {
1021
1237
  /** Host-only durable Usage reader; the caller's binding is always an expectation. */
1022
1238
  registerSourceGrowthRunProducer = (producer) => {
1023
1239
  this.abort.signal.throwIfAborted();
1024
- if (producer?.protocol !== 'assistant-growth-source-run-producer/v1' || typeof producer.inspect !== 'function'
1240
+ if (!['assistant-growth-source-run-producer/v1', 'assistant-growth-source-run-producer/v2'].includes(producer?.protocol) || typeof producer.inspect !== 'function'
1241
+ || producer.protocol === 'assistant-growth-source-run-producer/v2' && typeof producer.withWorkItemRunFence !== 'function'
1025
1242
  || this.growthRunProducer !== undefined || this.growthProviderDraining)
1026
1243
  throw new Error('plugin-control-plane: source growth run producer is invalid or already registered');
1027
1244
  this.growthRunProducer = producer;
@@ -1081,6 +1298,44 @@ export class PluginControlPlaneService extends Service {
1081
1298
  this.abort.signal.throwIfAborted();
1082
1299
  return this.creationCapabilityRuntime?.inspectCallEvidence(planId) ?? [];
1083
1300
  };
1301
+ /** Host-only read-only revision adoption readiness; undefined when not configured or not admitting. */
1302
+ inspectRevisionAdoptionReadiness = () => {
1303
+ this.abort.signal.throwIfAborted();
1304
+ return this.revisionCapabilityRuntime?.inspectReadiness();
1305
+ };
1306
+ /** Host-only current stable entry version; no certificate, artifact bytes or key material escape. */
1307
+ inspectRevisionCapabilityEntry = (entryId) => {
1308
+ this.abort.signal.throwIfAborted();
1309
+ return this.revisionCapabilityRuntime?.inspectEntry(entryId);
1310
+ };
1311
+ /** Host-only bounded call attribution of one stable entry, including the exact version per call. */
1312
+ inspectRevisionCapabilityCalls = (entryId) => {
1313
+ this.abort.signal.throwIfAborted();
1314
+ return this.revisionCapabilityRuntime?.inspectCallEvidence(entryId) ?? [];
1315
+ };
1316
+ /**
1317
+ * Host-only authenticated-owner exact parent rollback under its own finite grant. The parent
1318
+ * adoption receipt must still be current and the entry must be on the exact expected revision.
1319
+ */
1320
+ rollbackRevisionCapabilityAdoption = async (input) => {
1321
+ this.abort.signal.throwIfAborted();
1322
+ const runtime = this.revisionCapabilityRuntime;
1323
+ const config = this.config.revisionCapabilities;
1324
+ if (!runtime || !config)
1325
+ throw new Error('revision adoption is not configured');
1326
+ const delivery = this.ctx.get('assistantDelivery', false);
1327
+ if (!delivery || typeof delivery.validateOwnerRoute !== 'function')
1328
+ throw new Error('revision rollback requires Delivery owner validation');
1329
+ const receipt = delivery.validateOwnerRoute({ authorityId: config.owner.authorityId, principalId: config.owner.principalId,
1330
+ workspace: config.owner.workspace, agentPreset: config.owner.agentPreset });
1331
+ if (!receipt || receipt.authorityId !== config.owner.authorityId || receipt.authorityHash !== config.owner.authorityHash
1332
+ || receipt.principalId !== config.owner.principalId || receipt.principalRecordId !== config.owner.principalRecordId
1333
+ || receipt.principalVersion !== config.owner.principalVersion || receipt.workspace !== config.owner.workspace
1334
+ || receipt.agentPreset !== config.owner.agentPreset) {
1335
+ throw new Error('revision rollback caller is not the current authorized owner');
1336
+ }
1337
+ return runtime.rollback({ entryId: input.entryId, expectedCurrentPlanId: input.expectedVersionPlanId });
1338
+ };
1084
1339
  /** Host-only historical source data. It grants no continuation, activation or replacement. */
1085
1340
  inspectCreatedCapabilitySource = (planId) => {
1086
1341
  this.abort.signal.throwIfAborted();
@@ -1142,29 +1397,109 @@ export class PluginControlPlaneService extends Service {
1142
1397
  };
1143
1398
  /** Host-only current association of exact adopted call versions with authenticated task heads. */
1144
1399
  inspectCreatedCapabilityTaskAssociations = (planId) => {
1400
+ const input = this.createdCapabilityFeedbackInput(planId);
1401
+ if (!input)
1402
+ return [];
1403
+ try {
1404
+ return inspectCreationCapabilityTaskAssociations(input());
1405
+ }
1406
+ catch {
1407
+ return [];
1408
+ }
1409
+ };
1410
+ /** Host-only descriptive ordinary-use counts over the same fenced associations; never causal or adoption evidence. */
1411
+ inspectCreatedCapabilityOrdinaryUseReport = (planId) => {
1412
+ const input = this.createdCapabilityFeedbackInput(planId);
1413
+ if (!input)
1414
+ return undefined;
1415
+ try {
1416
+ return inspectCreationCapabilityOrdinaryUseReport(input());
1417
+ }
1418
+ catch {
1419
+ return undefined;
1420
+ }
1421
+ };
1422
+ /** Host-only observational before/after attribution of one adoption over the owner's certified tasks; never causal. */
1423
+ inspectCreatedCapabilityAvailabilityAttribution = (planId, options = {}) => {
1424
+ const input = this.createdCapabilityFeedbackInput(planId);
1425
+ if (!input)
1426
+ return undefined;
1427
+ try {
1428
+ return inspectCreationCapabilityAvailabilityAttribution({ ...input(),
1429
+ otherAdoptedAt: this.creationCapabilityRuntime?.inspectOtherAdoptionTimes(planId) ?? [],
1430
+ ...(options.windowMs === undefined ? {} : { windowMs: options.windowMs }) });
1431
+ }
1432
+ catch {
1433
+ return undefined;
1434
+ }
1435
+ };
1436
+ /**
1437
+ * Host-only longitudinal ordinary-use series of one adoption in fixed windows with a same-scope session-cohort
1438
+ * control and a model-stratified difference-in-differences estimate; observational, never causal. Pure read.
1439
+ */
1440
+ inspectCreatedCapabilityOrdinaryUseTrend = (planId, options = {}) => {
1441
+ const input = this.createdCapabilityFeedbackInput(planId);
1442
+ if (!input)
1443
+ return undefined;
1444
+ try {
1445
+ return inspectCreationCapabilityOrdinaryUseTrend({ ...input(),
1446
+ otherAdoptedAt: this.creationCapabilityRuntime?.inspectOtherAdoptionTimes(planId) ?? [],
1447
+ ...(options.windowMs === undefined ? {} : { windowMs: options.windowMs }),
1448
+ ...(options.preWindows === undefined ? {} : { preWindows: options.preWindows }),
1449
+ ...(options.maxPostWindows === undefined ? {} : { maxPostWindows: options.maxPostWindows }) });
1450
+ }
1451
+ catch {
1452
+ return undefined;
1453
+ }
1454
+ };
1455
+ /**
1456
+ * Host-only, owner-invoked: seal the current ordinary-use trend of one adoption into the append-only,
1457
+ * content-addressed snapshot ledger. Re-sealing an unchanged trend returns the existing row (`created: false`);
1458
+ * rows are never rewritten. Still observational (`causal: false`); never adoption or revocation evidence.
1459
+ */
1460
+ sealCreatedCapabilityOrdinaryUseTrendSnapshot = (planId, options = {}) => {
1461
+ const trend = this.inspectCreatedCapabilityOrdinaryUseTrend(planId, options);
1462
+ if (!trend)
1463
+ return undefined;
1464
+ try {
1465
+ return this.store.sealCreatedCapabilityTrendSnapshot(trend);
1466
+ }
1467
+ catch {
1468
+ return undefined;
1469
+ }
1470
+ };
1471
+ /** Host-only bounded newest-first read of sealed trend snapshots; undefined on invalid query or any tampered row. */
1472
+ inspectCreatedCapabilityOrdinaryUseTrendSnapshots = (query = {}) => {
1473
+ try {
1474
+ this.abort.signal.throwIfAborted();
1475
+ return this.store.listCreatedCapabilityTrendSnapshots(query);
1476
+ }
1477
+ catch {
1478
+ return undefined;
1479
+ }
1480
+ };
1481
+ /** The historical read is deferred so callers keep it inside their own failure boundary. */
1482
+ createdCapabilityFeedbackInput(planId) {
1145
1483
  this.abort.signal.throwIfAborted();
1146
1484
  const snapshot = this.creationCapabilityRuntime?.inspectAssociationEvidence(planId);
1147
1485
  if (!snapshot || !this.config.creationCapabilities)
1148
- return [];
1486
+ return undefined;
1149
1487
  const owner = this.config.creationCapabilities.owner;
1150
1488
  const evaluation = this.ctx.get('assistantEvaluation', false);
1151
1489
  const delivery = this.ctx.get('assistantDelivery', false);
1152
1490
  if (!evaluation || !delivery)
1153
- return [];
1154
- try {
1491
+ return undefined;
1492
+ return () => {
1155
1493
  const historical = this.store.getRetainedPreparedCreation(planId);
1156
- return inspectCreationCapabilityTaskAssociations({ ...snapshot, owner, evaluation, delivery,
1494
+ return { ...snapshot, owner, evaluation, delivery,
1157
1495
  triggerInboxId: historical.reference.projection.subjectRef,
1158
1496
  readSourceCurrent: () => {
1159
1497
  this.assertCreatedCapabilityAssociationSource(snapshot.record);
1160
1498
  const current = this.taskGaps.inspectCurrent(historical.plan.gapId, historical.reference.owner);
1161
1499
  return { projection: current.canonical.projection, scopeWatermark: current.canonical.scopeWatermark };
1162
- } });
1163
- }
1164
- catch {
1165
- return [];
1166
- }
1167
- };
1500
+ } };
1501
+ };
1502
+ }
1168
1503
  /** Exact historical certificate and source check without nesting an Evaluation writer fence. */
1169
1504
  assertCreatedCapabilityAssociationSource(record) {
1170
1505
  this.abort.signal.throwIfAborted();
@@ -1252,11 +1587,113 @@ export class PluginControlPlaneService extends Service {
1252
1587
  !== ownerGrant[key])) {
1253
1588
  throw new Error('created capability retained source binding changed');
1254
1589
  }
1590
+ if (growth.protocol === 'assistant-growth/source-run/v2') {
1591
+ if (reference.schemaVersion !== 2 || growth.ownerDigest !== controlPlaneDigest(reference.owner))
1592
+ throw new Error('retained work item reference changed');
1593
+ assertWorkItemRunReference(growth, reference);
1594
+ this.assertWorkItemPinsLocal(growth, 'retained');
1595
+ return callback(growth);
1596
+ }
1597
+ return callback();
1598
+ });
1599
+ }
1600
+ inspectWorkItemPreparedCreationProtocol = () => {
1601
+ return !this.abort.signal.aborted && this.config.sourceJobs?.creation?.workItemFailures === true && this.sourceRuntime?.available() ? 'dsh-prepared-creation/v2' : undefined;
1602
+ };
1603
+ inspectWorkItemCreationReadiness = (input) => {
1604
+ try {
1605
+ this.abort.signal.throwIfAborted();
1606
+ const owner = inertCreationData(input).owner, config = this.config, source = this.sourceRuntime?.inspectWorkItemReadiness(owner);
1607
+ if (!source || !config.sourceJobs || !config.sourceBuild || config.sourceJobs.creation?.workItemFailures !== true || config.creationCapabilities?.workItemFailures !== true || !config.creationVerifications)
1608
+ return undefined;
1609
+ const delivery = this.ctx.get('assistantDelivery', false);
1610
+ const verifier = this.ctx.get('assistantVerifier', false);
1611
+ if (typeof delivery?.withOwnerWorkItemEngineeringContextFence !== 'function' || typeof verifier?.inspectWorkItemCreationReviewReadiness !== 'function' || typeof this.creationCapabilityRuntime?.inspectWorkItemAdoptionReadiness !== 'function')
1612
+ return undefined;
1613
+ const actual = delivery.validateOwnerRoute({ authorityId: owner.authorityId, principalId: owner.principalId, workspace: owner.workspace, agentPreset: owner.agentPreset });
1614
+ if (!isSourceOwnerContinuation(actual, owner) || Object.entries(config.creationCapabilities.owner).some(([key, value]) => actual[key] !== value))
1615
+ return undefined;
1616
+ const review = verifier.inspectWorkItemCreationReviewReadiness(), adoption = this.creationCapabilityRuntime.inspectWorkItemAdoptionReadiness();
1617
+ if (!review || !adoption || review.protocol !== 'assistant-growth/work-item-review-readiness/v1' || adoption.protocol !== 'assistant-growth/work-item-adoption-readiness/v1'
1618
+ || review.sourceProtocol !== WORK_ITEM_ENGINEERING_PROTOCOL || adoption.sourceProtocol !== WORK_ITEM_ENGINEERING_PROTOCOL || review.runProtocol !== 'assistant-growth/source-run/v2' || adoption.runProtocol !== 'assistant-growth/source-run/v2'
1619
+ || review.workItemFailures !== true || adoption.workItemFailures !== true || controlPlaneDigest(review.authority) !== controlPlaneDigest(config.creationVerifications.authority)
1620
+ || !createPublicKey(review.publicKey).export({ type: 'spki', format: 'der' }).equals(createPublicKey(config.creationVerifications.publicKey).export({ type: 'spki', format: 'der' }))
1621
+ || adoption.authority.authorityDigest !== creationCapabilityAuthorityDigest({ path: join(config.statePath, 'creation-adoptions.sqlite'), config: config.creationCapabilities })
1622
+ || adoption.authority.authorityId !== config.creationCapabilities.authorityId || adoption.authority.expiresAt !== config.creationCapabilities.expiresAt)
1623
+ return undefined;
1624
+ const pins = { protocol: 'assistant-growth/work-item-authorities/v1', sourceJobs: { authorityId: source.authority.id, authorityDigest: source.authority.digest, expiresAt: source.authority.expiresAt },
1625
+ creationGrant: { id: source.creation.id, digest: controlPlaneDigest(source.creation), expiresAt: source.creation.expiresAt }, creationAcceptance: structuredClone(review.authority), adoption: structuredClone(adoption.authority) };
1626
+ validateWorkItemGrowthAuthorityPins(pins);
1627
+ if (source.authority.digest !== controlPlaneDigest({ config: config.sourceJobs, build: config.sourceBuild }) || [pins.sourceJobs.expiresAt, pins.creationGrant.expiresAt, pins.creationAcceptance.expiresAt, pins.adoption.expiresAt].some(expiry => Date.now() >= expiry))
1628
+ return undefined;
1629
+ const remaining = { ...source.remaining, verifications: review.remainingVerifications, adoptions: adoption.remainingAdoptions };
1630
+ if (Object.values(remaining).some(count => !Number.isSafeInteger(count) || count < 0))
1631
+ return undefined;
1632
+ return { protocol: 'assistant-growth/work-item-creation-readiness/v1', sourceProtocol: WORK_ITEM_ENGINEERING_PROTOCOL, runProtocol: 'assistant-growth/source-run/v2', workItemFailures: true, pins, remaining, available: Object.values(remaining).every(count => count > 0) && review.available && adoption.available };
1633
+ }
1634
+ catch {
1635
+ return undefined;
1636
+ }
1637
+ };
1638
+ assertWorkItemPinsLocal(run, phase = 'current') {
1639
+ const jobs = this.config.sourceJobs, verification = this.config.creationVerifications, adoption = this.config.creationCapabilities;
1640
+ if (!jobs || !this.config.sourceBuild || jobs.creation?.workItemFailures !== true || !verification || adoption?.workItemFailures !== true
1641
+ || run.authorities.sourceJobs.authorityId !== jobs.authorityId || run.authorities.sourceJobs.authorityDigest !== controlPlaneDigest({ config: jobs, build: this.config.sourceBuild }) || run.authorities.sourceJobs.expiresAt !== jobs.expiresAt
1642
+ || run.authorities.creationGrant.id !== jobs.creation.id || run.authorities.creationGrant.digest !== controlPlaneDigest(jobs.creation) || run.authorities.creationGrant.expiresAt !== jobs.creation.expiresAt
1643
+ || controlPlaneDigest(run.creationAcceptance) !== controlPlaneDigest(verification.authority) || controlPlaneDigest(run.authorities.creationAcceptance) !== controlPlaneDigest(verification.authority)
1644
+ || run.authorities.adoption.authorityId !== adoption.authorityId || run.authorities.adoption.authorityDigest !== creationCapabilityAuthorityDigest({ path: join(this.config.statePath, 'creation-adoptions.sqlite'), config: adoption }) || run.authorities.adoption.expiresAt !== adoption.expiresAt
1645
+ || (phase === 'current' ? [run.authorities.sourceJobs.expiresAt, run.authorities.creationGrant.expiresAt, run.authorities.creationAcceptance.expiresAt, run.authorities.adoption.expiresAt] : [run.authorities.adoption.expiresAt]).some(expiry => Date.now() >= expiry))
1646
+ throw new Error('work item creation authority pins changed or expired');
1647
+ }
1648
+ assertWorkItemRunLocal(run, gapId, owner, generation) {
1649
+ const reference = this.store.getOwnerTaskFailureReference(gapId);
1650
+ if (!reference || reference.schemaVersion !== 2 || run.ownerDigest !== controlPlaneDigest(reference.owner) || !isSourceOwnerContinuation(owner, reference.owner))
1651
+ throw new Error('work item creation owner reference changed');
1652
+ assertWorkItemRunReference(run, reference);
1653
+ this.assertWorkItemPinsLocal(run);
1654
+ if (Date.now() >= run.expiresAt || generation && Date.now() >= run.generationDeadlineAt)
1655
+ throw new Error('work item source run expired');
1656
+ if (run.modelOrigin === 'inherited-owner-task' && this.workItemCreationFrame && controlPlaneDigest(this.workItemCreationFrame.source.task.proofIdentity.supplier) !== controlPlaneDigest(run.model))
1657
+ throw new Error('work item source supplier changed');
1658
+ }
1659
+ withWorkItemGrowthFrame(run, gapId, owner, generation, callback) {
1660
+ this.abort.signal.throwIfAborted();
1661
+ if (this.workItemCreationFrame) {
1662
+ if (this.workItemCreationFrame.gapId !== gapId || sourceGrowthRunDigest(this.workItemCreationFrame.run) !== sourceGrowthRunDigest(run))
1663
+ throw new Error('work item growth frame reentry');
1664
+ this.assertWorkItemRunLocal(run, gapId, owner, generation);
1255
1665
  return callback();
1666
+ }
1667
+ const producer = this.growthRunProducer;
1668
+ if (!producer || producer.protocol !== 'assistant-growth-source-run-producer/v2')
1669
+ throw new SourceGrowthRunUnavailableError('work item growth producer unavailable');
1670
+ const readiness = this.inspectWorkItemCreationReadiness({ owner });
1671
+ if (!readiness)
1672
+ throw new SourceGrowthRunUnavailableError('work item growth peers unavailable');
1673
+ if (controlPlaneDigest(readiness.pins) !== controlPlaneDigest(run.authorities))
1674
+ throw new Error('work item growth authority pins changed');
1675
+ const source = this.taskGaps.engineeringSnapshot(gapId, owner);
1676
+ return producer.withWorkItemRunFence({ runId: run.runId, intentDigest: run.intentDigest, expectedGrowthRunDigest: sourceGrowthRunDigest(run) }, actual => {
1677
+ if (this.growthRunProducer !== producer || sourceGrowthRunDigest(actual) !== sourceGrowthRunDigest(run) || this.workItemCreationFrame)
1678
+ throw new Error('work item producer changed its true binding');
1679
+ this.workItemCreationFrame = { gapId, run: actual, source };
1680
+ try {
1681
+ return this.store.withOwnerTaskFailureGapAdmission(gapId, () => { this.assertWorkItemRunLocal(actual, gapId, owner, generation); return callback(); });
1682
+ }
1683
+ finally {
1684
+ this.workItemCreationFrame = undefined;
1685
+ }
1256
1686
  });
1257
1687
  }
1258
1688
  assertSourceGrowthRun(expected, gapId, owner, generation) {
1259
1689
  validateSourceGrowthRunBinding(expected);
1690
+ if (expected.protocol === 'assistant-growth/source-run/v2') {
1691
+ if (this.workItemCreationFrame) {
1692
+ this.assertWorkItemRunLocal(expected, gapId, owner, generation);
1693
+ return;
1694
+ }
1695
+ return this.withWorkItemGrowthFrame(expected, gapId, owner, generation, () => { });
1696
+ }
1260
1697
  const producer = this.growthRunProducer;
1261
1698
  if (!producer)
1262
1699
  throw new SourceGrowthRunUnavailableError('source growth run producer unavailable');
@@ -1264,6 +1701,8 @@ export class PluginControlPlaneService extends Service {
1264
1701
  if (!actual)
1265
1702
  throw new Error('source growth run is absent or no longer current');
1266
1703
  validateSourceGrowthRunBinding(actual);
1704
+ if (actual.protocol !== 'assistant-growth/source-run/v1')
1705
+ throw new Error('foreground source cannot use a work item run');
1267
1706
  if (sourceGrowthRunDigest(actual) !== sourceGrowthRunDigest(expected)
1268
1707
  || expected.ownerDigest !== controlPlaneDigest(owner))
1269
1708
  throw new Error('source growth run immutable binding changed');
@@ -1282,7 +1721,7 @@ export class PluginControlPlaneService extends Service {
1282
1721
  throw new Error('source growth run creation acceptance policy changed or was not pinned before authoring');
1283
1722
  }
1284
1723
  const reference = this.store.getOwnerTaskFailureReference(gapId);
1285
- if (!reference || expected.source.outcomeId !== reference.outcomeId
1724
+ if (!reference || reference.schemaVersion !== 1 || expected.source.outcomeId !== reference.outcomeId
1286
1725
  || expected.source.sourceDigest !== reference.sourceDigest
1287
1726
  || controlPlaneDigest(expected.source.projection) !== controlPlaneDigest(reference.projection)
1288
1727
  || expected.ownerDigest !== controlPlaneDigest(reference.owner))
@@ -1306,6 +1745,12 @@ export class PluginControlPlaneService extends Service {
1306
1745
  || Date.now() >= plan.expiresAt || !plan.creation || Date.now() >= plan.creation.grant.expiresAt) {
1307
1746
  throw new Error('prepared creation package is unavailable for independent verification');
1308
1747
  }
1748
+ if (reference.schemaVersion === 2) {
1749
+ const run = plan.creation.growthRun;
1750
+ if (run?.protocol !== 'assistant-growth/source-run/v2')
1751
+ throw new Error('work item prepared creation run unavailable');
1752
+ return this.withWorkItemGrowthFrame(run, plan.gapId, reference.owner, false, () => ({ protocol: 'dsh-prepared-creation/v2', plan: structuredClone(this.store.getSourcePlan(plan.id)), job: structuredClone(this.store.getPreparedSourceJob(plan.id)), reference: structuredClone(reference), source: structuredClone(this.workItemCreationFrame.source), artifact: this.store.readPreparedSourceArtifact(plan.id) }));
1753
+ }
1309
1754
  return this.taskGaps.withCurrent(plan.gapId, reference.owner, () => {
1310
1755
  this.abort.signal.throwIfAborted();
1311
1756
  const job = this.store.getPreparedSourceJob(plan.id);
@@ -1339,6 +1784,17 @@ export class PluginControlPlaneService extends Service {
1339
1784
  const reference = this.store.getOwnerTaskFailureReference(plan.gapId);
1340
1785
  if (!reference)
1341
1786
  throw new Error('creation verification source unavailable');
1787
+ if (reference.schemaVersion === 2) {
1788
+ const run = plan.creation?.growthRun;
1789
+ if (run?.protocol !== 'assistant-growth/source-run/v2')
1790
+ throw new Error('work item verification run unavailable');
1791
+ return this.withWorkItemGrowthFrame(run, plan.gapId, reference.owner, false, () => {
1792
+ const current = this.store.getSourcePlan(input.planId), job = this.store.getPreparedSourceJob(input.planId);
1793
+ if (current.digest !== input.planDigest || current.preparedEvidence?.pack.sha256 !== input.artifactSha256 || controlPlaneDigest(reference) !== input.referenceDigest || sourceGrowthRunDigest(run) !== input.growthRunDigest || !job.intent.creation?.growthRun || sourceGrowthRunDigest(job.intent.creation.growthRun) !== input.growthRunDigest || createHash('sha256').update(this.store.readPreparedSourceArtifact(input.planId)).digest('hex') !== input.artifactSha256)
1794
+ throw new Error('work item prepared verification exact binding changed');
1795
+ return callback(run);
1796
+ });
1797
+ }
1342
1798
  return this.taskGaps.withCurrent(plan.gapId, reference.owner, () => {
1343
1799
  this.abort.signal.throwIfAborted();
1344
1800
  const current = this.store.getSourcePlan(input.planId);
@@ -1382,6 +1838,105 @@ export class PluginControlPlaneService extends Service {
1382
1838
  current();
1383
1839
  return review;
1384
1840
  };
1841
+ /** Host-only read of the original successful build; never admits or replays a job. */
1842
+ inspectPreparedCreationLinuxBuild = async (planId, signal) => {
1843
+ const combined = AbortSignal.any([this.abort.signal, signal, AbortSignal.timeout(120_000)]);
1844
+ combined.throwIfAborted();
1845
+ const prepared = this.inspectPreparedCreation(planId), build = this.config.sourceBuild;
1846
+ const native = this.nativeSourceRunner, evidence = prepared.plan.preparedEvidence;
1847
+ if (!build || !('backend' in build) || build.backend !== 'linux-native' || !build.userNamespace || !build.systemSnapshot
1848
+ || native?.backend !== 'linux-native'
1849
+ || evidence?.schemaVersion !== 3 || evidence.boundary !== 'linux-native-repository-v1'
1850
+ || !('backend' in prepared.job.intent.build) || prepared.job.intent.build.backend !== 'linux-native'
1851
+ || prepared.job.intent.nativeResource?.backend !== 'linux-native' || prepared.job.intent.nativeResource.key !== prepared.job.id
1852
+ || evidence.runtimeDigest !== linuxRuntimeDigest(build.runtime)
1853
+ || evidence.runtimeDigest !== native.runner.runtimeDigest || evidence.executionContractDigest !== native.runner.contractDigest
1854
+ || evidence.buildScriptSha256 !== createHash('sha256').update(LINUX_PREPARED_SOURCE_BUILD_SCRIPT).digest('hex')) {
1855
+ throw new Error('creation verification Linux build binding unavailable');
1856
+ }
1857
+ const evidenceDigest = controlPlaneDigest(evidence);
1858
+ const current = () => this.withPreparedCreationFence({ planId, planDigest: prepared.plan.digest,
1859
+ artifactSha256: evidence.pack.sha256, growthRunDigest: sourceGrowthRunDigest(prepared.plan.creation.growthRun),
1860
+ referenceDigest: controlPlaneDigest(prepared.reference) }, () => {
1861
+ combined.throwIfAborted();
1862
+ if (this.nativeSourceRunner !== native || this.config.sourceBuild !== build || Date.now() >= build.expiresAt
1863
+ || controlPlaneDigest(this.store.getSourcePlan(planId).preparedEvidence) !== evidenceDigest
1864
+ || controlPlaneDigest(this.store.getPreparedSourceJob(planId)) !== controlPlaneDigest(prepared.job)) {
1865
+ throw new Error('creation verification Linux build authority changed');
1866
+ }
1867
+ });
1868
+ current();
1869
+ if (controlPlaneDigest(await this.boundTrust()) !== prepared.job.intent.trustDigest)
1870
+ throw new Error('creation verification trust changed');
1871
+ await verifyLinuxRuntime(build.runtime, combined);
1872
+ current();
1873
+ const execution = await native.runner.inspectSuccessfulExecution(prepared.job.id, combined);
1874
+ current();
1875
+ const artifact = execution?.artifacts[0];
1876
+ if (!execution || execution.runtimeDigest !== evidence.runtimeDigest || execution.contractDigest !== evidence.executionContractDigest
1877
+ || execution.result.status !== 'succeeded' || !execution.result.quiescent || execution.result.exitCode !== 0
1878
+ || !execution.receiptBinding || controlPlaneDigest(execution.receiptBinding) !== controlPlaneDigest(evidence.receipt)
1879
+ || execution.artifacts.length !== 1 || artifact?.path !== LINUX_SOURCE_PACK_PATH || artifact.sha256 !== evidence.pack.sha256
1880
+ || artifact.bytes.length !== evidence.pack.sizeBytes || !Buffer.from(artifact.bytes).equals(prepared.artifact)
1881
+ || createHash('sha256').update((execution.result.stdout ?? '') + '\0' + (execution.result.stderr ?? '')).digest('hex') !== evidence.logDigest) {
1882
+ throw new Error('creation verification original Linux build receipt or artifact changed');
1883
+ }
1884
+ if (controlPlaneDigest(await this.boundTrust()) !== prepared.job.intent.trustDigest)
1885
+ throw new Error('creation verification trust changed');
1886
+ await verifyLinuxRuntime(build.runtime, combined);
1887
+ current();
1888
+ return { runtime: structuredClone(build.runtime), evidenceDigest };
1889
+ };
1890
+ /** Host-only read of the original successful revision build; never admits or replays a job. */
1891
+ inspectPreparedRevisionLinuxBuild = async (planId, signal) => {
1892
+ const combined = AbortSignal.any([this.abort.signal, signal, AbortSignal.timeout(120_000)]);
1893
+ combined.throwIfAborted();
1894
+ const prepared = this.inspectPreparedRevision(planId), build = this.config.sourceBuild;
1895
+ const native = this.nativeSourceRunner, evidence = prepared.plan.preparedEvidence;
1896
+ if (!build || !('backend' in build) || build.backend !== 'linux-native' || !build.userNamespace || !build.systemSnapshot
1897
+ || native?.backend !== 'linux-native'
1898
+ || evidence?.schemaVersion !== 3 || evidence.boundary !== 'linux-native-repository-v1'
1899
+ || !('backend' in prepared.job.intent.build) || prepared.job.intent.build.backend !== 'linux-native'
1900
+ || prepared.job.intent.nativeResource?.backend !== 'linux-native' || prepared.job.intent.nativeResource.key !== prepared.job.id
1901
+ || evidence.runtimeDigest !== linuxRuntimeDigest(build.runtime)
1902
+ || evidence.runtimeDigest !== native.runner.runtimeDigest || evidence.executionContractDigest !== native.runner.contractDigest
1903
+ || evidence.buildScriptSha256 !== createHash('sha256').update(LINUX_PREPARED_SOURCE_BUILD_SCRIPT).digest('hex')) {
1904
+ throw new Error('revision verification Linux build binding unavailable');
1905
+ }
1906
+ const sourceRevision = prepared.plan.sourceRevision;
1907
+ const evidenceDigest = controlPlaneDigest(evidence);
1908
+ const current = () => this.withPreparedRevisionFence({ planId, planDigest: prepared.plan.digest,
1909
+ artifactSha256: evidence.pack.sha256, growthRunDigest: sourceGrowthRunDigest(sourceRevision.growthRun),
1910
+ referenceDigest: controlPlaneDigest(prepared.reference), parentDigest: controlPlaneDigest(sourceRevision.parent) }, () => {
1911
+ combined.throwIfAborted();
1912
+ if (this.nativeSourceRunner !== native || this.config.sourceBuild !== build || Date.now() >= build.expiresAt
1913
+ || controlPlaneDigest(this.store.getSourcePlan(planId).preparedEvidence) !== evidenceDigest
1914
+ || controlPlaneDigest(this.store.getPreparedRevisionJob(planId)) !== controlPlaneDigest(prepared.job)) {
1915
+ throw new Error('revision verification Linux build authority changed');
1916
+ }
1917
+ });
1918
+ current();
1919
+ if (controlPlaneDigest(await this.boundTrust()) !== prepared.job.intent.trustDigest)
1920
+ throw new Error('revision verification trust changed');
1921
+ await verifyLinuxRuntime(build.runtime, combined);
1922
+ current();
1923
+ const execution = await native.runner.inspectSuccessfulExecution(prepared.job.id, combined);
1924
+ current();
1925
+ const artifact = execution?.artifacts[0];
1926
+ if (!execution || execution.runtimeDigest !== evidence.runtimeDigest || execution.contractDigest !== evidence.executionContractDigest
1927
+ || execution.result.status !== 'succeeded' || !execution.result.quiescent || execution.result.exitCode !== 0
1928
+ || !execution.receiptBinding || controlPlaneDigest(execution.receiptBinding) !== controlPlaneDigest(evidence.receipt)
1929
+ || execution.artifacts.length !== 1 || artifact?.path !== LINUX_SOURCE_PACK_PATH || artifact.sha256 !== evidence.pack.sha256
1930
+ || artifact.bytes.length !== evidence.pack.sizeBytes || !Buffer.from(artifact.bytes).equals(prepared.artifact)
1931
+ || createHash('sha256').update((execution.result.stdout ?? '') + '\0' + (execution.result.stderr ?? '')).digest('hex') !== evidence.logDigest) {
1932
+ throw new Error('revision verification original Linux build receipt or artifact changed');
1933
+ }
1934
+ if (controlPlaneDigest(await this.boundTrust()) !== prepared.job.intent.trustDigest)
1935
+ throw new Error('revision verification trust changed');
1936
+ await verifyLinuxRuntime(build.runtime, combined);
1937
+ current();
1938
+ return { runtime: structuredClone(build.runtime), evidenceDigest };
1939
+ };
1385
1940
  /** Signed behavior evidence over stored pack/checks. Adoption must await async review-context recheck. */
1386
1941
  inspectVerifiedCreation = (planId) => {
1387
1942
  const certificate = this.store.getCreationVerification(planId);
@@ -1691,13 +2246,21 @@ export class PluginControlPlaneService extends Service {
1691
2246
  throw new Error('plugin-control-plane: durable source jobs unavailable or still draining');
1692
2247
  return this.sourceRuntime.reconcileUnknown(input);
1693
2248
  };
1694
- async prepareSourceJob(job, signal, assertCurrent) {
2249
+ async prepareSourceJob(job, signal, assertCurrent, preflight) {
1695
2250
  this.abort.signal.throwIfAborted();
1696
- if (this.sourceBuilds.size !== 0 || this.sourceInspections.size !== 0)
1697
- throw new Error('plugin-control-plane: another source operation is draining');
2251
+ if (this.sourceBuilds.size !== 0 || this.sourceInspections.size !== 0) {
2252
+ const error = new Error('plugin-control-plane: another source operation is draining');
2253
+ // This call has not created an operation/worktree or called a runner.
2254
+ if (preflight && job.intent.nativeResource?.backend === 'linux-native' && job.intent.nativeResource.key === job.id
2255
+ && controlPlaneDigest(job.intent) === job.intentDigest) {
2256
+ throw cleanedLinuxSourcePreflight(error, job, preflight.generation, () => !this.abort.signal.aborted
2257
+ && this.nativeSourceRunner?.backend === 'linux-native' && this.nativeSourceRunner.runner === preflight.runner);
2258
+ }
2259
+ throw error;
2260
+ }
1698
2261
  const intent = job.intent;
1699
2262
  const operation = this.prepareSourcePlanOwned({ gapId: intent.gapId, name: intent.name, repository: intent.repository, files: intent.files,
1700
- idempotencyKey: `source-job-plan:${job.id}`, expectedBaseCommit: intent.baseCommit, ttlMs: intent.ttlMs, timeoutMs: intent.build.timeoutMs, offline: true, signal, assertCurrent }, job);
2263
+ idempotencyKey: `source-job-plan:${job.id}`, expectedBaseCommit: intent.baseCommit, ttlMs: intent.ttlMs, timeoutMs: intent.build.timeoutMs, offline: true, signal, assertCurrent }, job, preflight);
1701
2264
  this.sourceBuilds.add(operation);
1702
2265
  try {
1703
2266
  return await operation;
@@ -2016,7 +2579,7 @@ export class PluginControlPlaneService extends Service {
2016
2579
  void operation.then(() => this.sourceBuilds.delete(operation), () => this.sourceBuilds.delete(operation));
2017
2580
  return operation;
2018
2581
  }
2019
- async prepareSourcePlanOwned(input, sourceJob) {
2582
+ async prepareSourcePlanOwned(input, sourceJob, preflight) {
2020
2583
  const signal = input.signal === undefined ? this.abort.signal : AbortSignal.any([this.abort.signal, input.signal]);
2021
2584
  const gapOwner = sourceJob?.intent.owner ?? input.owner;
2022
2585
  const creating = sourceJob?.intent.mode === 'create';
@@ -2065,8 +2628,10 @@ export class PluginControlPlaneService extends Service {
2065
2628
  if (!Number.isSafeInteger(ttlMs) || ttlMs < 900_000 || ttlMs > 86_400_000) {
2066
2629
  throw new ControlPlaneCliError('INVALID_ARGUMENT', 'ttlMs must be an integer within 900000..86400000');
2067
2630
  }
2068
- const maximumTimeoutMs = this.config.sourceBuild !== undefined && !('backend' in this.config.sourceBuild)
2069
- && ['repository', 'portable-repository'].includes(this.config.sourceBuild.profile ?? '') ? 1_800_000 : 240_000;
2631
+ const buildConfig = this.config.sourceBuild;
2632
+ const repositoryBuild = buildConfig !== undefined && ('backend' in buildConfig
2633
+ ? buildConfig.backend === 'linux-native' : ['repository', 'portable-repository'].includes(buildConfig.profile ?? ''));
2634
+ const maximumTimeoutMs = repositoryBuild ? 1_800_000 : 240_000;
2070
2635
  const timeoutMs = input.timeoutMs ?? 180_000;
2071
2636
  if (!Number.isSafeInteger(timeoutMs) || timeoutMs < 60_000 || timeoutMs > maximumTimeoutMs) {
2072
2637
  throw new ControlPlaneCliError('INVALID_ARGUMENT', `timeoutMs must be an integer within 60000..${maximumTimeoutMs}`);
@@ -2087,6 +2652,7 @@ export class PluginControlPlaneService extends Service {
2087
2652
  const stateRoot = join(this.config.statePath, 'source-worktrees');
2088
2653
  const isolated = await createIsolatedWorktree({ stateRoot, repository, baseCommit, environment,
2089
2654
  ...(sourceJob === undefined ? {} : { worktreeName: basename(sourceJob.intent.worktree) }) });
2655
+ let linuxBuildInvoked = false;
2090
2656
  try {
2091
2657
  const generated = revision !== undefined ? await prepareRevisedPluginWorkspace({ worktree: isolated.worktree,
2092
2658
  baseCommit, name, files: input.files, environment, signal, assertCurrent, revision, parentSource: parent.source, parentCertificate: parent.certificate })
@@ -2116,12 +2682,21 @@ export class PluginControlPlaneService extends Service {
2116
2682
  parentCertificate: parent.certificate, files: input.files } }), ...(capturePack ? { capturePack: true } : {}) };
2117
2683
  const checked = 'backend' in configured
2118
2684
  ? await (async () => {
2119
- if ((!creating && !revising) || sourceJob === undefined || this.nativeSourceRunner === undefined
2120
- || sourceJob.intent.nativeResource?.key !== sourceJob.id) {
2121
- throw new ControlPlaneCliError('SOURCE_BOUNDARY', 'native source preparation requires a durable Host-scaffolded creation job');
2685
+ const native = this.nativeSourceRunner;
2686
+ if (sourceJob === undefined || native === undefined || native.backend !== configured.backend
2687
+ || sourceJob.intent.nativeResource?.backend !== configured.backend || sourceJob.intent.nativeResource.key !== sourceJob.id) {
2688
+ throw new ControlPlaneCliError('SOURCE_BOUNDARY', 'native source preparation requires its bound durable source job');
2689
+ }
2690
+ if (configured.backend === 'linux-native' && native.backend === 'linux-native') {
2691
+ linuxBuildInvoked = true;
2692
+ return runLinuxPreparedChecks({ ...common, config: { ...configured, timeoutMs: Math.min(timeoutMs, configured.timeoutMs) },
2693
+ runner: native.runner, sourceJob: { id: sourceJob.id } });
2694
+ }
2695
+ if (configured.backend !== 'macos-seatbelt' || native.backend !== 'macos-seatbelt' || !creating && !revising) {
2696
+ throw new ControlPlaneCliError('SOURCE_BOUNDARY', 'macOS source preparation requires a Host-scaffolded creation job');
2122
2697
  }
2123
2698
  return runNativePreparedChecks({ ...common, config: { ...configured, timeoutMs: Math.min(timeoutMs, configured.timeoutMs) },
2124
- runner: this.nativeSourceRunner, sourceJob: { id: sourceJob.id } });
2699
+ runner: native.runner, sourceJob: { id: sourceJob.id } });
2125
2700
  })()
2126
2701
  : await runDockerPreparedChecks({ ...common, config: { ...configured, timeoutMs: Math.min(timeoutMs, configured.timeoutMs) },
2127
2702
  ...(sourceJob === undefined ? {} : { sourceJob: { id: sourceJob.id, containerName: sourceJob.intent.containerName } }) });
@@ -2145,7 +2720,15 @@ export class PluginControlPlaneService extends Service {
2145
2720
  }
2146
2721
  // The worktree survives success: the owner recomputes its digests on this
2147
2722
  // exact directory during `source verify-prepared`.
2148
- return this.taskGaps.withCurrent(input.gapId, gapOwner, () => this.store.createSourcePlan({ gapId: input.gapId, repository, worktree: isolated.worktree, baseCommit,
2723
+ const commitSource = (callback) => {
2724
+ if (creation?.growthRun?.protocol === 'assistant-growth/source-run/v2') {
2725
+ if (!gapOwner || !('receiptVersion' in gapOwner))
2726
+ throw new Error('work item creation owner unavailable');
2727
+ return this.withWorkItemGrowthFrame(creation.growthRun, input.gapId, gapOwner, false, callback);
2728
+ }
2729
+ return this.taskGaps.withCurrent(input.gapId, gapOwner, callback);
2730
+ };
2731
+ return commitSource(() => this.store.createSourcePlan({ gapId: input.gapId, repository, worktree: isolated.worktree, baseCommit,
2149
2732
  name, generatorDigest: generated?.generatorDigest ?? MODIFY_GENERATOR_DIGEST,
2150
2733
  scope: generated?.scope ?? [`plugins/${name}`], mode: creating ? 'prepared-create' : revising ? 'prepared-revise' : 'modify', ttlMs,
2151
2734
  ...(creation === undefined ? {} : { creation }), ...(revision === undefined ? {} : { revision }),
@@ -2156,6 +2739,19 @@ export class PluginControlPlaneService extends Service {
2156
2739
  }
2157
2740
  catch (error) {
2158
2741
  await isolated.remove();
2742
+ if (preflight && sourceJob && sourceJob.intent.nativeResource?.backend === 'linux-native'
2743
+ && sourceJob.intent.nativeResource.key === sourceJob.id && controlPlaneDigest(sourceJob.intent) === sourceJob.intentDigest) {
2744
+ // Preserve native authority checks across asynchronous worktree cleanup.
2745
+ // The runtime consumes the complete chain immediately before its original claim CAS.
2746
+ if (linuxBuildInvoked && isLinuxSourceBuildFailed(error)) {
2747
+ throw cleanedLinuxSourceBuildFailure(error, sourceJob, preflight.generation, () => !this.abort.signal.aborted && this.nativeSourceRunner?.backend === 'linux-native'
2748
+ && this.nativeSourceRunner.runner === preflight.runner
2749
+ ? consumeLinuxSourceBuildFailed(error, { runner: preflight.runner, key: sourceJob.id }) : undefined);
2750
+ }
2751
+ throw cleanedLinuxSourcePreflight(error, sourceJob, preflight.generation, () => !this.abort.signal.aborted && this.nativeSourceRunner?.backend === 'linux-native'
2752
+ && this.nativeSourceRunner.runner === preflight.runner
2753
+ && (!linuxBuildInvoked || consumeLinuxSourceNotStarted(error, { runner: preflight.runner, key: sourceJob.id })));
2754
+ }
2159
2755
  throw error;
2160
2756
  }
2161
2757
  }