@dsh-enhanced/plugin-control-plane 0.1.64 → 0.1.66

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 (121) hide show
  1. package/README.md +81 -5
  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 +9 -0
  10. package/lib/creation-capability-execution.d.ts.map +1 -0
  11. package/lib/creation-capability-execution.js +70 -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 +18 -0
  26. package/lib/creation-capability-runtime.d.ts.map +1 -1
  27. package/lib/creation-capability-runtime.js +76 -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 +12 -4
  48. package/lib/index.d.ts.map +1 -1
  49. package/lib/index.js +8 -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/scanner-owner-runtime.d.ts +34 -0
  70. package/lib/scanner-owner-runtime.d.ts.map +1 -0
  71. package/lib/scanner-owner-runtime.js +99 -0
  72. package/lib/scanner-owner-runtime.js.map +1 -0
  73. package/lib/service.d.ts +82 -5
  74. package/lib/service.d.ts.map +1 -1
  75. package/lib/service.js +450 -45
  76. package/lib/service.js.map +1 -1
  77. package/lib/source-approval-authority.d.ts.map +1 -1
  78. package/lib/source-approval-authority.js +25 -7
  79. package/lib/source-approval-authority.js.map +1 -1
  80. package/lib/source-build-diagnostics.d.ts +31 -0
  81. package/lib/source-build-diagnostics.d.ts.map +1 -0
  82. package/lib/source-build-diagnostics.js +110 -0
  83. package/lib/source-build-diagnostics.js.map +1 -0
  84. package/lib/source-build.d.ts +25 -2
  85. package/lib/source-build.d.ts.map +1 -1
  86. package/lib/source-build.js +145 -26
  87. package/lib/source-build.js.map +1 -1
  88. package/lib/source-creation.d.ts +6 -2
  89. package/lib/source-creation.d.ts.map +1 -1
  90. package/lib/source-creation.js +11 -2
  91. package/lib/source-creation.js.map +1 -1
  92. package/lib/source-job-types.d.ts +3 -1
  93. package/lib/source-job-types.d.ts.map +1 -1
  94. package/lib/source-jobs.d.ts +28 -1
  95. package/lib/source-jobs.d.ts.map +1 -1
  96. package/lib/source-jobs.js +117 -26
  97. package/lib/source-jobs.js.map +1 -1
  98. package/lib/sqlite.d.ts +1 -1
  99. package/lib/sqlite.d.ts.map +1 -1
  100. package/lib/sqlite.js +36 -1
  101. package/lib/sqlite.js.map +1 -1
  102. package/lib/store.d.ts +21 -1
  103. package/lib/store.d.ts.map +1 -1
  104. package/lib/store.js +470 -174
  105. package/lib/store.js.map +1 -1
  106. package/lib/systemd-host-authority.d.ts.map +1 -1
  107. package/lib/systemd-host-authority.js +4 -0
  108. package/lib/systemd-host-authority.js.map +1 -1
  109. package/lib/task-observation-runtime.d.ts +6 -2
  110. package/lib/task-observation-runtime.d.ts.map +1 -1
  111. package/lib/task-observation-runtime.js +69 -11
  112. package/lib/task-observation-runtime.js.map +1 -1
  113. package/lib/types.d.ts +47 -1
  114. package/lib/types.d.ts.map +1 -1
  115. package/lib/version.d.ts +1 -1
  116. package/lib/version.js +1 -1
  117. package/lib/work-item-creation-contract.d.ts +17 -0
  118. package/lib/work-item-creation-contract.d.ts.map +1 -0
  119. package/lib/work-item-creation-contract.js +61 -0
  120. package/lib/work-item-creation-contract.js.map +1 -0
  121. package/package.json +10 -10
package/lib/service.js CHANGED
@@ -1,16 +1,18 @@
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 { inspectCreationCapabilityAvailabilityAttribution, inspectCreationCapabilityOrdinaryUseReport, inspectCreationCapabilityOrdinaryUseTrend, inspectCreationCapabilityTaskAssociations } from './creation-capability-feedback.js';
5
6
  import { captureCreationCapabilitySource } from './creation-capability-source.js';
6
7
  import { lstat, readFile, realpath } from 'node:fs/promises';
7
8
  import { realpathSync } from 'node:fs';
8
9
  import { basename, dirname, isAbsolute, join, resolve } from 'node:path';
9
10
  import { createHash, createPublicKey, randomBytes } from 'node:crypto';
10
- import { isSourceOwnerContinuation, sourceGrowthRunDigest, validateCreationAcceptanceAuthorityRef, verifyPluginCreationVerificationCertificate, validateRevisionAcceptanceAuthorityRef, verifyPluginRevisionVerificationCertificate, validateRevisionRegressionAcceptanceAuthorityRef, verifyPluginRevisionRegressionCertificate, validateSourceGrowthRunBinding } from '@dsh-enhanced/assistant-growth-contract';
11
+ import { isSourceOwnerContinuation, anySourceGrowthRunDigest as sourceGrowthRunDigest, validateAnySourceGrowthRunBinding as validateSourceGrowthRunBinding, validateWorkItemGrowthAuthorityPins, validateCreationAcceptanceAuthorityRef, verifyPluginCreationVerificationCertificate, validateRevisionAcceptanceAuthorityRef, verifyPluginRevisionVerificationCertificate, validateRevisionRegressionAcceptanceAuthorityRef, verifyPluginRevisionRegressionCertificate } from '@dsh-enhanced/assistant-growth-contract';
11
12
  import { TaskObservationRuntime, validateTaskObservationConfig } from './task-observation-runtime.js';
12
13
  import { LiveQualificationRuntime, validateLiveQualificationConfig } from './live-qualification-runtime.js';
13
14
  import { rollbackPluginWatch } from './cli.js';
15
+ import { assertWorkItemRunReference, inertCreationData, WORK_ITEM_ENGINEERING_PROTOCOL } from './work-item-creation-contract.js';
14
16
  import { OwnerTaskFailureGaps } from './owner-task-gaps.js';
15
17
  import { Context, Service } from '@deepseek-ai/cordis';
16
18
  import Schema from '@deepseek-ai/schemastery';
@@ -27,7 +29,8 @@ import { Ed25519ApprovalAuthority } from './approval.js';
27
29
  import { ControlPlaneStore, MODIFY_GENERATOR_DIGEST, controlPlaneDigest } from './store.js';
28
30
  import { runDockerPreparedChecks, validateSourceBuildConfig } from './source-build.js';
29
31
  import { runNativePreparedChecks } from './native-source-build.js';
30
- import { NativeMacVerifierRunner } from '@dsh-enhanced/assistant-isolation';
32
+ import { LINUX_PREPARED_SOURCE_BUILD_SCRIPT, LINUX_SOURCE_PACK_PATH, runLinuxPreparedChecks } from './linux-source-build.js';
33
+ import { LinuxRepositoryRunner, NativeMacVerifierRunner, linuxRuntimeDigest, verifyLinuxRuntime } from '@dsh-enhanced/assistant-isolation';
31
34
  import { NATIVE_SOURCE_COMPILER } from './native-source-build-worker.js';
32
35
  import { awaitSourceSignal, inspectSourceContext, inspectSourceTargetsContext } from './source-context.js';
33
36
  import { inspectSourceRevisionContext, prepareRevisedPluginWorkspace, verifyRevisedPluginWorkspace } from './source-revision.js';
@@ -35,7 +38,7 @@ import { inspectSourceCreationContext, prepareCreatedPluginWorkspace, validateSo
35
38
  import { resolveSourceBaseline } from './source-baseline.js';
36
39
  import { inheritedEnvironment, loadTrustConfig, resolveTrustKey } from './trust.js';
37
40
  import { registerPluginControlTools } from './tools.js';
38
- import { SourceGrowthRunUnavailableError, SourceJobRuntime, validateSourceJobsConfig } from './source-jobs.js';
41
+ import { SourceGrowthRunUnavailableError, SourceJobRuntime, normalizeSourceJobsConfig } from './source-jobs.js';
39
42
  import { installRuntimeObserver, validateRuntimeObserverConfig } from './runtime-observer.js';
40
43
  import { installReplayEndpoint, validateReplayEndpointConfig } from './replay-endpoint.js';
41
44
  import { readPrivateRuntimeObserverKey } from './runtime-observer-protocol.js';
@@ -136,13 +139,21 @@ export function normalizeControlPlaneConfig(input) {
136
139
  validateSourceApprovalClientConfig(config.sourceApprovals);
137
140
  if (config.sourceBuild !== undefined)
138
141
  validateSourceBuildConfig(config.sourceBuild);
139
- if (config.sourceBuild !== undefined && 'backend' in config.sourceBuild) {
142
+ if (config.sourceBuild !== undefined && 'backend' in config.sourceBuild && config.sourceBuild.backend === 'macos-seatbelt') {
140
143
  if (!config.sourceJobs?.allowedModes || config.sourceJobs.allowedModes.some(mode => mode === 'modify')
141
144
  || config.sourceReleases !== undefined || config.sourceReleaseExecution !== undefined
142
145
  || config.sourceAdoptions !== undefined || config.sourceApprovals !== undefined) {
143
146
  throw new Error('plugin-control-plane: native source build only supports Host-scaffolded create/revise-created');
144
147
  }
145
148
  }
149
+ if (config.sourceBuild !== undefined && 'backend' in config.sourceBuild && config.sourceBuild.backend === 'linux-native') {
150
+ if (!config.sourceJobs)
151
+ throw new Error('plugin-control-plane: Linux repository preparation requires durable sourceJobs');
152
+ if ((config.creationVerifications || config.creationCapabilities || config.revisionVerifications || config.revisionRegressions)
153
+ && (!config.sourceBuild.userNamespace || !config.sourceBuild.systemSnapshot)) {
154
+ throw new Error('plugin-control-plane: Linux independent verification requires the dual-UID repository boundary');
155
+ }
156
+ }
146
157
  if (config.sourceReleases !== undefined) {
147
158
  validateSourceReleaseClientConfig(config.sourceReleases);
148
159
  if (!config.sourceApprovals || config.sourceBuild?.versioning !== 'patch')
@@ -168,7 +179,7 @@ export function normalizeControlPlaneConfig(input) {
168
179
  }
169
180
  }
170
181
  if (config.sourceJobs !== undefined) {
171
- validateSourceJobsConfig(config.sourceJobs, config.sourceBuild);
182
+ config.sourceJobs = normalizeSourceJobsConfig(config.sourceJobs, config.sourceBuild);
172
183
  if (realpathSync(config.sourceJobs.repository) !== config.sourceJobs.repository)
173
184
  throw new Error('plugin-control-plane: sourceJobs.repository must be canonical');
174
185
  }
@@ -227,9 +238,19 @@ export function normalizeControlPlaneConfig(input) {
227
238
  }
228
239
  if (config.creationCapabilities !== undefined) {
229
240
  validateCreationCapabilityConfig(config.creationCapabilities);
241
+ if (config.creationCapabilities.workItemFailures === false) {
242
+ config.creationCapabilities = { ...config.creationCapabilities };
243
+ delete config.creationCapabilities.workItemFailures;
244
+ }
230
245
  const capability = config.creationCapabilities;
231
246
  const verification = config.creationVerifications;
232
247
  const jobs = config.sourceJobs;
248
+ const build = config.sourceBuild;
249
+ const buildBackend = build && 'backend' in build ? build.backend : 'docker';
250
+ const adoptionBackend = 'backend' in capability.runner ? capability.runner.backend : 'docker';
251
+ if (!build || buildBackend !== adoptionBackend) {
252
+ throw new Error('plugin-control-plane: creation build, verification and adoption require matching execution backends');
253
+ }
233
254
  if (!verification || !jobs?.creation || capability.namePrefix !== jobs.creation.namePrefix
234
255
  || capability.owner.authorityId !== jobs.ownerRouteId || capability.owner.principalId !== jobs.principalId
235
256
  || capability.owner.workspace !== jobs.workspace || capability.owner.agentPreset !== jobs.preset
@@ -269,6 +290,7 @@ export class PluginControlPlaneService extends Service {
269
290
  creationCapabilityReconcileRequested = false;
270
291
  growthRunProducer;
271
292
  growthProviderDraining = false;
293
+ workItemCreationFrame;
272
294
  constructor(ctx, input) {
273
295
  super(ctx, 'pluginControlPlane');
274
296
  this.config = normalizeControlPlaneConfig(input);
@@ -437,9 +459,14 @@ export class PluginControlPlaneService extends Service {
437
459
  },
438
460
  delivery: {
439
461
  validateOwnerRoute: input => delivery().validateOwnerRoute(input),
462
+ inspectOwnerRouteState: input => delivery().inspectOwnerRouteState(input),
440
463
  inspectOwnerForegroundLearningTask: input => delivery().inspectOwnerForegroundLearningTask(input),
441
464
  },
442
465
  automations: {
466
+ inspectSystemOwnedRetirement: input => automations().inspectSystemOwnedRetirement(input),
467
+ onSchedulerWake: listener => automations().onSchedulerWake(listener),
468
+ retireSystemOwned: input => automations().retireSystemOwned(input),
469
+ inspectSystemOwned: input => automations().inspectSystemOwned(input),
443
470
  registerHostExecutor: input => automations().registerHostExecutor(input),
444
471
  reconcileSystem: input => automations().reconcileSystem(input),
445
472
  inspectSystemOwnedActivation: input => automations().inspectSystemOwnedActivation(input),
@@ -495,9 +522,14 @@ export class PluginControlPlaneService extends Service {
495
522
  },
496
523
  delivery: {
497
524
  validateOwnerRoute: input => delivery().validateOwnerRoute(input),
525
+ inspectOwnerRouteState: input => delivery().inspectOwnerRouteState(input),
498
526
  inspectOwnerForegroundLearningTask: input => delivery().inspectOwnerForegroundLearningTask(input),
499
527
  },
500
528
  automations: {
529
+ inspectSystemOwnedRetirement: input => automations().inspectSystemOwnedRetirement(input),
530
+ onSchedulerWake: listener => automations().onSchedulerWake(listener),
531
+ retireSystemOwned: input => automations().retireSystemOwned(input),
532
+ inspectSystemOwned: input => automations().inspectSystemOwned(input),
501
533
  registerHostExecutor: input => automations().registerHostExecutor(input),
502
534
  reconcileSystem: input => automations().reconcileSystem(input),
503
535
  inspectSystemOwnedActivation: input => automations().inspectSystemOwnedActivation(input),
@@ -558,10 +590,14 @@ export class PluginControlPlaneService extends Service {
558
590
  if (Object.keys(owner).some(key => prepared.reference.owner[key] !== owner[key])) {
559
591
  throw new Error('created capability owner differs from the finite grant');
560
592
  }
561
- return { certificate, artifact: prepared.artifact, owner };
593
+ return { certificate, artifact: prepared.artifact, owner, ...(prepared.protocol === 'dsh-prepared-creation/v2' ? { sourceRun: prepared.plan.creation.growthRun } : {}) };
562
594
  },
563
595
  recheck: async (planId, signal) => {
564
596
  await this.inspectPreparedCreationReviewContext(planId, signal);
597
+ if (this.config.sourceBuild && 'backend' in this.config.sourceBuild
598
+ && this.config.sourceBuild.backend === 'linux-native') {
599
+ await this.inspectPreparedCreationLinuxBuild(planId, signal);
600
+ }
565
601
  if (!this.inspectVerifiedCreation(planId))
566
602
  throw new Error('created capability independent certificate changed');
567
603
  },
@@ -584,7 +620,7 @@ export class PluginControlPlaneService extends Service {
584
620
  return source;
585
621
  },
586
622
  inspectRetained: record => this.withRetainedCreatedCapabilityCurrent(record, () => ({
587
- certificate: record.certificate, artifact: record.artifact, owner: config.owner
623
+ 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
624
  })),
589
625
  recheckRetained: async (record, signal) => {
590
626
  signal.throwIfAborted();
@@ -613,9 +649,6 @@ export class PluginControlPlaneService extends Service {
613
649
  },
614
650
  } });
615
651
  this.creationCapabilityRuntimes.add(capabilities);
616
- await capabilities.start();
617
- this.abort.signal.throwIfAborted();
618
- this.creationCapabilityRuntime = capabilities;
619
652
  unsubscribeTaskChanges = evaluation.onTrustedTaskChange(() => this.reconcileCreatedCapabilities());
620
653
  }
621
654
  catch (error) {
@@ -632,11 +665,24 @@ export class PluginControlPlaneService extends Service {
632
665
  const sourceBuild = this.config.sourceBuild;
633
666
  let nativeRunner;
634
667
  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;
668
+ nativeRunner = !('backend' in sourceBuild) ? undefined : sourceBuild.backend === 'macos-seatbelt'
669
+ ? { backend: 'macos-seatbelt', runner: new NativeMacVerifierRunner({ backend: 'macos-seatbelt',
670
+ stateRoot: sourceBuild.stateRoot, authorityDigest: sourceBuild.authorityDigest,
671
+ runtime: sourceBuild.runtime, source: NATIVE_SOURCE_COMPILER, expiresAt: sourceBuild.expiresAt,
672
+ maxRuns: sourceBuild.maxRuns, maxTotalDurationMs: sourceBuild.maxTotalDurationMs,
673
+ maxDurationMs: sourceBuild.timeoutMs, maxOutputBytes: sourceBuild.outputBytes }) }
674
+ : { backend: 'linux-native', runner: new LinuxRepositoryRunner({ backend: 'linux-native',
675
+ stateRoot: sourceBuild.stateRoot, authorityDigest: sourceBuild.authorityDigest,
676
+ runtime: sourceBuild.runtime, cgroupRoots: sourceBuild.cgroupRoots, expiresAt: sourceBuild.expiresAt,
677
+ maxRuns: sourceBuild.maxRuns, maxTotalDurationMs: sourceBuild.maxTotalDurationMs,
678
+ ...(sourceBuild.stateSeal === undefined ? {} : { stateSeal: sourceBuild.stateSeal }),
679
+ ...(sourceBuild.userNamespace === undefined ? {} : { userNamespace: sourceBuild.userNamespace,
680
+ systemSnapshot: sourceBuild.systemSnapshot }),
681
+ limits: { maxDurationMs: sourceBuild.timeoutMs, maxInputBytes: 64 * 1024 ** 2,
682
+ maxOutputBytes: sourceBuild.outputBytes, maxArtifactBytes: 32 * 1024 ** 2, maxFiles: 128,
683
+ memoryMiB: sourceBuild.memoryMiB, cpus: sourceBuild.cpus, pidsLimit: sourceBuild.pidsLimit,
684
+ workspaceMiB: sourceBuild.workspaceMiB, workspaceInodes: sourceBuild.workspaceInodes,
685
+ tmpMiB: sourceBuild.temporaryMiB, tmpInodes: sourceBuild.temporaryInodes, inputMiB: 65, inputInodes: 16 } }) };
640
686
  }
641
687
  catch (error) {
642
688
  unsubscribeTaskChanges?.();
@@ -648,7 +694,12 @@ export class PluginControlPlaneService extends Service {
648
694
  }
649
695
  this.nativeSourceRunner = nativeRunner;
650
696
  let runtime;
697
+ const preparationGeneration = {};
651
698
  try {
699
+ // Restored fresh adoptions read their original Linux build receipt.
700
+ // Their source runner must exist before capability reconciliation.
701
+ await capabilities?.start();
702
+ this.abort.signal.throwIfAborted();
652
703
  runtime = new SourceJobRuntime({ config: this.config.sourceJobs, build: sourceBuild, statePath: this.config.statePath, store: this.store,
653
704
  ports: {
654
705
  automations: {
@@ -661,6 +712,7 @@ export class PluginControlPlaneService extends Service {
661
712
  },
662
713
  delivery: { validateOwnerRoute: request => current('delivery').validateOwnerRoute(request) },
663
714
  }, withGapSourceFence: (gapId, owner, callback) => this.taskGaps.withCurrent(gapId, owner, callback),
715
+ withWorkItemGrowthRunFence: (run, gapId, owner, generation, callback) => this.withWorkItemGrowthFrame(run, gapId, owner, generation, callback),
664
716
  assertGrowthRun: (run, gapId, owner, generation) => this.assertSourceGrowthRun(run, gapId, owner, generation),
665
717
  ...(this.config.revisionVerifications ? { inspectRevisionParent: this.inspectRevisionParent,
666
718
  verifyPreparedRevision: (job, signal) => this.verifyPreparedRevision(job, signal, () => jobsCtx.get('assistantVerifier')) } : {}),
@@ -694,14 +746,16 @@ export class PluginControlPlaneService extends Service {
694
746
  throw new Error('source job has no released plan');
695
747
  await this.adoptOwnerSourceRelease({ sourcePlanId: job.planId, signal, expectedTrustDigest: job.intent.trustDigest, assertCurrent });
696
748
  } } : {}),
697
- trust: () => this.boundTrust(), prepare: (job, signal, assertCurrent) => this.prepareSourceJob(job, signal, assertCurrent),
698
- ...(nativeRunner ? { inspectNativeQuiescence: (key) => nativeRunner.inspectQuiescence(key) } : {}),
749
+ trust: () => this.boundTrust(), prepare: (job, signal, assertCurrent) => this.prepareSourceJob(job, signal, assertCurrent, nativeRunner?.backend === 'linux-native' ? { runner: nativeRunner.runner, generation: preparationGeneration } : undefined),
750
+ ...(nativeRunner?.backend === 'linux-native' ? { isPreparationNotStarted: (error, job) => consumeCleanedLinuxSourcePreflight(error, job, preparationGeneration),
751
+ deterministicBuildFailure: (error, job) => consumeCleanedLinuxSourceBuildFailure(error, job, preparationGeneration) } : {}),
752
+ ...(nativeRunner ? { inspectNativeQuiescence: (key) => nativeRunner.runner.inspectQuiescence(key) } : {}),
699
753
  });
700
754
  }
701
755
  catch (error) {
702
756
  if (this.nativeSourceRunner === nativeRunner)
703
757
  this.nativeSourceRunner = undefined;
704
- await nativeRunner?.close();
758
+ await nativeRunner?.runner.close();
705
759
  unsubscribeTaskChanges?.();
706
760
  if (capabilities) {
707
761
  await capabilities.close();
@@ -710,6 +764,7 @@ export class PluginControlPlaneService extends Service {
710
764
  throw error;
711
765
  }
712
766
  this.sourceRuntimes.add(runtime);
767
+ this.creationCapabilityRuntime = capabilities;
713
768
  const close = async () => {
714
769
  unsubscribeTaskChanges?.();
715
770
  if (this.sourceRuntime === runtime)
@@ -726,7 +781,7 @@ export class PluginControlPlaneService extends Service {
726
781
  this.sourceRuntimes.delete(runtime);
727
782
  if (this.creationCapabilityRuntime === capabilities)
728
783
  this.creationCapabilityRuntime = undefined;
729
- const results = await Promise.allSettled([nativeRunner?.close(), capabilities?.close()]);
784
+ const results = await Promise.allSettled([nativeRunner?.runner.close(), capabilities?.close()]);
730
785
  if (capabilities)
731
786
  this.creationCapabilityRuntimes.delete(capabilities);
732
787
  const failures = results.filter((result) => result.status === 'rejected');
@@ -738,6 +793,10 @@ export class PluginControlPlaneService extends Service {
738
793
  runtime.start();
739
794
  this.abort.signal.throwIfAborted();
740
795
  this.sourceRuntime = runtime;
796
+ // Producer/binding readiness can arrive while capabilities.start() is
797
+ // awaiting its source checks, before these runtime fields are public.
798
+ // Reconcile once after publication so that notification is not lost.
799
+ this.reconcileCreatedCapabilities();
741
800
  return close;
742
801
  }
743
802
  catch (error) {
@@ -782,6 +841,10 @@ export class PluginControlPlaneService extends Service {
782
841
  return discover(loaded.catalog, capability);
783
842
  }
784
843
  /** Host only: reread the exact source; no caller text or ratings are admitted. */
844
+ recordOwnerWorkItemFailureGap = (source) => {
845
+ this.abort.signal.throwIfAborted();
846
+ return this.taskGaps.recordWorkItem(source);
847
+ };
785
848
  recordOwnerTaskFailureGap = (source) => {
786
849
  this.abort.signal.throwIfAborted();
787
850
  return this.taskGaps.record(source);
@@ -1021,7 +1084,8 @@ export class PluginControlPlaneService extends Service {
1021
1084
  /** Host-only durable Usage reader; the caller's binding is always an expectation. */
1022
1085
  registerSourceGrowthRunProducer = (producer) => {
1023
1086
  this.abort.signal.throwIfAborted();
1024
- if (producer?.protocol !== 'assistant-growth-source-run-producer/v1' || typeof producer.inspect !== 'function'
1087
+ if (!['assistant-growth-source-run-producer/v1', 'assistant-growth-source-run-producer/v2'].includes(producer?.protocol) || typeof producer.inspect !== 'function'
1088
+ || producer.protocol === 'assistant-growth-source-run-producer/v2' && typeof producer.withWorkItemRunFence !== 'function'
1025
1089
  || this.growthRunProducer !== undefined || this.growthProviderDraining)
1026
1090
  throw new Error('plugin-control-plane: source growth run producer is invalid or already registered');
1027
1091
  this.growthRunProducer = producer;
@@ -1142,29 +1206,109 @@ export class PluginControlPlaneService extends Service {
1142
1206
  };
1143
1207
  /** Host-only current association of exact adopted call versions with authenticated task heads. */
1144
1208
  inspectCreatedCapabilityTaskAssociations = (planId) => {
1209
+ const input = this.createdCapabilityFeedbackInput(planId);
1210
+ if (!input)
1211
+ return [];
1212
+ try {
1213
+ return inspectCreationCapabilityTaskAssociations(input());
1214
+ }
1215
+ catch {
1216
+ return [];
1217
+ }
1218
+ };
1219
+ /** Host-only descriptive ordinary-use counts over the same fenced associations; never causal or adoption evidence. */
1220
+ inspectCreatedCapabilityOrdinaryUseReport = (planId) => {
1221
+ const input = this.createdCapabilityFeedbackInput(planId);
1222
+ if (!input)
1223
+ return undefined;
1224
+ try {
1225
+ return inspectCreationCapabilityOrdinaryUseReport(input());
1226
+ }
1227
+ catch {
1228
+ return undefined;
1229
+ }
1230
+ };
1231
+ /** Host-only observational before/after attribution of one adoption over the owner's certified tasks; never causal. */
1232
+ inspectCreatedCapabilityAvailabilityAttribution = (planId, options = {}) => {
1233
+ const input = this.createdCapabilityFeedbackInput(planId);
1234
+ if (!input)
1235
+ return undefined;
1236
+ try {
1237
+ return inspectCreationCapabilityAvailabilityAttribution({ ...input(),
1238
+ otherAdoptedAt: this.creationCapabilityRuntime?.inspectOtherAdoptionTimes(planId) ?? [],
1239
+ ...(options.windowMs === undefined ? {} : { windowMs: options.windowMs }) });
1240
+ }
1241
+ catch {
1242
+ return undefined;
1243
+ }
1244
+ };
1245
+ /**
1246
+ * Host-only longitudinal ordinary-use series of one adoption in fixed windows with a same-scope session-cohort
1247
+ * control and a model-stratified difference-in-differences estimate; observational, never causal. Pure read.
1248
+ */
1249
+ inspectCreatedCapabilityOrdinaryUseTrend = (planId, options = {}) => {
1250
+ const input = this.createdCapabilityFeedbackInput(planId);
1251
+ if (!input)
1252
+ return undefined;
1253
+ try {
1254
+ return inspectCreationCapabilityOrdinaryUseTrend({ ...input(),
1255
+ otherAdoptedAt: this.creationCapabilityRuntime?.inspectOtherAdoptionTimes(planId) ?? [],
1256
+ ...(options.windowMs === undefined ? {} : { windowMs: options.windowMs }),
1257
+ ...(options.preWindows === undefined ? {} : { preWindows: options.preWindows }),
1258
+ ...(options.maxPostWindows === undefined ? {} : { maxPostWindows: options.maxPostWindows }) });
1259
+ }
1260
+ catch {
1261
+ return undefined;
1262
+ }
1263
+ };
1264
+ /**
1265
+ * Host-only, owner-invoked: seal the current ordinary-use trend of one adoption into the append-only,
1266
+ * content-addressed snapshot ledger. Re-sealing an unchanged trend returns the existing row (`created: false`);
1267
+ * rows are never rewritten. Still observational (`causal: false`); never adoption or revocation evidence.
1268
+ */
1269
+ sealCreatedCapabilityOrdinaryUseTrendSnapshot = (planId, options = {}) => {
1270
+ const trend = this.inspectCreatedCapabilityOrdinaryUseTrend(planId, options);
1271
+ if (!trend)
1272
+ return undefined;
1273
+ try {
1274
+ return this.store.sealCreatedCapabilityTrendSnapshot(trend);
1275
+ }
1276
+ catch {
1277
+ return undefined;
1278
+ }
1279
+ };
1280
+ /** Host-only bounded newest-first read of sealed trend snapshots; undefined on invalid query or any tampered row. */
1281
+ inspectCreatedCapabilityOrdinaryUseTrendSnapshots = (query = {}) => {
1282
+ try {
1283
+ this.abort.signal.throwIfAborted();
1284
+ return this.store.listCreatedCapabilityTrendSnapshots(query);
1285
+ }
1286
+ catch {
1287
+ return undefined;
1288
+ }
1289
+ };
1290
+ /** The historical read is deferred so callers keep it inside their own failure boundary. */
1291
+ createdCapabilityFeedbackInput(planId) {
1145
1292
  this.abort.signal.throwIfAborted();
1146
1293
  const snapshot = this.creationCapabilityRuntime?.inspectAssociationEvidence(planId);
1147
1294
  if (!snapshot || !this.config.creationCapabilities)
1148
- return [];
1295
+ return undefined;
1149
1296
  const owner = this.config.creationCapabilities.owner;
1150
1297
  const evaluation = this.ctx.get('assistantEvaluation', false);
1151
1298
  const delivery = this.ctx.get('assistantDelivery', false);
1152
1299
  if (!evaluation || !delivery)
1153
- return [];
1154
- try {
1300
+ return undefined;
1301
+ return () => {
1155
1302
  const historical = this.store.getRetainedPreparedCreation(planId);
1156
- return inspectCreationCapabilityTaskAssociations({ ...snapshot, owner, evaluation, delivery,
1303
+ return { ...snapshot, owner, evaluation, delivery,
1157
1304
  triggerInboxId: historical.reference.projection.subjectRef,
1158
1305
  readSourceCurrent: () => {
1159
1306
  this.assertCreatedCapabilityAssociationSource(snapshot.record);
1160
1307
  const current = this.taskGaps.inspectCurrent(historical.plan.gapId, historical.reference.owner);
1161
1308
  return { projection: current.canonical.projection, scopeWatermark: current.canonical.scopeWatermark };
1162
- } });
1163
- }
1164
- catch {
1165
- return [];
1166
- }
1167
- };
1309
+ } };
1310
+ };
1311
+ }
1168
1312
  /** Exact historical certificate and source check without nesting an Evaluation writer fence. */
1169
1313
  assertCreatedCapabilityAssociationSource(record) {
1170
1314
  this.abort.signal.throwIfAborted();
@@ -1252,11 +1396,113 @@ export class PluginControlPlaneService extends Service {
1252
1396
  !== ownerGrant[key])) {
1253
1397
  throw new Error('created capability retained source binding changed');
1254
1398
  }
1399
+ if (growth.protocol === 'assistant-growth/source-run/v2') {
1400
+ if (reference.schemaVersion !== 2 || growth.ownerDigest !== controlPlaneDigest(reference.owner))
1401
+ throw new Error('retained work item reference changed');
1402
+ assertWorkItemRunReference(growth, reference);
1403
+ this.assertWorkItemPinsLocal(growth, 'retained');
1404
+ return callback(growth);
1405
+ }
1255
1406
  return callback();
1256
1407
  });
1257
1408
  }
1409
+ inspectWorkItemPreparedCreationProtocol = () => {
1410
+ return !this.abort.signal.aborted && this.config.sourceJobs?.creation?.workItemFailures === true && this.sourceRuntime?.available() ? 'dsh-prepared-creation/v2' : undefined;
1411
+ };
1412
+ inspectWorkItemCreationReadiness = (input) => {
1413
+ try {
1414
+ this.abort.signal.throwIfAborted();
1415
+ const owner = inertCreationData(input).owner, config = this.config, source = this.sourceRuntime?.inspectWorkItemReadiness(owner);
1416
+ if (!source || !config.sourceJobs || !config.sourceBuild || config.sourceJobs.creation?.workItemFailures !== true || config.creationCapabilities?.workItemFailures !== true || !config.creationVerifications)
1417
+ return undefined;
1418
+ const delivery = this.ctx.get('assistantDelivery', false);
1419
+ const verifier = this.ctx.get('assistantVerifier', false);
1420
+ if (typeof delivery?.withOwnerWorkItemEngineeringContextFence !== 'function' || typeof verifier?.inspectWorkItemCreationReviewReadiness !== 'function' || typeof this.creationCapabilityRuntime?.inspectWorkItemAdoptionReadiness !== 'function')
1421
+ return undefined;
1422
+ const actual = delivery.validateOwnerRoute({ authorityId: owner.authorityId, principalId: owner.principalId, workspace: owner.workspace, agentPreset: owner.agentPreset });
1423
+ if (!isSourceOwnerContinuation(actual, owner) || Object.entries(config.creationCapabilities.owner).some(([key, value]) => actual[key] !== value))
1424
+ return undefined;
1425
+ const review = verifier.inspectWorkItemCreationReviewReadiness(), adoption = this.creationCapabilityRuntime.inspectWorkItemAdoptionReadiness();
1426
+ if (!review || !adoption || review.protocol !== 'assistant-growth/work-item-review-readiness/v1' || adoption.protocol !== 'assistant-growth/work-item-adoption-readiness/v1'
1427
+ || 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'
1428
+ || review.workItemFailures !== true || adoption.workItemFailures !== true || controlPlaneDigest(review.authority) !== controlPlaneDigest(config.creationVerifications.authority)
1429
+ || !createPublicKey(review.publicKey).export({ type: 'spki', format: 'der' }).equals(createPublicKey(config.creationVerifications.publicKey).export({ type: 'spki', format: 'der' }))
1430
+ || adoption.authority.authorityDigest !== creationCapabilityAuthorityDigest({ path: join(config.statePath, 'creation-adoptions.sqlite'), config: config.creationCapabilities })
1431
+ || adoption.authority.authorityId !== config.creationCapabilities.authorityId || adoption.authority.expiresAt !== config.creationCapabilities.expiresAt)
1432
+ return undefined;
1433
+ const pins = { protocol: 'assistant-growth/work-item-authorities/v1', sourceJobs: { authorityId: source.authority.id, authorityDigest: source.authority.digest, expiresAt: source.authority.expiresAt },
1434
+ creationGrant: { id: source.creation.id, digest: controlPlaneDigest(source.creation), expiresAt: source.creation.expiresAt }, creationAcceptance: structuredClone(review.authority), adoption: structuredClone(adoption.authority) };
1435
+ validateWorkItemGrowthAuthorityPins(pins);
1436
+ 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))
1437
+ return undefined;
1438
+ const remaining = { ...source.remaining, verifications: review.remainingVerifications, adoptions: adoption.remainingAdoptions };
1439
+ if (Object.values(remaining).some(count => !Number.isSafeInteger(count) || count < 0))
1440
+ return undefined;
1441
+ 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 };
1442
+ }
1443
+ catch {
1444
+ return undefined;
1445
+ }
1446
+ };
1447
+ assertWorkItemPinsLocal(run, phase = 'current') {
1448
+ const jobs = this.config.sourceJobs, verification = this.config.creationVerifications, adoption = this.config.creationCapabilities;
1449
+ if (!jobs || !this.config.sourceBuild || jobs.creation?.workItemFailures !== true || !verification || adoption?.workItemFailures !== true
1450
+ || run.authorities.sourceJobs.authorityId !== jobs.authorityId || run.authorities.sourceJobs.authorityDigest !== controlPlaneDigest({ config: jobs, build: this.config.sourceBuild }) || run.authorities.sourceJobs.expiresAt !== jobs.expiresAt
1451
+ || run.authorities.creationGrant.id !== jobs.creation.id || run.authorities.creationGrant.digest !== controlPlaneDigest(jobs.creation) || run.authorities.creationGrant.expiresAt !== jobs.creation.expiresAt
1452
+ || controlPlaneDigest(run.creationAcceptance) !== controlPlaneDigest(verification.authority) || controlPlaneDigest(run.authorities.creationAcceptance) !== controlPlaneDigest(verification.authority)
1453
+ || 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
1454
+ || (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))
1455
+ throw new Error('work item creation authority pins changed or expired');
1456
+ }
1457
+ assertWorkItemRunLocal(run, gapId, owner, generation) {
1458
+ const reference = this.store.getOwnerTaskFailureReference(gapId);
1459
+ if (!reference || reference.schemaVersion !== 2 || run.ownerDigest !== controlPlaneDigest(reference.owner) || !isSourceOwnerContinuation(owner, reference.owner))
1460
+ throw new Error('work item creation owner reference changed');
1461
+ assertWorkItemRunReference(run, reference);
1462
+ this.assertWorkItemPinsLocal(run);
1463
+ if (Date.now() >= run.expiresAt || generation && Date.now() >= run.generationDeadlineAt)
1464
+ throw new Error('work item source run expired');
1465
+ if (run.modelOrigin === 'inherited-owner-task' && this.workItemCreationFrame && controlPlaneDigest(this.workItemCreationFrame.source.task.proofIdentity.supplier) !== controlPlaneDigest(run.model))
1466
+ throw new Error('work item source supplier changed');
1467
+ }
1468
+ withWorkItemGrowthFrame(run, gapId, owner, generation, callback) {
1469
+ this.abort.signal.throwIfAborted();
1470
+ if (this.workItemCreationFrame) {
1471
+ if (this.workItemCreationFrame.gapId !== gapId || sourceGrowthRunDigest(this.workItemCreationFrame.run) !== sourceGrowthRunDigest(run))
1472
+ throw new Error('work item growth frame reentry');
1473
+ this.assertWorkItemRunLocal(run, gapId, owner, generation);
1474
+ return callback();
1475
+ }
1476
+ const producer = this.growthRunProducer;
1477
+ if (!producer || producer.protocol !== 'assistant-growth-source-run-producer/v2')
1478
+ throw new SourceGrowthRunUnavailableError('work item growth producer unavailable');
1479
+ const readiness = this.inspectWorkItemCreationReadiness({ owner });
1480
+ if (!readiness)
1481
+ throw new SourceGrowthRunUnavailableError('work item growth peers unavailable');
1482
+ if (controlPlaneDigest(readiness.pins) !== controlPlaneDigest(run.authorities))
1483
+ throw new Error('work item growth authority pins changed');
1484
+ const source = this.taskGaps.engineeringSnapshot(gapId, owner);
1485
+ return producer.withWorkItemRunFence({ runId: run.runId, intentDigest: run.intentDigest, expectedGrowthRunDigest: sourceGrowthRunDigest(run) }, actual => {
1486
+ if (this.growthRunProducer !== producer || sourceGrowthRunDigest(actual) !== sourceGrowthRunDigest(run) || this.workItemCreationFrame)
1487
+ throw new Error('work item producer changed its true binding');
1488
+ this.workItemCreationFrame = { gapId, run: actual, source };
1489
+ try {
1490
+ return this.store.withOwnerTaskFailureGapAdmission(gapId, () => { this.assertWorkItemRunLocal(actual, gapId, owner, generation); return callback(); });
1491
+ }
1492
+ finally {
1493
+ this.workItemCreationFrame = undefined;
1494
+ }
1495
+ });
1496
+ }
1258
1497
  assertSourceGrowthRun(expected, gapId, owner, generation) {
1259
1498
  validateSourceGrowthRunBinding(expected);
1499
+ if (expected.protocol === 'assistant-growth/source-run/v2') {
1500
+ if (this.workItemCreationFrame) {
1501
+ this.assertWorkItemRunLocal(expected, gapId, owner, generation);
1502
+ return;
1503
+ }
1504
+ return this.withWorkItemGrowthFrame(expected, gapId, owner, generation, () => { });
1505
+ }
1260
1506
  const producer = this.growthRunProducer;
1261
1507
  if (!producer)
1262
1508
  throw new SourceGrowthRunUnavailableError('source growth run producer unavailable');
@@ -1264,6 +1510,8 @@ export class PluginControlPlaneService extends Service {
1264
1510
  if (!actual)
1265
1511
  throw new Error('source growth run is absent or no longer current');
1266
1512
  validateSourceGrowthRunBinding(actual);
1513
+ if (actual.protocol !== 'assistant-growth/source-run/v1')
1514
+ throw new Error('foreground source cannot use a work item run');
1267
1515
  if (sourceGrowthRunDigest(actual) !== sourceGrowthRunDigest(expected)
1268
1516
  || expected.ownerDigest !== controlPlaneDigest(owner))
1269
1517
  throw new Error('source growth run immutable binding changed');
@@ -1282,7 +1530,7 @@ export class PluginControlPlaneService extends Service {
1282
1530
  throw new Error('source growth run creation acceptance policy changed or was not pinned before authoring');
1283
1531
  }
1284
1532
  const reference = this.store.getOwnerTaskFailureReference(gapId);
1285
- if (!reference || expected.source.outcomeId !== reference.outcomeId
1533
+ if (!reference || reference.schemaVersion !== 1 || expected.source.outcomeId !== reference.outcomeId
1286
1534
  || expected.source.sourceDigest !== reference.sourceDigest
1287
1535
  || controlPlaneDigest(expected.source.projection) !== controlPlaneDigest(reference.projection)
1288
1536
  || expected.ownerDigest !== controlPlaneDigest(reference.owner))
@@ -1306,6 +1554,12 @@ export class PluginControlPlaneService extends Service {
1306
1554
  || Date.now() >= plan.expiresAt || !plan.creation || Date.now() >= plan.creation.grant.expiresAt) {
1307
1555
  throw new Error('prepared creation package is unavailable for independent verification');
1308
1556
  }
1557
+ if (reference.schemaVersion === 2) {
1558
+ const run = plan.creation.growthRun;
1559
+ if (run?.protocol !== 'assistant-growth/source-run/v2')
1560
+ throw new Error('work item prepared creation run unavailable');
1561
+ 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) }));
1562
+ }
1309
1563
  return this.taskGaps.withCurrent(plan.gapId, reference.owner, () => {
1310
1564
  this.abort.signal.throwIfAborted();
1311
1565
  const job = this.store.getPreparedSourceJob(plan.id);
@@ -1339,6 +1593,17 @@ export class PluginControlPlaneService extends Service {
1339
1593
  const reference = this.store.getOwnerTaskFailureReference(plan.gapId);
1340
1594
  if (!reference)
1341
1595
  throw new Error('creation verification source unavailable');
1596
+ if (reference.schemaVersion === 2) {
1597
+ const run = plan.creation?.growthRun;
1598
+ if (run?.protocol !== 'assistant-growth/source-run/v2')
1599
+ throw new Error('work item verification run unavailable');
1600
+ return this.withWorkItemGrowthFrame(run, plan.gapId, reference.owner, false, () => {
1601
+ const current = this.store.getSourcePlan(input.planId), job = this.store.getPreparedSourceJob(input.planId);
1602
+ 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)
1603
+ throw new Error('work item prepared verification exact binding changed');
1604
+ return callback(run);
1605
+ });
1606
+ }
1342
1607
  return this.taskGaps.withCurrent(plan.gapId, reference.owner, () => {
1343
1608
  this.abort.signal.throwIfAborted();
1344
1609
  const current = this.store.getSourcePlan(input.planId);
@@ -1382,6 +1647,105 @@ export class PluginControlPlaneService extends Service {
1382
1647
  current();
1383
1648
  return review;
1384
1649
  };
1650
+ /** Host-only read of the original successful build; never admits or replays a job. */
1651
+ inspectPreparedCreationLinuxBuild = async (planId, signal) => {
1652
+ const combined = AbortSignal.any([this.abort.signal, signal, AbortSignal.timeout(120_000)]);
1653
+ combined.throwIfAborted();
1654
+ const prepared = this.inspectPreparedCreation(planId), build = this.config.sourceBuild;
1655
+ const native = this.nativeSourceRunner, evidence = prepared.plan.preparedEvidence;
1656
+ if (!build || !('backend' in build) || build.backend !== 'linux-native' || !build.userNamespace || !build.systemSnapshot
1657
+ || native?.backend !== 'linux-native'
1658
+ || evidence?.schemaVersion !== 3 || evidence.boundary !== 'linux-native-repository-v1'
1659
+ || !('backend' in prepared.job.intent.build) || prepared.job.intent.build.backend !== 'linux-native'
1660
+ || prepared.job.intent.nativeResource?.backend !== 'linux-native' || prepared.job.intent.nativeResource.key !== prepared.job.id
1661
+ || evidence.runtimeDigest !== linuxRuntimeDigest(build.runtime)
1662
+ || evidence.runtimeDigest !== native.runner.runtimeDigest || evidence.executionContractDigest !== native.runner.contractDigest
1663
+ || evidence.buildScriptSha256 !== createHash('sha256').update(LINUX_PREPARED_SOURCE_BUILD_SCRIPT).digest('hex')) {
1664
+ throw new Error('creation verification Linux build binding unavailable');
1665
+ }
1666
+ const evidenceDigest = controlPlaneDigest(evidence);
1667
+ const current = () => this.withPreparedCreationFence({ planId, planDigest: prepared.plan.digest,
1668
+ artifactSha256: evidence.pack.sha256, growthRunDigest: sourceGrowthRunDigest(prepared.plan.creation.growthRun),
1669
+ referenceDigest: controlPlaneDigest(prepared.reference) }, () => {
1670
+ combined.throwIfAborted();
1671
+ if (this.nativeSourceRunner !== native || this.config.sourceBuild !== build || Date.now() >= build.expiresAt
1672
+ || controlPlaneDigest(this.store.getSourcePlan(planId).preparedEvidence) !== evidenceDigest
1673
+ || controlPlaneDigest(this.store.getPreparedSourceJob(planId)) !== controlPlaneDigest(prepared.job)) {
1674
+ throw new Error('creation verification Linux build authority changed');
1675
+ }
1676
+ });
1677
+ current();
1678
+ if (controlPlaneDigest(await this.boundTrust()) !== prepared.job.intent.trustDigest)
1679
+ throw new Error('creation verification trust changed');
1680
+ await verifyLinuxRuntime(build.runtime, combined);
1681
+ current();
1682
+ const execution = await native.runner.inspectSuccessfulExecution(prepared.job.id, combined);
1683
+ current();
1684
+ const artifact = execution?.artifacts[0];
1685
+ if (!execution || execution.runtimeDigest !== evidence.runtimeDigest || execution.contractDigest !== evidence.executionContractDigest
1686
+ || execution.result.status !== 'succeeded' || !execution.result.quiescent || execution.result.exitCode !== 0
1687
+ || !execution.receiptBinding || controlPlaneDigest(execution.receiptBinding) !== controlPlaneDigest(evidence.receipt)
1688
+ || execution.artifacts.length !== 1 || artifact?.path !== LINUX_SOURCE_PACK_PATH || artifact.sha256 !== evidence.pack.sha256
1689
+ || artifact.bytes.length !== evidence.pack.sizeBytes || !Buffer.from(artifact.bytes).equals(prepared.artifact)
1690
+ || createHash('sha256').update((execution.result.stdout ?? '') + '\0' + (execution.result.stderr ?? '')).digest('hex') !== evidence.logDigest) {
1691
+ throw new Error('creation verification original Linux build receipt or artifact changed');
1692
+ }
1693
+ if (controlPlaneDigest(await this.boundTrust()) !== prepared.job.intent.trustDigest)
1694
+ throw new Error('creation verification trust changed');
1695
+ await verifyLinuxRuntime(build.runtime, combined);
1696
+ current();
1697
+ return { runtime: structuredClone(build.runtime), evidenceDigest };
1698
+ };
1699
+ /** Host-only read of the original successful revision build; never admits or replays a job. */
1700
+ inspectPreparedRevisionLinuxBuild = async (planId, signal) => {
1701
+ const combined = AbortSignal.any([this.abort.signal, signal, AbortSignal.timeout(120_000)]);
1702
+ combined.throwIfAborted();
1703
+ const prepared = this.inspectPreparedRevision(planId), build = this.config.sourceBuild;
1704
+ const native = this.nativeSourceRunner, evidence = prepared.plan.preparedEvidence;
1705
+ if (!build || !('backend' in build) || build.backend !== 'linux-native' || !build.userNamespace || !build.systemSnapshot
1706
+ || native?.backend !== 'linux-native'
1707
+ || evidence?.schemaVersion !== 3 || evidence.boundary !== 'linux-native-repository-v1'
1708
+ || !('backend' in prepared.job.intent.build) || prepared.job.intent.build.backend !== 'linux-native'
1709
+ || prepared.job.intent.nativeResource?.backend !== 'linux-native' || prepared.job.intent.nativeResource.key !== prepared.job.id
1710
+ || evidence.runtimeDigest !== linuxRuntimeDigest(build.runtime)
1711
+ || evidence.runtimeDigest !== native.runner.runtimeDigest || evidence.executionContractDigest !== native.runner.contractDigest
1712
+ || evidence.buildScriptSha256 !== createHash('sha256').update(LINUX_PREPARED_SOURCE_BUILD_SCRIPT).digest('hex')) {
1713
+ throw new Error('revision verification Linux build binding unavailable');
1714
+ }
1715
+ const sourceRevision = prepared.plan.sourceRevision;
1716
+ const evidenceDigest = controlPlaneDigest(evidence);
1717
+ const current = () => this.withPreparedRevisionFence({ planId, planDigest: prepared.plan.digest,
1718
+ artifactSha256: evidence.pack.sha256, growthRunDigest: sourceGrowthRunDigest(sourceRevision.growthRun),
1719
+ referenceDigest: controlPlaneDigest(prepared.reference), parentDigest: controlPlaneDigest(sourceRevision.parent) }, () => {
1720
+ combined.throwIfAborted();
1721
+ if (this.nativeSourceRunner !== native || this.config.sourceBuild !== build || Date.now() >= build.expiresAt
1722
+ || controlPlaneDigest(this.store.getSourcePlan(planId).preparedEvidence) !== evidenceDigest
1723
+ || controlPlaneDigest(this.store.getPreparedRevisionJob(planId)) !== controlPlaneDigest(prepared.job)) {
1724
+ throw new Error('revision verification Linux build authority changed');
1725
+ }
1726
+ });
1727
+ current();
1728
+ if (controlPlaneDigest(await this.boundTrust()) !== prepared.job.intent.trustDigest)
1729
+ throw new Error('revision verification trust changed');
1730
+ await verifyLinuxRuntime(build.runtime, combined);
1731
+ current();
1732
+ const execution = await native.runner.inspectSuccessfulExecution(prepared.job.id, combined);
1733
+ current();
1734
+ const artifact = execution?.artifacts[0];
1735
+ if (!execution || execution.runtimeDigest !== evidence.runtimeDigest || execution.contractDigest !== evidence.executionContractDigest
1736
+ || execution.result.status !== 'succeeded' || !execution.result.quiescent || execution.result.exitCode !== 0
1737
+ || !execution.receiptBinding || controlPlaneDigest(execution.receiptBinding) !== controlPlaneDigest(evidence.receipt)
1738
+ || execution.artifacts.length !== 1 || artifact?.path !== LINUX_SOURCE_PACK_PATH || artifact.sha256 !== evidence.pack.sha256
1739
+ || artifact.bytes.length !== evidence.pack.sizeBytes || !Buffer.from(artifact.bytes).equals(prepared.artifact)
1740
+ || createHash('sha256').update((execution.result.stdout ?? '') + '\0' + (execution.result.stderr ?? '')).digest('hex') !== evidence.logDigest) {
1741
+ throw new Error('revision verification original Linux build receipt or artifact changed');
1742
+ }
1743
+ if (controlPlaneDigest(await this.boundTrust()) !== prepared.job.intent.trustDigest)
1744
+ throw new Error('revision verification trust changed');
1745
+ await verifyLinuxRuntime(build.runtime, combined);
1746
+ current();
1747
+ return { runtime: structuredClone(build.runtime), evidenceDigest };
1748
+ };
1385
1749
  /** Signed behavior evidence over stored pack/checks. Adoption must await async review-context recheck. */
1386
1750
  inspectVerifiedCreation = (planId) => {
1387
1751
  const certificate = this.store.getCreationVerification(planId);
@@ -1691,13 +2055,21 @@ export class PluginControlPlaneService extends Service {
1691
2055
  throw new Error('plugin-control-plane: durable source jobs unavailable or still draining');
1692
2056
  return this.sourceRuntime.reconcileUnknown(input);
1693
2057
  };
1694
- async prepareSourceJob(job, signal, assertCurrent) {
2058
+ async prepareSourceJob(job, signal, assertCurrent, preflight) {
1695
2059
  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');
2060
+ if (this.sourceBuilds.size !== 0 || this.sourceInspections.size !== 0) {
2061
+ const error = new Error('plugin-control-plane: another source operation is draining');
2062
+ // This call has not created an operation/worktree or called a runner.
2063
+ if (preflight && job.intent.nativeResource?.backend === 'linux-native' && job.intent.nativeResource.key === job.id
2064
+ && controlPlaneDigest(job.intent) === job.intentDigest) {
2065
+ throw cleanedLinuxSourcePreflight(error, job, preflight.generation, () => !this.abort.signal.aborted
2066
+ && this.nativeSourceRunner?.backend === 'linux-native' && this.nativeSourceRunner.runner === preflight.runner);
2067
+ }
2068
+ throw error;
2069
+ }
1698
2070
  const intent = job.intent;
1699
2071
  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);
2072
+ idempotencyKey: `source-job-plan:${job.id}`, expectedBaseCommit: intent.baseCommit, ttlMs: intent.ttlMs, timeoutMs: intent.build.timeoutMs, offline: true, signal, assertCurrent }, job, preflight);
1701
2073
  this.sourceBuilds.add(operation);
1702
2074
  try {
1703
2075
  return await operation;
@@ -2016,7 +2388,7 @@ export class PluginControlPlaneService extends Service {
2016
2388
  void operation.then(() => this.sourceBuilds.delete(operation), () => this.sourceBuilds.delete(operation));
2017
2389
  return operation;
2018
2390
  }
2019
- async prepareSourcePlanOwned(input, sourceJob) {
2391
+ async prepareSourcePlanOwned(input, sourceJob, preflight) {
2020
2392
  const signal = input.signal === undefined ? this.abort.signal : AbortSignal.any([this.abort.signal, input.signal]);
2021
2393
  const gapOwner = sourceJob?.intent.owner ?? input.owner;
2022
2394
  const creating = sourceJob?.intent.mode === 'create';
@@ -2065,8 +2437,10 @@ export class PluginControlPlaneService extends Service {
2065
2437
  if (!Number.isSafeInteger(ttlMs) || ttlMs < 900_000 || ttlMs > 86_400_000) {
2066
2438
  throw new ControlPlaneCliError('INVALID_ARGUMENT', 'ttlMs must be an integer within 900000..86400000');
2067
2439
  }
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;
2440
+ const buildConfig = this.config.sourceBuild;
2441
+ const repositoryBuild = buildConfig !== undefined && ('backend' in buildConfig
2442
+ ? buildConfig.backend === 'linux-native' : ['repository', 'portable-repository'].includes(buildConfig.profile ?? ''));
2443
+ const maximumTimeoutMs = repositoryBuild ? 1_800_000 : 240_000;
2070
2444
  const timeoutMs = input.timeoutMs ?? 180_000;
2071
2445
  if (!Number.isSafeInteger(timeoutMs) || timeoutMs < 60_000 || timeoutMs > maximumTimeoutMs) {
2072
2446
  throw new ControlPlaneCliError('INVALID_ARGUMENT', `timeoutMs must be an integer within 60000..${maximumTimeoutMs}`);
@@ -2087,6 +2461,7 @@ export class PluginControlPlaneService extends Service {
2087
2461
  const stateRoot = join(this.config.statePath, 'source-worktrees');
2088
2462
  const isolated = await createIsolatedWorktree({ stateRoot, repository, baseCommit, environment,
2089
2463
  ...(sourceJob === undefined ? {} : { worktreeName: basename(sourceJob.intent.worktree) }) });
2464
+ let linuxBuildInvoked = false;
2090
2465
  try {
2091
2466
  const generated = revision !== undefined ? await prepareRevisedPluginWorkspace({ worktree: isolated.worktree,
2092
2467
  baseCommit, name, files: input.files, environment, signal, assertCurrent, revision, parentSource: parent.source, parentCertificate: parent.certificate })
@@ -2116,12 +2491,21 @@ export class PluginControlPlaneService extends Service {
2116
2491
  parentCertificate: parent.certificate, files: input.files } }), ...(capturePack ? { capturePack: true } : {}) };
2117
2492
  const checked = 'backend' in configured
2118
2493
  ? 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');
2494
+ const native = this.nativeSourceRunner;
2495
+ if (sourceJob === undefined || native === undefined || native.backend !== configured.backend
2496
+ || sourceJob.intent.nativeResource?.backend !== configured.backend || sourceJob.intent.nativeResource.key !== sourceJob.id) {
2497
+ throw new ControlPlaneCliError('SOURCE_BOUNDARY', 'native source preparation requires its bound durable source job');
2498
+ }
2499
+ if (configured.backend === 'linux-native' && native.backend === 'linux-native') {
2500
+ linuxBuildInvoked = true;
2501
+ return runLinuxPreparedChecks({ ...common, config: { ...configured, timeoutMs: Math.min(timeoutMs, configured.timeoutMs) },
2502
+ runner: native.runner, sourceJob: { id: sourceJob.id } });
2503
+ }
2504
+ if (configured.backend !== 'macos-seatbelt' || native.backend !== 'macos-seatbelt' || !creating && !revising) {
2505
+ throw new ControlPlaneCliError('SOURCE_BOUNDARY', 'macOS source preparation requires a Host-scaffolded creation job');
2122
2506
  }
2123
2507
  return runNativePreparedChecks({ ...common, config: { ...configured, timeoutMs: Math.min(timeoutMs, configured.timeoutMs) },
2124
- runner: this.nativeSourceRunner, sourceJob: { id: sourceJob.id } });
2508
+ runner: native.runner, sourceJob: { id: sourceJob.id } });
2125
2509
  })()
2126
2510
  : await runDockerPreparedChecks({ ...common, config: { ...configured, timeoutMs: Math.min(timeoutMs, configured.timeoutMs) },
2127
2511
  ...(sourceJob === undefined ? {} : { sourceJob: { id: sourceJob.id, containerName: sourceJob.intent.containerName } }) });
@@ -2145,7 +2529,15 @@ export class PluginControlPlaneService extends Service {
2145
2529
  }
2146
2530
  // The worktree survives success: the owner recomputes its digests on this
2147
2531
  // 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,
2532
+ const commitSource = (callback) => {
2533
+ if (creation?.growthRun?.protocol === 'assistant-growth/source-run/v2') {
2534
+ if (!gapOwner || !('receiptVersion' in gapOwner))
2535
+ throw new Error('work item creation owner unavailable');
2536
+ return this.withWorkItemGrowthFrame(creation.growthRun, input.gapId, gapOwner, false, callback);
2537
+ }
2538
+ return this.taskGaps.withCurrent(input.gapId, gapOwner, callback);
2539
+ };
2540
+ return commitSource(() => this.store.createSourcePlan({ gapId: input.gapId, repository, worktree: isolated.worktree, baseCommit,
2149
2541
  name, generatorDigest: generated?.generatorDigest ?? MODIFY_GENERATOR_DIGEST,
2150
2542
  scope: generated?.scope ?? [`plugins/${name}`], mode: creating ? 'prepared-create' : revising ? 'prepared-revise' : 'modify', ttlMs,
2151
2543
  ...(creation === undefined ? {} : { creation }), ...(revision === undefined ? {} : { revision }),
@@ -2156,6 +2548,19 @@ export class PluginControlPlaneService extends Service {
2156
2548
  }
2157
2549
  catch (error) {
2158
2550
  await isolated.remove();
2551
+ if (preflight && sourceJob && sourceJob.intent.nativeResource?.backend === 'linux-native'
2552
+ && sourceJob.intent.nativeResource.key === sourceJob.id && controlPlaneDigest(sourceJob.intent) === sourceJob.intentDigest) {
2553
+ // Preserve native authority checks across asynchronous worktree cleanup.
2554
+ // The runtime consumes the complete chain immediately before its original claim CAS.
2555
+ if (linuxBuildInvoked && isLinuxSourceBuildFailed(error)) {
2556
+ throw cleanedLinuxSourceBuildFailure(error, sourceJob, preflight.generation, () => !this.abort.signal.aborted && this.nativeSourceRunner?.backend === 'linux-native'
2557
+ && this.nativeSourceRunner.runner === preflight.runner
2558
+ ? consumeLinuxSourceBuildFailed(error, { runner: preflight.runner, key: sourceJob.id }) : undefined);
2559
+ }
2560
+ throw cleanedLinuxSourcePreflight(error, sourceJob, preflight.generation, () => !this.abort.signal.aborted && this.nativeSourceRunner?.backend === 'linux-native'
2561
+ && this.nativeSourceRunner.runner === preflight.runner
2562
+ && (!linuxBuildInvoked || consumeLinuxSourceNotStarted(error, { runner: preflight.runner, key: sourceJob.id })));
2563
+ }
2159
2564
  throw error;
2160
2565
  }
2161
2566
  }