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

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 (97) hide show
  1. package/README.md +96 -22
  2. package/lib/cli.d.ts.map +1 -1
  3. package/lib/cli.js +66 -11
  4. package/lib/cli.js.map +1 -1
  5. package/lib/creation-capability-execution.d.ts +5 -1
  6. package/lib/creation-capability-execution.d.ts.map +1 -1
  7. package/lib/creation-capability-execution.js +42 -0
  8. package/lib/creation-capability-execution.js.map +1 -1
  9. package/lib/creation-capability-feedback.d.ts +16 -1
  10. package/lib/creation-capability-feedback.d.ts.map +1 -1
  11. package/lib/creation-capability-feedback.js +82 -5
  12. package/lib/creation-capability-feedback.js.map +1 -1
  13. package/lib/creation-capability-journal.d.ts.map +1 -1
  14. package/lib/creation-capability-journal.js +2 -0
  15. package/lib/creation-capability-journal.js.map +1 -1
  16. package/lib/creation-capability-runtime.d.ts +47 -1
  17. package/lib/creation-capability-runtime.d.ts.map +1 -1
  18. package/lib/creation-capability-runtime.js +105 -1
  19. package/lib/creation-capability-runtime.js.map +1 -1
  20. package/lib/creation-capability-types.d.ts +1 -0
  21. package/lib/creation-capability-types.d.ts.map +1 -1
  22. package/lib/index.d.ts +3 -0
  23. package/lib/index.d.ts.map +1 -1
  24. package/lib/index.js +2 -0
  25. package/lib/index.js.map +1 -1
  26. package/lib/linux-source-build.d.ts +13 -2
  27. package/lib/linux-source-build.d.ts.map +1 -1
  28. package/lib/linux-source-build.js.map +1 -1
  29. package/lib/live-qualification-authority.js +2 -2
  30. package/lib/live-qualification-authority.js.map +1 -1
  31. package/lib/owner-instruction-drain.d.ts +60 -0
  32. package/lib/owner-instruction-drain.d.ts.map +1 -0
  33. package/lib/owner-instruction-drain.js +101 -0
  34. package/lib/owner-instruction-drain.js.map +1 -0
  35. package/lib/revision-capability-feedback.d.ts +165 -0
  36. package/lib/revision-capability-feedback.d.ts.map +1 -0
  37. package/lib/revision-capability-feedback.js +316 -0
  38. package/lib/revision-capability-feedback.js.map +1 -0
  39. package/lib/revision-capability-journal.d.ts +126 -0
  40. package/lib/revision-capability-journal.d.ts.map +1 -0
  41. package/lib/revision-capability-journal.js +981 -0
  42. package/lib/revision-capability-journal.js.map +1 -0
  43. package/lib/revision-capability-runtime.d.ts +92 -0
  44. package/lib/revision-capability-runtime.d.ts.map +1 -0
  45. package/lib/revision-capability-runtime.js +635 -0
  46. package/lib/revision-capability-runtime.js.map +1 -0
  47. package/lib/revision-capability-types.d.ts +335 -0
  48. package/lib/revision-capability-types.d.ts.map +1 -0
  49. package/lib/revision-capability-types.js +2 -0
  50. package/lib/revision-capability-types.js.map +1 -0
  51. package/lib/service.d.ts +199 -4
  52. package/lib/service.d.ts.map +1 -1
  53. package/lib/service.js +925 -70
  54. package/lib/service.js.map +1 -1
  55. package/lib/source-adoption-authority.js +2 -2
  56. package/lib/source-adoption-authority.js.map +1 -1
  57. package/lib/source-approval-authority.js +3 -3
  58. package/lib/source-approval-authority.js.map +1 -1
  59. package/lib/source-build.d.ts +33 -53
  60. package/lib/source-build.d.ts.map +1 -1
  61. package/lib/source-build.js +28 -408
  62. package/lib/source-build.js.map +1 -1
  63. package/lib/source-job-types.d.ts +111 -4
  64. package/lib/source-job-types.d.ts.map +1 -1
  65. package/lib/source-jobs.d.ts +72 -3
  66. package/lib/source-jobs.d.ts.map +1 -1
  67. package/lib/source-jobs.js +314 -43
  68. package/lib/source-jobs.js.map +1 -1
  69. package/lib/source-release-authority.js +2 -2
  70. package/lib/source-release-authority.js.map +1 -1
  71. package/lib/source-revision.d.ts +29 -3
  72. package/lib/source-revision.d.ts.map +1 -1
  73. package/lib/source-revision.js +40 -9
  74. package/lib/source-revision.js.map +1 -1
  75. package/lib/source-workspace.d.ts +11 -4
  76. package/lib/source-workspace.d.ts.map +1 -1
  77. package/lib/source-workspace.js +51 -18
  78. package/lib/source-workspace.js.map +1 -1
  79. package/lib/sqlite.d.ts +6 -7
  80. package/lib/sqlite.d.ts.map +1 -1
  81. package/lib/sqlite.js +255 -17
  82. package/lib/sqlite.js.map +1 -1
  83. package/lib/store.d.ts +143 -4
  84. package/lib/store.d.ts.map +1 -1
  85. package/lib/store.js +739 -66
  86. package/lib/store.js.map +1 -1
  87. package/lib/systemd-host-authority.js +4 -4
  88. package/lib/systemd-host-authority.js.map +1 -1
  89. package/lib/task-observation-authority.js +2 -2
  90. package/lib/task-observation-authority.js.map +1 -1
  91. package/lib/version.d.ts +1 -1
  92. package/lib/version.js +1 -1
  93. package/lib/work-item-creation-contract.d.ts +3 -1
  94. package/lib/work-item-creation-contract.d.ts.map +1 -1
  95. package/lib/work-item-creation-contract.js +10 -1
  96. package/lib/work-item-creation-contract.js.map +1 -1
  97. package/package.json +11 -8
package/lib/service.js CHANGED
@@ -2,17 +2,20 @@ import { cleanedLinuxSourceBuildFailure, cleanedLinuxSourcePreflight, consumeCle
2
2
  import { AdoptionCoordinatorRuntime, validateAdoptionCoordinatorConfig } from './adoption-coordinator.js';
3
3
  import { CreationCapabilityJournal, creationCapabilityAuthorityDigest, creationCapabilityPublicKey, validateCreationCapabilityConfig } from './creation-capability-journal.js';
4
4
  import { CreationCapabilityRuntime } from './creation-capability-runtime.js';
5
- import { inspectCreationCapabilityAvailabilityAttribution, inspectCreationCapabilityOrdinaryUseReport, inspectCreationCapabilityOrdinaryUseTrend, inspectCreationCapabilityTaskAssociations } from './creation-capability-feedback.js';
5
+ import { RevisionCapabilityJournal, revisionCapabilityAuthorityDigest, revisionCapabilityPublicKey, validateRevisionCapabilityConfig } from './revision-capability-journal.js';
6
+ import { RevisionCapabilityRuntime } from './revision-capability-runtime.js';
7
+ import { inspectCreationCapabilityAvailabilityAttribution, inspectCreationCapabilityBenefitWindow, inspectCreationCapabilityOrdinaryUseReport, inspectCreationCapabilityOrdinaryUseTrend, inspectCreationCapabilityTaskAssociations } from './creation-capability-feedback.js';
8
+ import { autoRollbackInferiorRevision, evaluateRevisionBenefitEvidence, inspectRevisionBenefitTerminal, inspectRevisionCapabilityBenefitWindow, mergeRevisionBenefitWindows, revisionBenefitObservationDeadline, revisionBenefitPairedWindows } from './revision-capability-feedback.js';
6
9
  import { captureCreationCapabilitySource } from './creation-capability-source.js';
7
10
  import { lstat, readFile, realpath } from 'node:fs/promises';
8
11
  import { realpathSync } from 'node:fs';
9
12
  import { basename, dirname, isAbsolute, join, resolve } from 'node:path';
10
13
  import { createHash, createPublicKey, randomBytes } from 'node:crypto';
11
- import { isSourceOwnerContinuation, anySourceGrowthRunDigest as sourceGrowthRunDigest, validateAnySourceGrowthRunBinding as validateSourceGrowthRunBinding, validateWorkItemGrowthAuthorityPins, validateCreationAcceptanceAuthorityRef, verifyPluginCreationVerificationCertificate, validateRevisionAcceptanceAuthorityRef, verifyPluginRevisionVerificationCertificate, validateRevisionRegressionAcceptanceAuthorityRef, verifyPluginRevisionRegressionCertificate } from '@dsh-enhanced/assistant-growth-contract';
14
+ import { isSourceOwnerContinuation, isPluginRevisionTaskSource, anySourceGrowthRunDigest as sourceGrowthRunDigest, validateAnySourceGrowthRunBinding as validateSourceGrowthRunBinding, validateWorkItemGrowthAuthorityPins, validateCreationAcceptanceAuthorityRef, verifyPluginCreationVerificationCertificate, validateRevisionAcceptanceAuthorityRef, verifyPluginRevisionVerificationCertificate, validateRevisionRegressionAcceptanceAuthorityRef, verifyPluginRevisionRegressionCertificate, validateWorkItemRevisionAuthorityPins, revisionBenefitContractDigest } from '@dsh-enhanced/assistant-growth-contract';
12
15
  import { TaskObservationRuntime, validateTaskObservationConfig } from './task-observation-runtime.js';
13
16
  import { LiveQualificationRuntime, validateLiveQualificationConfig } from './live-qualification-runtime.js';
14
17
  import { rollbackPluginWatch } from './cli.js';
15
- import { assertWorkItemRunReference, inertCreationData, WORK_ITEM_ENGINEERING_PROTOCOL } from './work-item-creation-contract.js';
18
+ import { assertWorkItemRunReference, assertWorkItemRevisionRunReference, inertCreationData, WORK_ITEM_ENGINEERING_PROTOCOL } from './work-item-creation-contract.js';
16
19
  import { OwnerTaskFailureGaps } from './owner-task-gaps.js';
17
20
  import { Context, Service } from '@deepseek-ai/cordis';
18
21
  import Schema from '@deepseek-ai/schemastery';
@@ -26,8 +29,8 @@ import { Ed25519SourceReleaseAuthorizationAuthority } from './release.js';
26
29
  import { advanceSourceRelease, sourceReleaseAuthorities, validateSourceReleaseExecutionConfig } from './source-release-runner.js';
27
30
  import { adoptSourceRelease, validateSourceAdoptionConfig } from './source-adoption-runner.js';
28
31
  import { Ed25519ApprovalAuthority } from './approval.js';
29
- import { ControlPlaneStore, MODIFY_GENERATOR_DIGEST, controlPlaneDigest } from './store.js';
30
- import { runDockerPreparedChecks, validateSourceBuildConfig } from './source-build.js';
32
+ import { ControlPlaneStore, MODIFY_GENERATOR_DIGEST, controlPlaneDigest, createOwnerInstructionReviewFreeze } from './store.js';
33
+ import { validateSourceBuildConfig } from './source-build.js';
31
34
  import { runNativePreparedChecks } from './native-source-build.js';
32
35
  import { LINUX_PREPARED_SOURCE_BUILD_SCRIPT, LINUX_SOURCE_PACK_PATH, runLinuxPreparedChecks } from './linux-source-build.js';
33
36
  import { LinuxRepositoryRunner, NativeMacVerifierRunner, linuxRuntimeDigest, verifyLinuxRuntime } from '@dsh-enhanced/assistant-isolation';
@@ -38,7 +41,8 @@ import { inspectSourceCreationContext, prepareCreatedPluginWorkspace, validateSo
38
41
  import { resolveSourceBaseline } from './source-baseline.js';
39
42
  import { inheritedEnvironment, loadTrustConfig, resolveTrustKey } from './trust.js';
40
43
  import { registerPluginControlTools } from './tools.js';
41
- import { SourceGrowthRunUnavailableError, SourceJobRuntime, normalizeSourceJobsConfig } from './source-jobs.js';
44
+ import { SourceGrowthRunUnavailableError, SourceJobRuntime, normalizeSourceJobsConfig, instructionReviewWindowFor } from './source-jobs.js';
45
+ import { drainOwnerInstructionRequests as drainPendingOwnerInstructionRequests, OWNER_INSTRUCTION_CLAIM_TIMEOUT_MS } from './owner-instruction-drain.js';
42
46
  import { installRuntimeObserver, validateRuntimeObserverConfig } from './runtime-observer.js';
43
47
  import { installReplayEndpoint, validateReplayEndpointConfig } from './replay-endpoint.js';
44
48
  import { readPrivateRuntimeObserverKey } from './runtime-observer-protocol.js';
@@ -57,6 +61,7 @@ const schema = Schema.object({
57
61
  revisionVerifications: Schema.any(),
58
62
  revisionRegressions: Schema.any(),
59
63
  creationCapabilities: Schema.any(),
64
+ revisionCapabilities: Schema.any(),
60
65
  sourceApprovals: Schema.any(),
61
66
  sourceReleases: Schema.any(),
62
67
  sourceReleaseExecution: Schema.any(),
@@ -149,7 +154,8 @@ export function normalizeControlPlaneConfig(input) {
149
154
  if (config.sourceBuild !== undefined && 'backend' in config.sourceBuild && config.sourceBuild.backend === 'linux-native') {
150
155
  if (!config.sourceJobs)
151
156
  throw new Error('plugin-control-plane: Linux repository preparation requires durable sourceJobs');
152
- if ((config.creationVerifications || config.creationCapabilities || config.revisionVerifications || config.revisionRegressions)
157
+ if ((config.creationVerifications || config.creationCapabilities || config.revisionVerifications || config.revisionRegressions
158
+ || config.revisionCapabilities)
153
159
  && (!config.sourceBuild.userNamespace || !config.sourceBuild.systemSnapshot)) {
154
160
  throw new Error('plugin-control-plane: Linux independent verification requires the dual-UID repository boundary');
155
161
  }
@@ -246,9 +252,15 @@ export function normalizeControlPlaneConfig(input) {
246
252
  const verification = config.creationVerifications;
247
253
  const jobs = config.sourceJobs;
248
254
  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) {
255
+ if (!build || !('backend' in build)) {
256
+ throw new Error('plugin-control-plane: creation build requires a native execution backend');
257
+ }
258
+ const buildBackend = build.backend;
259
+ if (!('backend' in capability.runner)) {
260
+ throw new Error('plugin-control-plane: creation adoption runner requires a native execution backend');
261
+ }
262
+ const adoptionBackend = capability.runner.backend;
263
+ if (buildBackend !== adoptionBackend) {
252
264
  throw new Error('plugin-control-plane: creation build, verification and adoption require matching execution backends');
253
265
  }
254
266
  if (!verification || !jobs?.creation || capability.namePrefix !== jobs.creation.namePrefix
@@ -262,6 +274,39 @@ export function normalizeControlPlaneConfig(input) {
262
274
  if (adoptionKey.equals(verificationKey))
263
275
  throw new Error('plugin-control-plane: adoption and verification signing keys must be separate');
264
276
  }
277
+ if (config.revisionCapabilities !== undefined) {
278
+ validateRevisionCapabilityConfig(config.revisionCapabilities);
279
+ const capability = config.revisionCapabilities;
280
+ const creation = config.creationCapabilities;
281
+ const verification = config.revisionVerifications;
282
+ const jobs = config.sourceJobs;
283
+ const build = config.sourceBuild;
284
+ if (!creation || !verification || !jobs?.revision || !build) {
285
+ throw new Error('plugin-control-plane: revision adoption requires creation capabilities, revision verification and finite revision source');
286
+ }
287
+ const buildBackend = 'backend' in build ? build.backend : 'docker';
288
+ const runnerBackend = 'backend' in creation.runner ? creation.runner.backend : 'docker';
289
+ if (buildBackend !== runnerBackend
290
+ || capability.namePrefix !== creation.namePrefix || capability.namePrefix !== jobs.revision.namePrefix
291
+ || controlPlaneDigest(capability.owner) !== controlPlaneDigest(creation.owner)
292
+ || capability.expiresAt > creation.expiresAt) {
293
+ throw new Error('plugin-control-plane: revision adoption authority does not match its authenticated creation lane');
294
+ }
295
+ if (capability.authorityId === creation.authorityId || capability.authorityId === verification.authority.authorityId
296
+ || capability.rollback.authorityId === capability.authorityId || capability.rollback.authorityId === creation.authorityId
297
+ || capability.rollback.authorityId === verification.authority.authorityId) {
298
+ throw new Error('plugin-control-plane: revision adoption, rollback, verification and creation authorities must be separate');
299
+ }
300
+ if (capability.keyId === creation.keyId || capability.keyId === verification.authority.keyId) {
301
+ throw new Error('plugin-control-plane: revision adoption signing key must differ from acceptance and creation keys');
302
+ }
303
+ const revisionKey = createPublicKey(revisionCapabilityPublicKey(capability)).export({ format: 'der', type: 'spki' });
304
+ const creationKey = createPublicKey(creationCapabilityPublicKey(creation)).export({ format: 'der', type: 'spki' });
305
+ const verificationKey = createPublicKey(verification.publicKey).export({ format: 'der', type: 'spki' });
306
+ if (revisionKey.equals(creationKey) || revisionKey.equals(verificationKey)) {
307
+ throw new Error('plugin-control-plane: revision adoption signing key material must be separate');
308
+ }
309
+ }
265
310
  if (![config.catalogPath, config.statePath, config.trustPath].every(isAbsolute))
266
311
  throw new Error('plugin-control-plane: catalogPath, statePath and trustPath must be absolute');
267
312
  return config;
@@ -285,12 +330,14 @@ export class PluginControlPlaneService extends Service {
285
330
  nativeSourceRunner;
286
331
  sourceRuntimes = new Set();
287
332
  creationCapabilityRuntime;
333
+ revisionCapabilityRuntime;
288
334
  creationCapabilityRuntimes = new Set();
289
335
  creationCapabilityFlights = new Set();
290
336
  creationCapabilityReconcileRequested = false;
291
337
  growthRunProducer;
292
338
  growthProviderDraining = false;
293
339
  workItemCreationFrame;
340
+ workItemRevisionFrame;
294
341
  constructor(ctx, input) {
295
342
  super(ctx, 'pluginControlPlane');
296
343
  this.config = normalizeControlPlaneConfig(input);
@@ -565,6 +612,11 @@ export class PluginControlPlaneService extends Service {
565
612
  if (typeof current('automations')[method] !== 'function')
566
613
  throw new Error(`plugin-control-plane: durable source jobs require assistantAutomations.${method}`);
567
614
  }
615
+ const instructionDrainEnabled = this.config.sourceJobs?.revision !== undefined
616
+ && this.config.sourceJobs.allowedModes?.includes('revise-created') !== false;
617
+ if (instructionDrainEnabled && typeof current('automations').onSchedulerWake !== 'function') {
618
+ throw new Error('plugin-control-plane: owner instruction drain requires assistantAutomations.onSchedulerWake');
619
+ }
568
620
  if (typeof current('delivery').validateOwnerRoute !== 'function')
569
621
  throw new Error('plugin-control-plane: durable source jobs require Delivery v2 owner validation');
570
622
  let capabilities;
@@ -662,30 +714,133 @@ export class PluginControlPlaneService extends Service {
662
714
  throw error;
663
715
  }
664
716
  }
717
+ let revisionCapabilities;
718
+ if (this.config.revisionCapabilities) {
719
+ const config = this.config.revisionCapabilities;
720
+ const delivery = current('delivery');
721
+ if (typeof delivery.validateOwnerAgentForRoute !== 'function') {
722
+ throw new Error('plugin-control-plane: revision adoption requires authenticated live owner Agent admission');
723
+ }
724
+ const journal = new RevisionCapabilityJournal({ path: join(this.config.statePath, 'revision-adoptions.sqlite'), config });
725
+ try {
726
+ revisionCapabilities = new RevisionCapabilityRuntime({ ctx: jobsCtx, config, journal,
727
+ regressionRequired: this.config.revisionRegressions !== undefined,
728
+ ports: {
729
+ inspect: planId => {
730
+ const certificate = this.inspectVerifiedRevision(planId);
731
+ if (!certificate)
732
+ throw new Error('revision adoption requires a fresh independent certificate');
733
+ const prepared = this.inspectPreparedRevision(planId);
734
+ const source = this.store.readRevisionSource(planId);
735
+ if (!source)
736
+ throw new Error('revision adoption source archive unavailable');
737
+ const regression = this.config.revisionRegressions === undefined
738
+ ? undefined : this.inspectVerifiedRevisionRegression(planId);
739
+ return { certificate, artifact: Buffer.from(prepared.artifact), source,
740
+ plan: { id: prepared.plan.id, digest: prepared.plan.digest, name: prepared.plan.name },
741
+ ...(regression === undefined ? {} : { regression }), owner: config.owner };
742
+ },
743
+ recheck: async (planId, signal) => {
744
+ await this.inspectPreparedRevisionReviewContext(planId, signal);
745
+ if (this.config.sourceBuild && 'backend' in this.config.sourceBuild
746
+ && this.config.sourceBuild.backend === 'linux-native') {
747
+ await this.inspectPreparedRevisionLinuxBuild(planId, signal);
748
+ }
749
+ if (!this.inspectVerifiedRevision(planId))
750
+ throw new Error('revision adoption independent certificate changed');
751
+ },
752
+ parent: planId => {
753
+ const adoption = capabilities?.inspectAdoptionVersion(planId);
754
+ if (!adoption)
755
+ throw new Error('revision adoption parent adoption unavailable');
756
+ return adoption;
757
+ },
758
+ recheckParent: async (planId, signal) => {
759
+ if (!capabilities)
760
+ throw new Error('revision adoption parent adoption unavailable');
761
+ await capabilities.recheckAdoptionVersion(planId, signal);
762
+ },
763
+ inFlightParentCalls: planId => capabilities?.inspectInFlightCalls(planId) ?? 0,
764
+ suspendParent: async (planId) => {
765
+ if (!capabilities)
766
+ throw new Error('revision adoption parent adoption unavailable');
767
+ await capabilities.suspendEntry(planId);
768
+ },
769
+ resumeParent: async (planId) => {
770
+ if (!capabilities)
771
+ throw new Error('revision adoption parent adoption unavailable');
772
+ await capabilities.resumeEntry(planId);
773
+ },
774
+ runVersion: input => {
775
+ if (!capabilities)
776
+ throw new Error('revision adoption runner unavailable');
777
+ return capabilities.runRevisionArtifact(input);
778
+ },
779
+ recheckExecution: async (certificate, signal) => {
780
+ if (!capabilities)
781
+ throw new Error('revision adoption runner unavailable');
782
+ await capabilities.verifyArtifactExecution(certificate, signal);
783
+ },
784
+ matchesExecution: certificate => capabilities?.matchesArtifactExecution(certificate) === true,
785
+ inspectCall: (_record, execution, toolAlias, argumentsJson) => {
786
+ if (!execution.agent || typeof delivery.inspectOwnerForegroundToolCall !== 'function')
787
+ return undefined;
788
+ return delivery.inspectOwnerForegroundToolCall({ agent: execution.agent,
789
+ authorityId: config.owner.authorityId, callId: String(execution.callId),
790
+ toolName: toolAlias, argumentsJson });
791
+ },
792
+ assertCaller: execution => {
793
+ if (!execution.agent)
794
+ throw new Error('revision capability requires an authenticated owner Agent');
795
+ const receipt = delivery.validateOwnerAgentForRoute(execution.agent, config.owner.authorityId);
796
+ if (!receipt || Object.keys(config.owner).some(key => receipt[key] !== config.owner[key])) {
797
+ throw new Error('revision capability caller is not the current authorized owner');
798
+ }
799
+ },
800
+ } });
801
+ }
802
+ catch (error) {
803
+ journal.close();
804
+ throw error;
805
+ }
806
+ }
807
+ const closeRevision = async () => {
808
+ const currentRuntime = revisionCapabilities;
809
+ revisionCapabilities = undefined;
810
+ if (this.revisionCapabilityRuntime === currentRuntime)
811
+ this.revisionCapabilityRuntime = undefined;
812
+ if (currentRuntime)
813
+ await currentRuntime.close();
814
+ };
665
815
  const sourceBuild = this.config.sourceBuild;
666
816
  let nativeRunner;
667
817
  try {
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 } }) };
818
+ nativeRunner = !('backend' in sourceBuild) ? undefined
819
+ : sourceBuild.backend === 'macos-seatbelt'
820
+ ? { backend: 'macos-seatbelt', runner: new NativeMacVerifierRunner({ backend: 'macos-seatbelt',
821
+ stateRoot: sourceBuild.stateRoot, authorityDigest: sourceBuild.authorityDigest,
822
+ runtime: sourceBuild.runtime, source: NATIVE_SOURCE_COMPILER, expiresAt: sourceBuild.expiresAt,
823
+ maxRuns: sourceBuild.maxRuns, maxTotalDurationMs: sourceBuild.maxTotalDurationMs,
824
+ maxDurationMs: sourceBuild.timeoutMs, maxOutputBytes: sourceBuild.outputBytes }) }
825
+ : { backend: 'linux-native', runner: new LinuxRepositoryRunner({ backend: 'linux-native',
826
+ stateRoot: sourceBuild.stateRoot, authorityDigest: sourceBuild.authorityDigest,
827
+ runtime: sourceBuild.runtime, cgroupRoots: sourceBuild.cgroupRoots, expiresAt: sourceBuild.expiresAt,
828
+ maxRuns: sourceBuild.maxRuns, maxTotalDurationMs: sourceBuild.maxTotalDurationMs,
829
+ ...(sourceBuild.stateSeal === undefined ? {} : { stateSeal: sourceBuild.stateSeal }),
830
+ ...(sourceBuild.userNamespace === undefined ? {} : { userNamespace: sourceBuild.userNamespace,
831
+ systemSnapshot: sourceBuild.systemSnapshot }),
832
+ limits: { maxDurationMs: sourceBuild.timeoutMs, maxInputBytes: 64 * 1024 ** 2,
833
+ maxOutputBytes: sourceBuild.outputBytes, maxArtifactBytes: 32 * 1024 ** 2, maxFiles: 128,
834
+ memoryMiB: sourceBuild.memoryMiB, cpus: sourceBuild.cpus, pidsLimit: sourceBuild.pidsLimit,
835
+ workspaceMiB: sourceBuild.workspaceMiB, workspaceInodes: sourceBuild.workspaceInodes,
836
+ tmpMiB: sourceBuild.temporaryMiB, tmpInodes: sourceBuild.temporaryInodes, inputMiB: 65, inputInodes: 16 } }) };
686
837
  }
687
838
  catch (error) {
688
839
  unsubscribeTaskChanges?.();
840
+ try {
841
+ await closeRevision();
842
+ }
843
+ catch { /* original error wins */ }
689
844
  if (capabilities) {
690
845
  await capabilities.close();
691
846
  this.creationCapabilityRuntimes.delete(capabilities);
@@ -699,11 +854,13 @@ export class PluginControlPlaneService extends Service {
699
854
  // Restored fresh adoptions read their original Linux build receipt.
700
855
  // Their source runner must exist before capability reconciliation.
701
856
  await capabilities?.start();
857
+ await revisionCapabilities?.start();
702
858
  this.abort.signal.throwIfAborted();
703
859
  runtime = new SourceJobRuntime({ config: this.config.sourceJobs, build: sourceBuild, statePath: this.config.statePath, store: this.store,
704
860
  ports: {
705
861
  automations: {
706
862
  registerHostExecutor: executor => current('automations').registerHostExecutor(executor),
863
+ onSchedulerWake: listener => current('automations').onSchedulerWake(listener),
707
864
  reconcileSystem: request => current('automations').reconcileSystem(request),
708
865
  reconcileSystemExact: request => current('automations').reconcileSystemExact(request),
709
866
  pauseSystemOwned: request => current('automations').pauseSystemOwned(request),
@@ -712,7 +869,7 @@ export class PluginControlPlaneService extends Service {
712
869
  },
713
870
  delivery: { validateOwnerRoute: request => current('delivery').validateOwnerRoute(request) },
714
871
  }, withGapSourceFence: (gapId, owner, callback) => this.taskGaps.withCurrent(gapId, owner, callback),
715
- withWorkItemGrowthRunFence: (run, gapId, owner, generation, callback) => this.withWorkItemGrowthFrame(run, gapId, owner, generation, callback),
872
+ withWorkItemGrowthRunFence: (run, gapId, owner, generation, callback) => run.protocol === 'assistant-growth/source-run/v3' ? this.withWorkItemRevisionFrame(run, gapId, owner, generation, callback) : this.withWorkItemGrowthFrame(run, gapId, owner, generation, callback),
716
873
  assertGrowthRun: (run, gapId, owner, generation) => this.assertSourceGrowthRun(run, gapId, owner, generation),
717
874
  ...(this.config.revisionVerifications ? { inspectRevisionParent: this.inspectRevisionParent,
718
875
  verifyPreparedRevision: (job, signal) => this.verifyPreparedRevision(job, signal, () => jobsCtx.get('assistantVerifier')) } : {}),
@@ -724,6 +881,12 @@ export class PluginControlPlaneService extends Service {
724
881
  throw new Error('created capability has no prepared source plan');
725
882
  await capabilities.adopt(job.planId, signal);
726
883
  } } : {}),
884
+ ...(revisionCapabilities ? { revisionAdoptionEligible: (planId) => revisionCapabilities.eligible(planId),
885
+ adoptVerifiedRevision: async (job, signal) => {
886
+ if (!job.planId)
887
+ throw new Error('revision adoption has no prepared source plan');
888
+ await revisionCapabilities.adopt(job.planId, signal);
889
+ } } : {}),
727
890
  ...(this.config.sourceApprovals ? { approvePrepared: async (job, signal) => {
728
891
  if (!job.planId)
729
892
  throw new Error('source job has no prepared plan');
@@ -746,6 +909,14 @@ export class PluginControlPlaneService extends Service {
746
909
  throw new Error('source job has no released plan');
747
910
  await this.adoptOwnerSourceRelease({ sourcePlanId: job.planId, signal, expectedTrustDigest: job.intent.trustDigest, assertCurrent });
748
911
  } } : {}),
912
+ ...(this.config.sourceJobs?.revisionBenefit === undefined ? {} : {
913
+ reconcileRevisionBenefits: () => this.reconcileRevisionBenefits(),
914
+ allowReviewOnlyFirstAdoption: this.config.sourceJobs.allowReviewOnlyFirstAdoption === true,
915
+ }),
916
+ ...(instructionDrainEnabled ? {
917
+ hasOwnerInstructionRequests: () => this.store.hasOwnerInstructionRequestWork(Date.now(), OWNER_INSTRUCTION_CLAIM_TIMEOUT_MS),
918
+ drainOwnerInstructionRequests: (signal) => this.drainOwnerInstructionRequests(signal),
919
+ } : {}),
749
920
  trust: () => this.boundTrust(), prepare: (job, signal, assertCurrent) => this.prepareSourceJob(job, signal, assertCurrent, nativeRunner?.backend === 'linux-native' ? { runner: nativeRunner.runner, generation: preparationGeneration } : undefined),
750
921
  ...(nativeRunner?.backend === 'linux-native' ? { isPreparationNotStarted: (error, job) => consumeCleanedLinuxSourcePreflight(error, job, preparationGeneration),
751
922
  deterministicBuildFailure: (error, job) => consumeCleanedLinuxSourceBuildFailure(error, job, preparationGeneration) } : {}),
@@ -757,6 +928,10 @@ export class PluginControlPlaneService extends Service {
757
928
  this.nativeSourceRunner = undefined;
758
929
  await nativeRunner?.runner.close();
759
930
  unsubscribeTaskChanges?.();
931
+ try {
932
+ await closeRevision();
933
+ }
934
+ catch { /* original error wins */ }
760
935
  if (capabilities) {
761
936
  await capabilities.close();
762
937
  this.creationCapabilityRuntimes.delete(capabilities);
@@ -765,6 +940,7 @@ export class PluginControlPlaneService extends Service {
765
940
  }
766
941
  this.sourceRuntimes.add(runtime);
767
942
  this.creationCapabilityRuntime = capabilities;
943
+ this.revisionCapabilityRuntime = revisionCapabilities;
768
944
  const close = async () => {
769
945
  unsubscribeTaskChanges?.();
770
946
  if (this.sourceRuntime === runtime)
@@ -781,12 +957,20 @@ export class PluginControlPlaneService extends Service {
781
957
  this.sourceRuntimes.delete(runtime);
782
958
  if (this.creationCapabilityRuntime === capabilities)
783
959
  this.creationCapabilityRuntime = undefined;
960
+ let revisionCloseError;
961
+ try {
962
+ await closeRevision();
963
+ }
964
+ catch (error) {
965
+ revisionCloseError = error;
966
+ }
784
967
  const results = await Promise.allSettled([nativeRunner?.runner.close(), capabilities?.close()]);
785
968
  if (capabilities)
786
969
  this.creationCapabilityRuntimes.delete(capabilities);
787
970
  const failures = results.filter((result) => result.status === 'rejected');
788
- if (closeError !== undefined || failures.length) {
789
- throw new AggregateError([...(closeError === undefined ? [] : [closeError]), ...failures.map(result => result.reason)], 'source runtime cleanup failed');
971
+ if (closeError !== undefined || revisionCloseError !== undefined || failures.length) {
972
+ throw new AggregateError([...(closeError === undefined ? [] : [closeError]),
973
+ ...(revisionCloseError === undefined ? [] : [revisionCloseError]), ...failures.map(result => result.reason)], 'source runtime cleanup failed');
790
974
  }
791
975
  };
792
976
  try {
@@ -1145,6 +1329,78 @@ export class PluginControlPlaneService extends Service {
1145
1329
  this.abort.signal.throwIfAborted();
1146
1330
  return this.creationCapabilityRuntime?.inspectCallEvidence(planId) ?? [];
1147
1331
  };
1332
+ /** Host-only read-only revision adoption readiness; undefined when not configured or not admitting. */
1333
+ inspectRevisionAdoptionReadiness = () => {
1334
+ this.abort.signal.throwIfAborted();
1335
+ return this.revisionCapabilityRuntime?.inspectReadiness();
1336
+ };
1337
+ /** Host-only current stable entry version; no certificate, artifact bytes or key material escape. */
1338
+ inspectRevisionCapabilityEntry = (entryId) => {
1339
+ this.abort.signal.throwIfAborted();
1340
+ return this.revisionCapabilityRuntime?.inspectEntry(entryId);
1341
+ };
1342
+ /** Host-only bounded call attribution of one stable entry, including the exact version per call. */
1343
+ inspectRevisionCapabilityCalls = (entryId) => {
1344
+ this.abort.signal.throwIfAborted();
1345
+ return this.revisionCapabilityRuntime?.inspectCallEvidence(entryId) ?? [];
1346
+ };
1347
+ /**
1348
+ * Host-only P1-4 negative-observation rollback of one active revision: after
1349
+ * the observation window, a recorded `inferior` benefit outcome rolls the
1350
+ * stable entry back to its exact parent under the separate rollback grant,
1351
+ * and stops this lineage from preparing further spontaneous revisions until
1352
+ * the owner re-authorizes. Anything else leaves the adoption untouched.
1353
+ */
1354
+ autoRollbackRevisionIfInferior = async (planId, options) => {
1355
+ this.abort.signal.throwIfAborted();
1356
+ const runtime = this.revisionCapabilityRuntime;
1357
+ const contract = this.config.sourceJobs?.revisionBenefit;
1358
+ if (runtime === undefined || contract === undefined) {
1359
+ return { rolledBack: false, reason: 'benefit-not-configured' };
1360
+ }
1361
+ const intent = this.store.inspectRevisionBenefitIntent(planId);
1362
+ const pinned = intent?.pinnedContractDigest === undefined ? undefined : { contractDigest: intent.pinnedContractDigest };
1363
+ const liveDigest = revisionBenefitContractDigest(contract);
1364
+ const plan = this.store.getSourcePlan(planId);
1365
+ const result = await autoRollbackInferiorRevision({
1366
+ runtime: { listEntries: () => runtime.listEntries(), inspectEntry: entryId => runtime.inspectEntry(entryId),
1367
+ rollback: input => runtime.rollback(input) },
1368
+ planId, benefitConfigured: true,
1369
+ pinnedContractDigest: pinned !== undefined && pinned.contractDigest === liveDigest ? pinned.contractDigest : undefined,
1370
+ growthRunCreatedAt: plan.sourceRevision?.growthRun?.createdAt,
1371
+ observeWindowMs: options.observeWindowMs,
1372
+ ...(options.observationDeadline === undefined ? {} : { observationDeadline: options.observationDeadline }),
1373
+ outcome: this.store.inspectRevisionBenefitOutcome(planId), now: Date.now(),
1374
+ ...(options.signal === undefined ? {} : {
1375
+ rollback: (target, input) => target.rollback({ ...input, signal: options.signal }),
1376
+ }),
1377
+ });
1378
+ return { rolledBack: result.rolledBack, reason: result.reason,
1379
+ ...(result.record === undefined ? {} : { record: result.record }) };
1380
+ };
1381
+ /**
1382
+ * Host-only authenticated-owner exact parent rollback under its own finite grant. The parent
1383
+ * adoption receipt must still be current and the entry must be on the exact expected revision.
1384
+ */
1385
+ rollbackRevisionCapabilityAdoption = async (input) => {
1386
+ this.abort.signal.throwIfAborted();
1387
+ const runtime = this.revisionCapabilityRuntime;
1388
+ const config = this.config.revisionCapabilities;
1389
+ if (!runtime || !config)
1390
+ throw new Error('revision adoption is not configured');
1391
+ const delivery = this.ctx.get('assistantDelivery', false);
1392
+ if (!delivery || typeof delivery.validateOwnerRoute !== 'function')
1393
+ throw new Error('revision rollback requires Delivery owner validation');
1394
+ const receipt = delivery.validateOwnerRoute({ authorityId: config.owner.authorityId, principalId: config.owner.principalId,
1395
+ workspace: config.owner.workspace, agentPreset: config.owner.agentPreset });
1396
+ if (!receipt || receipt.authorityId !== config.owner.authorityId || receipt.authorityHash !== config.owner.authorityHash
1397
+ || receipt.principalId !== config.owner.principalId || receipt.principalRecordId !== config.owner.principalRecordId
1398
+ || receipt.principalVersion !== config.owner.principalVersion || receipt.workspace !== config.owner.workspace
1399
+ || receipt.agentPreset !== config.owner.agentPreset) {
1400
+ throw new Error('revision rollback caller is not the current authorized owner');
1401
+ }
1402
+ return runtime.rollback({ entryId: input.entryId, expectedCurrentPlanId: input.expectedVersionPlanId });
1403
+ };
1148
1404
  /** Host-only historical source data. It grants no continuation, activation or replacement. */
1149
1405
  inspectCreatedCapabilitySource = (planId) => {
1150
1406
  this.abort.signal.throwIfAborted();
@@ -1261,6 +1517,143 @@ export class PluginControlPlaneService extends Service {
1261
1517
  return undefined;
1262
1518
  }
1263
1519
  };
1520
+ /**
1521
+ * Host-only P1-4 benefit evaluation of one prepared revision. Windows come
1522
+ * from the revision journal's foreground-attributed entry calls (parent
1523
+ * version before adoption, candidate version after it), never from the
1524
+ * creation-capability association ledger. The outcome is recorded only for a
1525
+ * revision job whose grant pins this exact contract digest. Never adoption
1526
+ * evidence by itself and never model-callable.
1527
+ */
1528
+ /**
1529
+ * Host-only terminal read: the benefit ledger outcome/reason plus the latest
1530
+ * exact-parent rollback from the revision journal. Not a model-callable tool.
1531
+ */
1532
+ inspectRevisionBenefitTerminal = (planId) => inspectRevisionBenefitTerminal({ planId, outcome: this.store.inspectRevisionBenefitOutcome(planId),
1533
+ runtime: this.revisionCapabilityRuntime ?? { inspectBenefitEvidence: () => undefined, listEntries: () => [],
1534
+ inspectLatestRollback: () => undefined } });
1535
+ evaluateRevisionBenefit = (planId) => {
1536
+ const contract = this.config.sourceJobs?.revisionBenefit;
1537
+ const plan = this.store.getSourcePlan(planId);
1538
+ const runtime = this.revisionCapabilityRuntime;
1539
+ // The benefit rows are owner-scope evidence; adoption and creation
1540
+ // capabilities are validated to carry the same owner when both are set.
1541
+ const owner = this.config.revisionCapabilities?.owner ?? this.config.creationCapabilities?.owner;
1542
+ if (!contract || !runtime || !owner || plan.mode !== 'prepared-revise'
1543
+ || !plan.sourceRevision?.parent)
1544
+ return undefined;
1545
+ const intent = this.store.inspectRevisionBenefitIntent(planId);
1546
+ if (intent === undefined || intent.pinnedContractDigest === undefined || !intent.growthRunAcceptance)
1547
+ return undefined;
1548
+ const pinned = { contractDigest: intent.pinnedContractDigest };
1549
+ const evaluationPort = this.ctx.get('assistantEvaluation', false);
1550
+ const delivery = this.ctx.get('assistantDelivery', false);
1551
+ if (!evaluationPort || !delivery)
1552
+ return undefined;
1553
+ // Candidate evidence is the revision journal's post-adoption foreground
1554
+ // calls. Parent evidence combines its creation-adoption associations (before
1555
+ // the switch) with revision-journal parent-version calls (after a rollback or
1556
+ // for a chained revision parent); the two sources are disjoint by entry
1557
+ // lifetime. Windows are equal-length and adjacent around the adoption.
1558
+ const candidate = runtime.inspectBenefitEvidence(planId);
1559
+ if (!candidate)
1560
+ return undefined;
1561
+ const parentPlanId = plan.sourceRevision.parent.planId;
1562
+ const parentRevision = runtime.inspectBenefitEvidence(parentPlanId);
1563
+ const parentInput = this.createdCapabilityFeedbackInput(parentPlanId);
1564
+ const parentVersion = parentRevision?.version ?? (() => {
1565
+ const receipt = parentInput?.().record.receipt;
1566
+ return receipt === undefined ? undefined : { adoptedAt: receipt.adoptedAt, expiresAt: receipt.expiresAt };
1567
+ })();
1568
+ if (parentVersion === undefined)
1569
+ return undefined;
1570
+ const now = Date.now();
1571
+ const windows = revisionBenefitPairedWindows({ candidate, parent: parentVersion, now });
1572
+ if (windows === undefined)
1573
+ return undefined;
1574
+ const creationStats = parentInput === undefined ? undefined
1575
+ : inspectCreationCapabilityBenefitWindow({ ...parentInput(), window: windows.parent });
1576
+ const revisionParentStats = parentRevision === undefined ? undefined
1577
+ : inspectRevisionCapabilityBenefitWindow({ sample: parentRevision, windowStart: windows.parent.start,
1578
+ windowEnd: windows.parent.end, owner, evaluation: evaluationPort, delivery, now });
1579
+ const parent = creationStats === undefined ? revisionParentStats
1580
+ : revisionParentStats === undefined ? creationStats : mergeRevisionBenefitWindows(creationStats, revisionParentStats);
1581
+ if (parent === undefined)
1582
+ return undefined;
1583
+ const result = evaluateRevisionBenefitEvidence({ contract, candidate, parent, windows, owner,
1584
+ evaluation: evaluationPort, delivery, now });
1585
+ if (result === undefined)
1586
+ return undefined;
1587
+ if (result.contractDigest === pinned.contractDigest) {
1588
+ this.store.recordRevisionBenefit(planId, { contractDigest: result.contractDigest,
1589
+ status: result.evaluation.verdict.status, reason: result.evaluation.verdict.reason,
1590
+ evaluation: result.evaluation });
1591
+ }
1592
+ return { planId, contractDigest: result.contractDigest,
1593
+ pinned: result.contractDigest === pinned.contractDigest, evaluation: result.evaluation };
1594
+ };
1595
+ /**
1596
+ * Host-only P1-4 reconciliation over every prepared revision plan: after its
1597
+ * observation window, evaluate the benefit outcome and roll back an active
1598
+ * inferior revision. Driven by the SourceJobs continuation cron occurrence;
1599
+ * never a new loop and never model-callable.
1600
+ */
1601
+ reconcileRevisionBenefits = () => {
1602
+ const contract = this.config.sourceJobs?.revisionBenefit;
1603
+ const runtime = this.revisionCapabilityRuntime;
1604
+ if (!contract || !runtime)
1605
+ return;
1606
+ const contractDigest = revisionBenefitContractDigest(contract);
1607
+ for (const plan of this.store.listPreparedSourcePlans()) {
1608
+ if (plan.mode !== 'prepared-revise')
1609
+ continue;
1610
+ try {
1611
+ const intent = this.store.inspectRevisionBenefitIntent(plan.id);
1612
+ if (intent?.pinnedContractDigest === undefined || intent.pinnedContractDigest !== contractDigest)
1613
+ continue;
1614
+ const evidence = runtime.inspectBenefitEvidence(plan.id);
1615
+ if (evidence === undefined)
1616
+ continue;
1617
+ const now = Date.now();
1618
+ const parentVersion = this.revisionBenefitParentVersion(plan);
1619
+ const deadline = parentVersion === undefined ? undefined
1620
+ : revisionBenefitObservationDeadline({ candidate: evidence, parent: parentVersion, now });
1621
+ if (deadline === undefined)
1622
+ continue;
1623
+ const evaluation = this.evaluateRevisionBenefit(plan.id);
1624
+ const outcome = this.store.inspectRevisionBenefitOutcome(plan.id);
1625
+ if (evaluation !== undefined && evaluation.pinned && evaluation.evaluation.verdict.status === 'inferior') {
1626
+ void this.autoRollbackRevisionIfInferior(plan.id, { observeWindowMs: 0 })
1627
+ .catch(() => { });
1628
+ continue;
1629
+ }
1630
+ // Bounded terminal: once the observation window elapsed without a
1631
+ // non-inferior verdict, record the owner-visible unverified result and
1632
+ // roll the stable entry back instead of leaving a silent mount.
1633
+ if (now < deadline || outcome?.status === 'non-inferior')
1634
+ continue;
1635
+ if (outcome === undefined || outcome.status === 'insufficient') {
1636
+ this.store.recordRevisionBenefit(plan.id, { contractDigest, status: 'insufficient',
1637
+ reason: 'observation-expired-unverified',
1638
+ evaluation: evaluation?.evaluation ?? { verdict: { status: 'insufficient', reason: 'observation-expired-unverified' } } });
1639
+ }
1640
+ void this.autoRollbackRevisionIfInferior(plan.id, { observeWindowMs: 0, observationDeadline: deadline })
1641
+ .catch(() => { });
1642
+ }
1643
+ catch { /* a plan without a current job or admission is skipped this tick */ }
1644
+ }
1645
+ };
1646
+ /** Parent adoption version for the same equal-length paired window as evaluation. */
1647
+ revisionBenefitParentVersion(plan) {
1648
+ const parentPlanId = plan.sourceRevision?.parent?.planId;
1649
+ if (parentPlanId === undefined)
1650
+ return undefined;
1651
+ const revision = this.revisionCapabilityRuntime?.inspectBenefitEvidence(parentPlanId);
1652
+ if (revision !== undefined)
1653
+ return revision.version;
1654
+ const receipt = this.createdCapabilityFeedbackInput(parentPlanId)?.().record.receipt;
1655
+ return receipt === undefined ? undefined : { adoptedAt: receipt.adoptedAt, expiresAt: receipt.expiresAt };
1656
+ }
1264
1657
  /**
1265
1658
  * Host-only, owner-invoked: seal the current ordinary-use trend of one adoption into the append-only,
1266
1659
  * content-addressed snapshot ledger. Re-sealing an unchanged trend returns the existing row (`created: false`);
@@ -1323,7 +1716,8 @@ export class PluginControlPlaneService extends Service {
1323
1716
  const growth = plan.creation?.growthRun;
1324
1717
  const artifactSha256 = createHash('sha256').update(record.artifact).digest('hex');
1325
1718
  if (!certificate || controlPlaneDigest(certificate) !== controlPlaneDigest(record.certificate)
1326
- || !growth || !plan.creation || !job.intent.creation?.growthRun || !plan.sourceCheck || !plan.preparedEvidence
1719
+ || !growth || growth.protocol === 'assistant-growth/source-run/v3'
1720
+ || !plan.creation || !job.intent.creation?.growthRun || !plan.sourceCheck || !plan.preparedEvidence
1327
1721
  || plan.id !== receipt.planId || plan.digest !== receipt.planDigest
1328
1722
  || plan.name !== record.certificate.plan.name || plan.generatorDigest !== record.certificate.plan.generatorDigest
1329
1723
  || plan.sourceCheck.treeDigest !== record.certificate.plan.sourceTreeDigest
@@ -1375,7 +1769,8 @@ export class PluginControlPlaneService extends Service {
1375
1769
  const ownerGrant = this.config.creationCapabilities?.owner;
1376
1770
  const artifactSha256 = createHash('sha256').update(record.artifact).digest('hex');
1377
1771
  if (!certificate || controlPlaneDigest(certificate) !== controlPlaneDigest(record.certificate)
1378
- || !growth || !creation || !ownerGrant || !job.intent.creation?.growthRun
1772
+ || !growth || growth.protocol === 'assistant-growth/source-run/v3'
1773
+ || !creation || !ownerGrant || !job.intent.creation?.growthRun
1379
1774
  || !plan.sourceCheck || !plan.preparedEvidence
1380
1775
  || plan.id !== receipt.planId || plan.digest !== receipt.planDigest
1381
1776
  || plan.name !== record.certificate.plan.name || plan.generatorDigest !== record.certificate.plan.generatorDigest
@@ -1483,7 +1878,7 @@ export class PluginControlPlaneService extends Service {
1483
1878
  throw new Error('work item growth authority pins changed');
1484
1879
  const source = this.taskGaps.engineeringSnapshot(gapId, owner);
1485
1880
  return producer.withWorkItemRunFence({ runId: run.runId, intentDigest: run.intentDigest, expectedGrowthRunDigest: sourceGrowthRunDigest(run) }, actual => {
1486
- if (this.growthRunProducer !== producer || sourceGrowthRunDigest(actual) !== sourceGrowthRunDigest(run) || this.workItemCreationFrame)
1881
+ if (this.growthRunProducer !== producer || sourceGrowthRunDigest(actual) !== sourceGrowthRunDigest(run) || actual.protocol !== 'assistant-growth/source-run/v2' || this.workItemCreationFrame)
1487
1882
  throw new Error('work item producer changed its true binding');
1488
1883
  this.workItemCreationFrame = { gapId, run: actual, source };
1489
1884
  try {
@@ -1494,6 +1889,121 @@ export class PluginControlPlaneService extends Service {
1494
1889
  }
1495
1890
  });
1496
1891
  }
1892
+ inspectWorkItemPreparedRevisionProtocol = () => {
1893
+ return !this.abort.signal.aborted && this.config.sourceJobs?.revision?.workItemFailures === true && this.sourceRuntime?.available() ? 'dsh-prepared-revision/v2' : undefined;
1894
+ };
1895
+ inspectWorkItemRevisionReadiness = (input) => {
1896
+ try {
1897
+ this.abort.signal.throwIfAborted();
1898
+ const owner = inertCreationData(input).owner, config = this.config, source = this.sourceRuntime?.inspectWorkItemRevisionReadiness(owner);
1899
+ if (!source || !config.sourceJobs || !config.sourceBuild || config.sourceJobs.revision?.workItemFailures !== true || !config.revisionVerifications)
1900
+ return undefined;
1901
+ const delivery = this.ctx.get('assistantDelivery', false);
1902
+ const verifier = this.ctx.get('assistantVerifier', false);
1903
+ if (typeof delivery?.withOwnerWorkItemEngineeringContextFence !== 'function' || typeof verifier?.inspectWorkItemRevisionReviewReadiness !== 'function')
1904
+ return undefined;
1905
+ const actual = delivery.validateOwnerRoute({ authorityId: owner.authorityId, principalId: owner.principalId, workspace: owner.workspace, agentPreset: owner.agentPreset });
1906
+ if (!isSourceOwnerContinuation(actual, owner))
1907
+ return undefined;
1908
+ const review = verifier.inspectWorkItemRevisionReviewReadiness();
1909
+ if (!review || review.protocol !== 'assistant-growth/work-item-revision-review-readiness/v1'
1910
+ || review.sourceProtocol !== WORK_ITEM_ENGINEERING_PROTOCOL || review.runProtocol !== 'assistant-growth/source-run/v3'
1911
+ || review.workItemFailures !== true || controlPlaneDigest(review.authority) !== controlPlaneDigest(config.revisionVerifications.authority)
1912
+ || !createPublicKey(review.publicKey).export({ type: 'spki', format: 'der' }).equals(createPublicKey(config.revisionVerifications.publicKey).export({ type: 'spki', format: 'der' })))
1913
+ return undefined;
1914
+ const adoption = this.revisionCapabilityRuntime?.inspectReadiness();
1915
+ const adoptionPins = config.revisionCapabilities && adoption ? {
1916
+ authorityId: adoption.authority.authorityId,
1917
+ authorityDigest: revisionCapabilityAuthorityDigest({ path: join(config.statePath, 'revision-adoptions.sqlite'), config: config.revisionCapabilities }),
1918
+ expiresAt: adoption.authority.expiresAt
1919
+ } : undefined;
1920
+ if (adoption !== undefined && !adoptionPins)
1921
+ return undefined;
1922
+ const pins = { protocol: 'assistant-growth/work-item-revision-authorities/v1',
1923
+ sourceJobs: { authorityId: source.authority.id, authorityDigest: source.authority.digest, expiresAt: source.authority.expiresAt },
1924
+ revisionGrant: { id: source.revision.id, digest: controlPlaneDigest(source.revision), expiresAt: source.revision.expiresAt, workItemFailures: true },
1925
+ revisionAcceptance: structuredClone(review.authority),
1926
+ ...(adoptionPins ? { revisionAdoption: adoptionPins } : {}) };
1927
+ validateWorkItemRevisionAuthorityPins(pins);
1928
+ if (source.authority.digest !== controlPlaneDigest({ config: config.sourceJobs, build: config.sourceBuild }))
1929
+ return undefined;
1930
+ const expiries = [pins.sourceJobs.expiresAt, pins.revisionGrant.expiresAt, pins.revisionAcceptance.expiresAt, ...(pins.revisionAdoption ? [pins.revisionAdoption.expiresAt] : [])];
1931
+ if (expiries.some(expiry => Date.now() >= expiry))
1932
+ return undefined;
1933
+ const remaining = { ...source.remaining, verifications: review.remainingVerifications, ...(adoption ? { adoptions: adoption.remainingAdoptions } : {}) };
1934
+ if (Object.values(remaining).some(count => !Number.isSafeInteger(count) || count < 0))
1935
+ return undefined;
1936
+ return { protocol: 'assistant-growth/work-item-revision-readiness/v1', sourceProtocol: WORK_ITEM_ENGINEERING_PROTOCOL, runProtocol: 'assistant-growth/source-run/v3', workItemFailures: true, pins, remaining, available: Object.values(remaining).every(count => count > 0) && review.available && (adoption ? adoption.available : true) };
1937
+ }
1938
+ catch {
1939
+ return undefined;
1940
+ }
1941
+ };
1942
+ assertWorkItemRevisionPinsLocal(run) {
1943
+ const jobs = this.config.sourceJobs, verification = this.config.revisionVerifications;
1944
+ if (!jobs || !this.config.sourceBuild || jobs.revision?.workItemFailures !== true || !verification
1945
+ || run.authorities.sourceJobs.authorityId !== jobs.authorityId || run.authorities.sourceJobs.authorityDigest !== controlPlaneDigest({ config: jobs, build: this.config.sourceBuild }) || run.authorities.sourceJobs.expiresAt !== jobs.expiresAt
1946
+ || run.authorities.revisionGrant.id !== jobs.revision.id || run.authorities.revisionGrant.digest !== controlPlaneDigest(jobs.revision) || run.authorities.revisionGrant.expiresAt !== jobs.revision.expiresAt
1947
+ || run.authorities.revisionGrant.workItemFailures !== true
1948
+ || controlPlaneDigest(run.revisionAcceptance) !== controlPlaneDigest(verification.authority) || controlPlaneDigest(run.authorities.revisionAcceptance) !== controlPlaneDigest(verification.authority)
1949
+ || (run.authorities.revisionAdoption !== undefined) !== (this.config.revisionCapabilities !== undefined))
1950
+ throw new Error('work item revision authority pins changed');
1951
+ if (run.authorities.revisionAdoption !== undefined && this.config.revisionCapabilities !== undefined) {
1952
+ const adoption = this.config.revisionCapabilities;
1953
+ if (run.authorities.revisionAdoption.authorityId !== adoption.authorityId
1954
+ || run.authorities.revisionAdoption.authorityDigest !== revisionCapabilityAuthorityDigest({ path: join(this.config.statePath, 'revision-adoptions.sqlite'), config: adoption })
1955
+ || run.authorities.revisionAdoption.expiresAt !== adoption.expiresAt)
1956
+ throw new Error('work item revision authority pins changed');
1957
+ }
1958
+ if (run.authorities.revisionRegressionAcceptance !== undefined) {
1959
+ const regression = this.config.revisionRegressions;
1960
+ if (!regression || controlPlaneDigest(run.authorities.revisionRegressionAcceptance) !== controlPlaneDigest(regression.authority))
1961
+ throw new Error('work item revision authority pins changed');
1962
+ }
1963
+ const expiries = [run.authorities.sourceJobs.expiresAt, run.authorities.revisionGrant.expiresAt, run.authorities.revisionAcceptance.expiresAt, ...(run.authorities.revisionAdoption ? [run.authorities.revisionAdoption.expiresAt] : [])];
1964
+ if (expiries.some(expiry => Date.now() >= expiry))
1965
+ throw new Error('work item revision authority pins changed or expired');
1966
+ }
1967
+ assertWorkItemRevisionRunLocal(run, gapId, owner, generation) {
1968
+ const reference = this.store.getOwnerTaskFailureReference(gapId);
1969
+ if (!reference || reference.schemaVersion !== 2 || run.ownerDigest !== controlPlaneDigest(reference.owner) || !isSourceOwnerContinuation(owner, reference.owner))
1970
+ throw new Error('work item revision owner reference changed');
1971
+ assertWorkItemRevisionRunReference(run, reference);
1972
+ this.assertWorkItemRevisionPinsLocal(run);
1973
+ if (Date.now() >= run.expiresAt || generation && Date.now() >= run.generationDeadlineAt)
1974
+ throw new Error('work item revision source run expired');
1975
+ if (run.modelOrigin === 'inherited-owner-task' && this.workItemRevisionFrame && controlPlaneDigest(this.workItemRevisionFrame.source.task.proofIdentity.supplier) !== controlPlaneDigest(run.model))
1976
+ throw new Error('work item revision source supplier changed');
1977
+ }
1978
+ withWorkItemRevisionFrame(run, gapId, owner, generation, callback) {
1979
+ this.abort.signal.throwIfAborted();
1980
+ if (this.workItemRevisionFrame) {
1981
+ if (this.workItemRevisionFrame.gapId !== gapId || sourceGrowthRunDigest(this.workItemRevisionFrame.run) !== sourceGrowthRunDigest(run))
1982
+ throw new Error('work item revision frame reentry');
1983
+ this.assertWorkItemRevisionRunLocal(run, gapId, owner, generation);
1984
+ return callback();
1985
+ }
1986
+ const producer = this.growthRunProducer;
1987
+ if (!producer || producer.protocol !== 'assistant-growth-source-run-producer/v2')
1988
+ throw new SourceGrowthRunUnavailableError('work item revision growth producer unavailable');
1989
+ const readiness = this.inspectWorkItemRevisionReadiness({ owner });
1990
+ if (!readiness)
1991
+ throw new SourceGrowthRunUnavailableError('work item revision growth peers unavailable');
1992
+ if (controlPlaneDigest(readiness.pins) !== controlPlaneDigest(run.authorities))
1993
+ throw new Error('work item revision authority pins changed');
1994
+ const source = this.taskGaps.engineeringSnapshot(gapId, owner);
1995
+ return producer.withWorkItemRunFence({ runId: run.runId, intentDigest: run.intentDigest, expectedGrowthRunDigest: sourceGrowthRunDigest(run) }, actual => {
1996
+ if (this.growthRunProducer !== producer || sourceGrowthRunDigest(actual) !== sourceGrowthRunDigest(run) || actual.protocol !== 'assistant-growth/source-run/v3' || this.workItemRevisionFrame)
1997
+ throw new Error('work item revision producer changed its true binding');
1998
+ this.workItemRevisionFrame = { gapId, run: actual, source };
1999
+ try {
2000
+ return this.store.withOwnerTaskFailureGapAdmission(gapId, () => { this.assertWorkItemRevisionRunLocal(actual, gapId, owner, generation); return callback(); });
2001
+ }
2002
+ finally {
2003
+ this.workItemRevisionFrame = undefined;
2004
+ }
2005
+ });
2006
+ }
1497
2007
  assertSourceGrowthRun(expected, gapId, owner, generation) {
1498
2008
  validateSourceGrowthRunBinding(expected);
1499
2009
  if (expected.protocol === 'assistant-growth/source-run/v2') {
@@ -1503,6 +2013,13 @@ export class PluginControlPlaneService extends Service {
1503
2013
  }
1504
2014
  return this.withWorkItemGrowthFrame(expected, gapId, owner, generation, () => { });
1505
2015
  }
2016
+ if (expected.protocol === 'assistant-growth/source-run/v3') {
2017
+ if (this.workItemRevisionFrame) {
2018
+ this.assertWorkItemRevisionRunLocal(expected, gapId, owner, generation);
2019
+ return;
2020
+ }
2021
+ return this.withWorkItemRevisionFrame(expected, gapId, owner, generation, () => { });
2022
+ }
1506
2023
  const producer = this.growthRunProducer;
1507
2024
  if (!producer)
1508
2025
  throw new SourceGrowthRunUnavailableError('source growth run producer unavailable');
@@ -1525,7 +2042,7 @@ export class PluginControlPlaneService extends Service {
1525
2042
  || controlPlaneDigest(expected.revisionRegressionAcceptance) !== controlPlaneDigest(this.config.revisionRegressions.authority))) {
1526
2043
  throw new Error('source growth run regression policy changed or was not pinned before authoring');
1527
2044
  }
1528
- if (expected.creationAcceptance !== undefined
2045
+ if ('creationAcceptance' in expected && expected.creationAcceptance !== undefined
1529
2046
  && (policy === undefined || controlPlaneDigest(policy) !== controlPlaneDigest(expected.creationAcceptance))) {
1530
2047
  throw new Error('source growth run creation acceptance policy changed or was not pinned before authoring');
1531
2048
  }
@@ -1609,6 +2126,7 @@ export class PluginControlPlaneService extends Service {
1609
2126
  const current = this.store.getSourcePlan(input.planId);
1610
2127
  const job = this.store.getPreparedSourceJob(input.planId);
1611
2128
  if (current.digest !== input.planDigest || current.mode !== 'prepared-create' || !current.creation?.growthRun
2129
+ || current.creation.growthRun.protocol === 'assistant-growth/source-run/v3'
1612
2130
  || !job.intent.creation?.growthRun || !current.sourceCheck || !current.preparedEvidence
1613
2131
  || current.preparedEvidence.pack.sha256 !== input.artifactSha256
1614
2132
  || controlPlaneDigest(reference) !== input.referenceDigest
@@ -1701,6 +2219,9 @@ export class PluginControlPlaneService extends Service {
1701
2219
  const combined = AbortSignal.any([this.abort.signal, signal, AbortSignal.timeout(120_000)]);
1702
2220
  combined.throwIfAborted();
1703
2221
  const prepared = this.inspectPreparedRevision(planId), build = this.config.sourceBuild;
2222
+ if (prepared.protocol === 'dsh-prepared-revision/v3') {
2223
+ throw new Error('owner-instruction revisions do not use the task-failure Linux build binding');
2224
+ }
1704
2225
  const native = this.nativeSourceRunner, evidence = prepared.plan.preparedEvidence;
1705
2226
  if (!build || !('backend' in build) || build.backend !== 'linux-native' || !build.userNamespace || !build.systemSnapshot
1706
2227
  || native?.backend !== 'linux-native'
@@ -1788,10 +2309,34 @@ export class PluginControlPlaneService extends Service {
1788
2309
  this.abort.signal.throwIfAborted();
1789
2310
  const plan = this.store.getSourcePlan(planId);
1790
2311
  const reference = this.store.getOwnerTaskFailureReference(plan.gapId);
1791
- if (!reference || plan.mode !== 'prepared-revise' || plan.status !== 'pending-approval'
2312
+ if (plan.mode !== 'prepared-revise' || plan.status !== 'pending-approval'
1792
2313
  || Date.now() >= plan.expiresAt || !plan.sourceRevision || Date.now() >= plan.sourceRevision.grant.expiresAt) {
1793
2314
  throw new Error('prepared revision package is unavailable for independent verification');
1794
2315
  }
2316
+ if (!reference) {
2317
+ // Owner-instruction revisions have no task reference; the frozen
2318
+ // instruction is their whole provenance.
2319
+ const job = this.store.getPreparedRevisionJob(plan.id);
2320
+ const binding = job.intent.ownerInstruction;
2321
+ const stored = binding === undefined ? undefined : this.store.getOwnerInstruction(binding.instructionId);
2322
+ if (!binding || stored === undefined || controlPlaneDigest(stored) !== binding.digest
2323
+ || stored.expiresAt <= Date.now() || plan.sourceRevision.growthRun !== undefined
2324
+ || stored.planId !== plan.sourceRevision.parent.planId || stored.pluginName !== plan.name
2325
+ || job.intent.revision?.growthRun !== undefined) {
2326
+ throw new Error('prepared instruction revision is unavailable or changed');
2327
+ }
2328
+ this.assertInstructionRevisionOwner(stored.ownerReceipt, plan);
2329
+ return { protocol: 'dsh-prepared-revision/v3', plan: structuredClone(plan), job: structuredClone(job),
2330
+ source: { instruction: binding, owner: job.intent.owner,
2331
+ reviewModel: this.resolveInstructionReviewModel(stored, job.intent.ownerInstruction) },
2332
+ artifact: this.store.readPreparedSourceArtifact(plan.id) };
2333
+ }
2334
+ if (reference.schemaVersion === 2) {
2335
+ const run = plan.sourceRevision.growthRun;
2336
+ if (run?.protocol !== 'assistant-growth/source-run/v3')
2337
+ throw new Error('work item prepared revision run unavailable');
2338
+ return this.withWorkItemRevisionFrame(run, plan.gapId, reference.owner, false, () => ({ protocol: 'dsh-prepared-revision/v2', plan: structuredClone(this.store.getSourcePlan(plan.id)), job: structuredClone(this.store.getPreparedRevisionJob(plan.id)), reference: structuredClone(reference), source: structuredClone(this.workItemRevisionFrame.source), artifact: this.store.readPreparedSourceArtifact(plan.id) }));
2339
+ }
1795
2340
  return this.taskGaps.withCurrent(plan.gapId, reference.owner, () => {
1796
2341
  this.abort.signal.throwIfAborted();
1797
2342
  const job = this.store.getPreparedRevisionJob(plan.id);
@@ -1808,6 +2353,104 @@ export class PluginControlPlaneService extends Service {
1808
2353
  });
1809
2354
  };
1810
2355
  /** Host-only pre-author policy reference; never grants candidate or model authority. */
2356
+ assertInstructionRevisionOwner(owner, plan) {
2357
+ const config = this.config.sourceJobs;
2358
+ const revision = plan.sourceRevision;
2359
+ const delivery = this.ctx.get('assistantDelivery', false);
2360
+ if (!config?.revision || !revision || !delivery || config.expiresAt <= Date.now()
2361
+ || controlPlaneDigest(config.revision) !== controlPlaneDigest(revision.grant)) {
2362
+ throw new Error('instruction revision owner authority unavailable');
2363
+ }
2364
+ const current = delivery.validateOwnerRoute({ authorityId: config.ownerRouteId, principalId: config.principalId,
2365
+ workspace: config.workspace, agentPreset: config.preset });
2366
+ if (!isSourceOwnerContinuation(current, owner)
2367
+ || controlPlaneDigest(this.inspectRevisionParent(revision.parent.planId).parent) !== controlPlaneDigest(revision.parent)) {
2368
+ throw new Error('instruction revision owner or adopted parent changed');
2369
+ }
2370
+ }
2371
+ /**
2372
+ * Reads the review supplier frozen when the owner instruction was enqueued.
2373
+ * Missing or drifted freeze fails closed: the live owner route and live
2374
+ * verifier configuration are never substituted for the recorded values.
2375
+ */
2376
+ resolveInstructionReviewModel(stored, expected) {
2377
+ const freeze = this.store.getOwnerInstructionReviewFreeze(stored.instructionId);
2378
+ if (freeze === undefined)
2379
+ throw new Error('owner instruction has no frozen review supplier');
2380
+ if (expected !== undefined && (expected.reviewFreezeDigest === undefined || expected.reviewWindow === undefined
2381
+ || expected.reviewFreezeDigest !== freeze.digest
2382
+ || controlPlaneDigest(expected.reviewWindow) !== controlPlaneDigest(freeze.budget.window))) {
2383
+ throw new Error('owner instruction frozen review supplier or budget changed');
2384
+ }
2385
+ const live = this.ownerInstructionReviewBudget();
2386
+ if (controlPlaneDigest(freeze.budget.authority) !== controlPlaneDigest(live.authority)
2387
+ || freeze.budget.verifierKeyDigest !== live.verifierKeyDigest) {
2388
+ throw new Error('frozen review budget no longer matches the configured revision verifier');
2389
+ }
2390
+ if (freeze.budget.authority.expiresAt <= Date.now())
2391
+ throw new Error('frozen review authority expired');
2392
+ return { provider: freeze.supplier.provider, model: freeze.supplier.model,
2393
+ ...(freeze.supplier.reasoningEffort === undefined ? {} : { reasoningEffort: freeze.supplier.reasoningEffort }) };
2394
+ }
2395
+ /**
2396
+ * The independent-review budget identity configured for this Host, evaluated
2397
+ * once per use. The budget itself (maxOutputTokens/maxDurationMs/maxCases/
2398
+ * maxVerifications/runner/policy) is committed by
2399
+ * `RevisionAcceptanceAuthorityRef.authorityDigest`, so freezing this exact
2400
+ * authority ref plus the verifier key digest pins the review budget identity.
2401
+ * Narrowing the budget for one request (a per-plan token cap) is NOT possible
2402
+ * under the current review request protocol, which carries only the plan id
2403
+ * and lets the verifier compile its bounds once; that would require a
2404
+ * verifier request/response protocol v2. Do not emulate it by re-resolving
2405
+ * live configuration during recovery.
2406
+ */
2407
+ ownerInstructionReviewBudget() {
2408
+ const verification = this.config.revisionVerifications;
2409
+ if (verification === undefined)
2410
+ throw new Error('owner-instruction revision requires a configured independent review authority');
2411
+ const build = this.config.sourceBuild;
2412
+ const window = instructionReviewWindowFor({
2413
+ linuxBackend: build !== undefined && 'backend' in build && build.backend === 'linux-native',
2414
+ verification: true,
2415
+ regression: this.config.revisionRegressions !== undefined,
2416
+ adoption: this.config.revisionCapabilities !== undefined,
2417
+ });
2418
+ return Object.freeze({ protocol: 'plugin-control-plane/owner-instruction-review-budget/v1',
2419
+ authority: Object.freeze(structuredClone(verification.authority)),
2420
+ verifierKeyDigest: createHash('sha256').update(verification.publicKey).digest('hex'), window });
2421
+ }
2422
+ /** Live owner-route supplier resolution is allowed once, when the instruction is created. */
2423
+ resolveLiveInstructionReviewSupplier(receipt) {
2424
+ const delivery = this.ctx.get('assistantDelivery', false);
2425
+ const selection = delivery?.inspectOwnerModelSelection?.({ authorityId: receipt.authorityId,
2426
+ principalId: receipt.principalId, workspace: receipt.workspace,
2427
+ agentPreset: receipt.agentPreset });
2428
+ if (selection?.provider === undefined || selection.model === undefined) {
2429
+ throw new Error('owner-instruction revision requires a live owner model selection');
2430
+ }
2431
+ return { provider: selection.provider, model: selection.model,
2432
+ ...(selection.reasoningEffort === undefined ? {} : { reasoningEffort: selection.reasoningEffort }) };
2433
+ }
2434
+ /** Builds the write-once freeze for a first instruction write. */
2435
+ frozenInstructionReview(receipt) {
2436
+ return createOwnerInstructionReviewFreeze({ supplier: this.resolveLiveInstructionReviewSupplier(receipt),
2437
+ budget: this.ownerInstructionReviewBudget() });
2438
+ }
2439
+ /** Re-reads an existing freeze and fails closed if the review capability drifted. */
2440
+ reusedInstructionReview(stored) {
2441
+ const freeze = this.store.getOwnerInstructionReviewFreeze(stored.instructionId);
2442
+ if (freeze === undefined)
2443
+ throw new Error('owner instruction was recorded without a frozen review supplier; request a new revision');
2444
+ const live = this.ownerInstructionReviewBudget();
2445
+ if (controlPlaneDigest(freeze.budget.authority) !== controlPlaneDigest(live.authority)
2446
+ || freeze.budget.verifierKeyDigest !== live.verifierKeyDigest
2447
+ || controlPlaneDigest(freeze.budget.window) !== controlPlaneDigest(live.window)) {
2448
+ throw new Error('frozen review budget no longer matches the configured revision verifier');
2449
+ }
2450
+ if (freeze.budget.authority.expiresAt <= Date.now())
2451
+ throw new Error('frozen review authority expired');
2452
+ return freeze;
2453
+ }
1811
2454
  inspectSourceRevisionAcceptanceAuthority = () => {
1812
2455
  const policy = this.config.revisionVerifications?.authority;
1813
2456
  if (this.abort.signal.aborted || !policy || Date.now() >= policy.expiresAt
@@ -1834,7 +2477,7 @@ export class PluginControlPlaneService extends Service {
1834
2477
  const policy = this.inspectSourceRevisionRegressionAcceptanceAuthority();
1835
2478
  const run = prepared.plan.sourceRevision.growthRun;
1836
2479
  const source = this.store.readRevisionSource(planId);
1837
- if (!certificate || !source || !policy || !run.revisionRegressionAcceptance
2480
+ if (!certificate || !source || !policy || !run || !run.revisionRegressionAcceptance
1838
2481
  || controlPlaneDigest(run.revisionRegressionAcceptance) !== controlPlaneDigest(policy)) {
1839
2482
  throw new Error('regression requires fresh independently verified candidate and pre-author policy');
1840
2483
  }
@@ -1848,6 +2491,9 @@ export class PluginControlPlaneService extends Service {
1848
2491
  throw new Error('regression parent immutable artifact unavailable or changed');
1849
2492
  }
1850
2493
  this.inspectRevisionParent(parent.parent.planId);
2494
+ if (prepared.protocol === 'dsh-prepared-revision/v3') {
2495
+ throw new Error('owner-instruction revisions carry no regression acceptance');
2496
+ }
1851
2497
  return { protocol: 'dsh-prepared-revision-regression/v1',
1852
2498
  plan: prepared.plan, job: prepared.job, reference: prepared.reference,
1853
2499
  candidate: { certificate, artifact: Buffer.from(prepared.artifact), sourceDigest: source.digest },
@@ -1868,6 +2514,7 @@ export class PluginControlPlaneService extends Service {
1868
2514
  || controlPlaneDigest(snapshot.candidate.certificate) !== input.candidateVerificationDigest
1869
2515
  || source.digest !== input.sourceDigest || snapshot.candidate.sourceDigest !== input.sourceDigest
1870
2516
  || controlPlaneDigest(policy) !== input.regressionAuthorityDigest
2517
+ || snapshot.plan.sourceRevision.growthRun?.revisionRegressionAcceptance === undefined
1871
2518
  || controlPlaneDigest(snapshot.plan.sourceRevision.growthRun.revisionRegressionAcceptance) !== input.regressionAuthorityDigest
1872
2519
  || controlPlaneDigest(snapshot.parent.binding) !== input.parentDigest) {
1873
2520
  throw new Error('regression parent, candidate or policy changed');
@@ -1879,6 +2526,9 @@ export class PluginControlPlaneService extends Service {
1879
2526
  const certificate = this.store.getRevisionRegression(planId), policy = this.config.revisionRegressions;
1880
2527
  if (!certificate || !policy || !verifyPluginRevisionRegressionCertificate(certificate, policy.authority, policy.publicKey))
1881
2528
  return undefined;
2529
+ // Instruction revisions carry no regression acceptance and never enter this corridor.
2530
+ if (!isPluginRevisionTaskSource(certificate.source))
2531
+ return undefined;
1882
2532
  const prepared = this.inspectPreparedRevisionRegression(planId);
1883
2533
  this.withPreparedRevisionRegressionFence({ planId, planDigest: certificate.plan.digest, artifactSha256: certificate.plan.artifactSha256,
1884
2534
  growthRunDigest: certificate.source.growthRunDigest, referenceDigest: certificate.source.referenceDigest,
@@ -1902,6 +2552,17 @@ export class PluginControlPlaneService extends Service {
1902
2552
  const reference = this.store.getOwnerTaskFailureReference(plan.gapId);
1903
2553
  if (!reference)
1904
2554
  throw new Error('revision verification source unavailable');
2555
+ if (reference.schemaVersion === 2) {
2556
+ const run = plan.sourceRevision?.growthRun;
2557
+ if (run?.protocol !== 'assistant-growth/source-run/v3')
2558
+ throw new Error('work item revision verification run unavailable');
2559
+ return this.withWorkItemRevisionFrame(run, plan.gapId, reference.owner, false, () => {
2560
+ const current = this.store.getSourcePlan(input.planId), job = this.store.getPreparedRevisionJob(input.planId);
2561
+ if (current.digest !== input.planDigest || current.preparedEvidence?.pack.sha256 !== input.artifactSha256 || controlPlaneDigest(reference) !== input.referenceDigest || sourceGrowthRunDigest(run) !== input.growthRunDigest || !job.intent.revision?.growthRun || sourceGrowthRunDigest(job.intent.revision.growthRun) !== input.growthRunDigest || controlPlaneDigest(current.sourceRevision.parent) !== input.parentDigest || createHash('sha256').update(this.store.readPreparedSourceArtifact(input.planId)).digest('hex') !== input.artifactSha256)
2562
+ throw new Error('work item prepared revision exact binding changed');
2563
+ return callback();
2564
+ });
2565
+ }
1905
2566
  return this.taskGaps.withCurrent(plan.gapId, reference.owner, () => {
1906
2567
  this.abort.signal.throwIfAborted();
1907
2568
  const current = this.store.getSourcePlan(input.planId);
@@ -1927,15 +2588,57 @@ export class PluginControlPlaneService extends Service {
1927
2588
  return callback();
1928
2589
  });
1929
2590
  };
2591
+ /**
2592
+ * Exact-source fence for owner-instruction revisions: the frozen instruction
2593
+ * row replaces the task reference and growth run, and every comparison is
2594
+ * replayed against durable state inside the Evaluation writer transaction.
2595
+ */
2596
+ withPreparedInstructionRevisionFence = (input, callback) => {
2597
+ this.abort.signal.throwIfAborted();
2598
+ const policy = this.inspectSourceRevisionAcceptanceAuthority();
2599
+ if (!policy)
2600
+ throw new Error('revision verification authority unavailable');
2601
+ const plan = this.store.getSourcePlan(input.planId);
2602
+ if (this.store.getOwnerTaskFailureReference(plan.gapId) !== undefined) {
2603
+ throw new Error('instruction revision fence cannot ride a task failure');
2604
+ }
2605
+ const job = this.store.getPreparedRevisionJob(input.planId);
2606
+ const instruction = job.intent.ownerInstruction;
2607
+ const current = this.store.getSourcePlan(input.planId);
2608
+ const stored = instruction === undefined ? undefined : this.store.getOwnerInstruction(instruction.instructionId);
2609
+ if (!instruction || stored === undefined || controlPlaneDigest(stored) !== instruction.digest
2610
+ || instruction.digest !== input.instructionDigest
2611
+ || current.digest !== input.planDigest || current.mode !== 'prepared-revise' || !current.sourceCheck || !current.preparedEvidence
2612
+ || current.preparedEvidence.pack.sha256 !== input.artifactSha256
2613
+ || current.sourceRevision?.growthRun !== undefined || job.intent.revision?.growthRun !== undefined
2614
+ || controlPlaneDigest(current.sourceRevision?.parent) !== input.parentDigest
2615
+ || controlPlaneDigest(current.sourceRevision?.grant) !== controlPlaneDigest(this.config.sourceJobs?.revision)) {
2616
+ throw new Error('instruction revision exact source binding changed');
2617
+ }
2618
+ if (stored.expiresAt <= Date.now())
2619
+ throw new Error('owner instruction expired');
2620
+ this.assertInstructionRevisionOwner(stored.ownerReceipt, current);
2621
+ const artifact = this.store.readPreparedSourceArtifact(input.planId);
2622
+ if (createHash('sha256').update(artifact).digest('hex') !== input.artifactSha256) {
2623
+ throw new Error('instruction revision artifact changed');
2624
+ }
2625
+ return callback();
2626
+ };
1930
2627
  /** Independent reviewer reads the actual checked tree through a disposable Git index. */
1931
2628
  inspectPreparedRevisionReviewContext = async (planId, signal) => {
1932
2629
  const combined = AbortSignal.any([this.abort.signal, signal, AbortSignal.timeout(120_000)]);
1933
2630
  combined.throwIfAborted();
1934
2631
  const prepared = this.inspectPreparedRevision(planId);
1935
- const current = () => this.withPreparedRevisionFence({ planId, planDigest: prepared.plan.digest,
1936
- artifactSha256: prepared.plan.preparedEvidence.pack.sha256,
1937
- growthRunDigest: sourceGrowthRunDigest(prepared.plan.sourceRevision.growthRun),
1938
- referenceDigest: controlPlaneDigest(prepared.reference), parentDigest: controlPlaneDigest(prepared.plan.sourceRevision.parent) }, () => undefined);
2632
+ const assertCurrent = prepared.protocol === 'dsh-prepared-revision/v3'
2633
+ ? () => this.withPreparedInstructionRevisionFence({ planId, planDigest: prepared.plan.digest,
2634
+ artifactSha256: prepared.plan.preparedEvidence.pack.sha256,
2635
+ instructionDigest: prepared.source.instruction.digest,
2636
+ parentDigest: controlPlaneDigest(prepared.plan.sourceRevision.parent) }, () => undefined)
2637
+ : () => this.withPreparedRevisionFence({ planId, planDigest: prepared.plan.digest,
2638
+ artifactSha256: prepared.plan.preparedEvidence.pack.sha256,
2639
+ growthRunDigest: sourceGrowthRunDigest(prepared.plan.sourceRevision.growthRun),
2640
+ referenceDigest: controlPlaneDigest(prepared.reference), parentDigest: controlPlaneDigest(prepared.plan.sourceRevision.parent) }, () => undefined);
2641
+ const current = assertCurrent;
1939
2642
  current();
1940
2643
  const trust = await this.boundTrust();
1941
2644
  const environment = inheritedEnvironment(trust);
@@ -1956,6 +2659,21 @@ export class PluginControlPlaneService extends Service {
1956
2659
  const config = this.config.revisionVerifications;
1957
2660
  if (!config || !verifyPluginRevisionVerificationCertificate(certificate, config.authority, config.publicKey))
1958
2661
  return undefined;
2662
+ if (!isPluginRevisionTaskSource(certificate.source)) {
2663
+ this.withPreparedInstructionRevisionFence({ planId, planDigest: certificate.plan.digest,
2664
+ artifactSha256: certificate.plan.artifactSha256,
2665
+ instructionDigest: certificate.source.instructionDigest, parentDigest: controlPlaneDigest(certificate.parent) }, () => {
2666
+ const plan = this.store.getSourcePlan(planId);
2667
+ if (!plan.sourceCheck || !plan.preparedEvidence || certificate.plan.name !== plan.name
2668
+ || certificate.plan.generatorDigest !== plan.generatorDigest
2669
+ || certificate.plan.sourceTreeDigest !== plan.sourceCheck.treeDigest
2670
+ || certificate.plan.sourcePatchDigest !== plan.sourceCheck.patchDigest
2671
+ || certificate.plan.artifactBytes !== plan.preparedEvidence.pack.sizeBytes) {
2672
+ throw new Error('revision verification exact plan evidence changed');
2673
+ }
2674
+ });
2675
+ return structuredClone(certificate);
2676
+ }
1959
2677
  this.withPreparedRevisionFence({ planId, planDigest: certificate.plan.digest,
1960
2678
  artifactSha256: certificate.plan.artifactSha256, growthRunDigest: certificate.source.growthRunDigest,
1961
2679
  referenceDigest: certificate.source.referenceDigest, parentDigest: controlPlaneDigest(certificate.parent) }, () => {
@@ -1975,8 +2693,17 @@ export class PluginControlPlaneService extends Service {
1975
2693
  this.abort.signal.throwIfAborted();
1976
2694
  const plan = this.store.getSourcePlan(planId);
1977
2695
  const reference = this.store.getOwnerTaskFailureReference(plan.gapId);
1978
- if (!reference || plan.mode !== 'prepared-revise')
2696
+ if (plan.mode !== 'prepared-revise')
1979
2697
  throw new Error('revision verification owner source unavailable');
2698
+ if (!reference) {
2699
+ const job = this.store.getPreparedRevisionJob(planId);
2700
+ const instruction = job.intent.ownerInstruction;
2701
+ if (!instruction || !plan.preparedEvidence || !plan.sourceRevision)
2702
+ throw new Error('revision verification owner source unavailable');
2703
+ return this.withPreparedInstructionRevisionFence({ planId, planDigest: plan.digest,
2704
+ artifactSha256: plan.preparedEvidence.pack.sha256, instructionDigest: instruction.digest,
2705
+ parentDigest: controlPlaneDigest(plan.sourceRevision.parent) }, () => this.store.inspectRevisionVerificationRecord(planId));
2706
+ }
1980
2707
  return this.taskGaps.withCurrent(plan.gapId, reference.owner, () => {
1981
2708
  const current = this.store.getSourcePlan(planId);
1982
2709
  if (current.mode !== 'prepared-revise' || current.digest !== plan.digest) {
@@ -2005,6 +2732,8 @@ export class PluginControlPlaneService extends Service {
2005
2732
  const { plan, reference, job } = this.store.getRetainedPreparedRevision(planId);
2006
2733
  const certificate = this.store.getRevisionVerification(planId);
2007
2734
  if (!certificate || !plan.sourceRevision || !isSourceOwnerContinuation(before, reference.owner)
2735
+ || !isPluginRevisionTaskSource(certificate.source)
2736
+ || plan.sourceRevision.growthRun === undefined
2008
2737
  || !verifyPluginRevisionVerificationCertificate(certificate, policy.authority, policy.publicKey, certificate.verifiedAt)
2009
2738
  || certificate.plan.digest !== plan.digest || certificate.source.referenceDigest !== controlPlaneDigest(reference)
2010
2739
  || certificate.source.ownerDigest !== job.intent.ownerDigest
@@ -2050,6 +2779,104 @@ export class PluginControlPlaneService extends Service {
2050
2779
  throw new Error('plugin-control-plane: durable source jobs unavailable');
2051
2780
  return this.sourceRuntime.inspect(input);
2052
2781
  };
2782
+ async drainOwnerInstructionRequests(signal) {
2783
+ await drainPendingOwnerInstructionRequests({
2784
+ store: this.store,
2785
+ now: Date.now(),
2786
+ readNow: Date.now,
2787
+ signal,
2788
+ maxPerPass: 1,
2789
+ admit: (request, assertCurrent) => this.requestOwnerInstructionRevision({ planId: request.planId, pluginName: request.pluginName,
2790
+ files: request.files, ttlMs: request.ttlMs, idempotencyKey: request.idempotencyKey,
2791
+ expectedBaseCommit: request.expectedBaseCommit, window: { createdAt: request.createdAt, expiresAt: request.expiresAt },
2792
+ signal, assertCurrent }),
2793
+ });
2794
+ }
2795
+ /**
2796
+ * Host-only owner instruction entry. Freezes one authenticated owner
2797
+ * receipt, the adopted revision parent and the exact submitted files into an
2798
+ * expiring instruction row, then enqueues one `revise-created` source job
2799
+ * against the synthetic instruction gap. The model tool surface never
2800
+ * reaches this method; without a valid instruction the admission still
2801
+ * requires an owner task failure.
2802
+ */
2803
+ requestOwnerInstructionRevision = (input) => {
2804
+ const assertCurrent = () => {
2805
+ this.abort.signal.throwIfAborted();
2806
+ input.signal?.throwIfAborted();
2807
+ input.assertCurrent?.();
2808
+ };
2809
+ assertCurrent();
2810
+ const config = this.config.sourceJobs;
2811
+ const delivery = this.ctx.get('assistantDelivery', false);
2812
+ if (config === undefined || config.revision === undefined || delivery === undefined) {
2813
+ throw new Error('plugin-control-plane: owner instruction revisions require source jobs, a revision grant and a live owner route');
2814
+ }
2815
+ if (config.revision.expiresAt <= Date.now())
2816
+ throw new Error('plugin-control-plane: the source revision grant is expired');
2817
+ if (!Number.isSafeInteger(input.ttlMs) || input.ttlMs < 900_000 || input.ttlMs > 86_400_000) {
2818
+ throw new Error('plugin-control-plane: owner instruction ttl is invalid');
2819
+ }
2820
+ if (!/^[A-Za-z0-9][A-Za-z0-9._:-]{0,159}$/u.test(input.idempotencyKey)) {
2821
+ throw new Error('plugin-control-plane: owner instruction idempotency key is invalid');
2822
+ }
2823
+ validateSourceCreationFiles(input.files);
2824
+ const receipt = delivery.validateOwnerRoute({ authorityId: config.ownerRouteId,
2825
+ principalId: config.principalId, workspace: config.workspace, agentPreset: config.preset });
2826
+ const parent = this.inspectRevisionParent(input.planId);
2827
+ if (parent.name !== input.pluginName)
2828
+ throw new Error('plugin-control-plane: owner instruction target does not match the adopted parent');
2829
+ const instructionId = `instruction-${createHash('sha256').update(config.authorityId).update('\0')
2830
+ .update(input.idempotencyKey).digest('hex').slice(0, 32)}`;
2831
+ const prior = this.store.getOwnerInstruction(instructionId);
2832
+ const now = Date.now();
2833
+ const createdAt = input.window?.createdAt ?? prior?.createdAt ?? now;
2834
+ const expiresAt = input.window?.expiresAt ?? prior?.expiresAt ?? createdAt + input.ttlMs;
2835
+ if (!Number.isSafeInteger(createdAt) || !Number.isSafeInteger(expiresAt)
2836
+ || expiresAt - createdAt !== input.ttlMs || expiresAt <= now) {
2837
+ throw new Error('plugin-control-plane: owner instruction request expired or invalid');
2838
+ }
2839
+ if (prior && !isSourceOwnerContinuation(receipt, prior.ownerReceipt))
2840
+ throw new Error('plugin-control-plane: owner route changed');
2841
+ // Resolve the independent review supplier and budget exactly once. An
2842
+ // existing instruction reuses its write-once freeze; a changed owner model
2843
+ // selection or changed verifier identity can never silently replace it.
2844
+ const review = prior === undefined ? this.frozenInstructionReview(receipt) : this.reusedInstructionReview(prior);
2845
+ const gap = this.store.recordGap({
2846
+ idempotencyKey: `owner-instruction:${instructionId}`, capability: `plugin-revision:${input.pluginName}`,
2847
+ context: 'Owner instruction revision request', expectedValue: 1, frequency: 1, estimatedCost: 1, risk: 0,
2848
+ });
2849
+ const instruction = this.store.recordOwnerInstruction({
2850
+ instructionId, ownerReceipt: prior?.ownerReceipt ?? receipt, ownerRouteId: config.ownerRouteId, planId: input.planId,
2851
+ pluginName: input.pluginName, filesDigest: controlPlaneDigest(input.files),
2852
+ createdAt, expiresAt, idempotencyKey: input.idempotencyKey, review,
2853
+ });
2854
+ const signal = AbortSignal.any([this.abort.signal, AbortSignal.timeout(Math.max(1, expiresAt - Date.now())),
2855
+ ...(input.signal === undefined ? [] : [input.signal])]);
2856
+ return this.enqueueSourceJob({
2857
+ gapId: gap.id, mode: 'revise-created', name: input.pluginName, files: input.files,
2858
+ repository: config.repository, parentPlanId: input.planId,
2859
+ ownerInstruction: { instructionId: instruction.instructionId,
2860
+ digest: controlPlaneDigest(instruction) },
2861
+ expectedBaseCommit: input.expectedBaseCommit, ttlMs: input.ttlMs,
2862
+ idempotencyKey: input.idempotencyKey,
2863
+ owner: { ownerRouteId: receipt.authorityId, principalId: receipt.principalId,
2864
+ principalRecordId: receipt.principalRecordId, principalVersion: receipt.principalVersion,
2865
+ workspace: receipt.workspace, preset: receipt.agentPreset },
2866
+ signal, assertCurrent,
2867
+ }).then(job => {
2868
+ const persisted = this.store.getSourceJob(job.id);
2869
+ // A queued result from enqueue has passed native activation. For an
2870
+ // already settled job, the bound definition alone only proves the
2871
+ // paused registration existed. A durable occurrence proves dispatch.
2872
+ if (persisted?.intent.ownerInstruction?.instructionId !== instruction.instructionId
2873
+ || persisted.intent.ownerInstruction.digest !== controlPlaneDigest(instruction)
2874
+ || !persisted.definitionHash || (persisted.status !== 'queued' && persisted.occurrenceId === undefined)) {
2875
+ throw new Error('plugin-control-plane: source-job-registration-rejected');
2876
+ }
2877
+ return { instructionId: instruction.instructionId, jobId: job.id };
2878
+ });
2879
+ };
2053
2880
  reconcileSourceJob = (input) => {
2054
2881
  if (this.sourceRuntime === undefined || this.sourceBuilds.size !== 0)
2055
2882
  throw new Error('plugin-control-plane: durable source jobs unavailable or still draining');
@@ -2131,19 +2958,42 @@ export class PluginControlPlaneService extends Service {
2131
2958
  if (!job.planId || !this.config.revisionVerifications || this.store.getRevisionVerificationStatus(job.planId) !== undefined)
2132
2959
  return;
2133
2960
  const prepared = this.inspectPreparedRevision(job.planId);
2134
- if (prepared.job.id !== job.id || !prepared.plan.sourceRevision?.growthRun || !prepared.plan.preparedEvidence) {
2961
+ if (prepared.job.id !== job.id || !prepared.plan.preparedEvidence) {
2135
2962
  throw new Error('revision verification job is not the exact prepared source');
2136
2963
  }
2964
+ if (prepared.protocol === 'dsh-prepared-revision/v3') {
2965
+ // Owner-instruction revisions claim through their instruction binding.
2966
+ const instruction = prepared.source.instruction;
2967
+ this.withPreparedInstructionRevisionFence({ planId: prepared.plan.id, planDigest: prepared.plan.digest,
2968
+ artifactSha256: prepared.plan.preparedEvidence.pack.sha256,
2969
+ instructionDigest: instruction.digest, parentDigest: controlPlaneDigest(prepared.plan.sourceRevision.parent) }, () => this.store.claimRevisionVerification(job.planId));
2970
+ return this.runPreparedRevisionVerifier(job, prepared, signal, verifier, { planId: prepared.plan.id, planDigest: prepared.plan.digest,
2971
+ artifactSha256: prepared.plan.preparedEvidence.pack.sha256,
2972
+ instructionDigest: instruction.digest, parentDigest: controlPlaneDigest(prepared.plan.sourceRevision.parent),
2973
+ ownerDigest: controlPlaneDigest(prepared.source.owner) });
2974
+ }
2975
+ if (!prepared.plan.sourceRevision?.growthRun)
2976
+ throw new Error('prepared revision lacks a frozen source growth run');
2137
2977
  const binding = { planId: prepared.plan.id, planDigest: prepared.plan.digest,
2138
2978
  artifactSha256: prepared.plan.preparedEvidence.pack.sha256,
2139
2979
  growthRunDigest: sourceGrowthRunDigest(prepared.plan.sourceRevision.growthRun),
2140
2980
  referenceDigest: controlPlaneDigest(prepared.reference), parentDigest: controlPlaneDigest(prepared.plan.sourceRevision.parent) };
2141
2981
  this.withPreparedRevisionFence(binding, () => this.store.claimRevisionVerification(job.planId));
2982
+ return this.runPreparedRevisionVerifier(job, prepared, signal, verifier, binding);
2983
+ }
2984
+ /** Runs the independent reviewer and settles the claimed row inside the caller's exact-source fence. */
2985
+ async runPreparedRevisionVerifier(job, prepared, signal, verifier, fenceInput) {
2986
+ const inFence = (callback) => 'growthRunDigest' in fenceInput
2987
+ ? this.withPreparedRevisionFence(fenceInput, callback)
2988
+ : this.withPreparedInstructionRevisionFence(fenceInput, callback);
2989
+ const planId = job.planId;
2990
+ if (!planId)
2991
+ throw new Error('revision verification job has no plan');
2142
2992
  try {
2143
2993
  const current = verifier();
2144
2994
  if (typeof current?.verifyPluginRevision !== 'function')
2145
2995
  throw new Error('independent revision verifier unavailable');
2146
- const result = await current.verifyPluginRevision({ protocol: 'assistant-growth/revision-verification-request/v1', planId: job.planId }, signal);
2996
+ const result = await current.verifyPluginRevision({ protocol: 'assistant-growth/revision-verification-request/v1', planId }, signal);
2147
2997
  signal.throwIfAborted();
2148
2998
  if (result.status === 'verified' && !verifyPluginRevisionVerificationCertificate(result.certificate, this.config.revisionVerifications.authority, this.config.revisionVerifications.publicKey)) {
2149
2999
  throw new Error('revision verification certificate signature or policy invalid');
@@ -2154,7 +3004,7 @@ export class PluginControlPlaneService extends Service {
2154
3004
  const source = result.status === 'verified' ? await captureCreationCapabilitySource({ worktree: prepared.plan.worktree,
2155
3005
  baseCommit: prepared.plan.baseCommit, scope: [...prepared.plan.scope].sort(), certificate: result.certificate,
2156
3006
  environment: inheritedEnvironment(await this.boundTrust()), signal }) : undefined;
2157
- this.withPreparedRevisionFence(binding, () => {
3007
+ inFence(() => {
2158
3008
  if (result.status === 'verified') {
2159
3009
  if (!source)
2160
3010
  throw new Error('verified revision source archive unavailable');
@@ -2169,7 +3019,7 @@ export class PluginControlPlaneService extends Service {
2169
3019
  // The claimed row is a durable no-replay fence even if the process dies
2170
3020
  // before settlement. A later native tick cannot dispatch another review.
2171
3021
  try {
2172
- this.withPreparedRevisionFence(binding, () => {
3022
+ inFence(() => {
2173
3023
  if (this.store.getRevisionVerificationStatus(job.planId) === 'claimed') {
2174
3024
  this.store.settleRevisionVerification(job.planId, 'unknown', 'independent-verification-unsettled');
2175
3025
  }
@@ -2187,11 +3037,17 @@ export class PluginControlPlaneService extends Service {
2187
3037
  if (prepared.job.id !== job.id)
2188
3038
  throw new Error('regression job is not exact prepared revision');
2189
3039
  const certificate = prepared.candidate.certificate;
3040
+ const acceptance = prepared.plan.sourceRevision?.growthRun?.revisionRegressionAcceptance;
3041
+ if (acceptance === undefined)
3042
+ throw new Error('owner-instruction revisions carry no regression acceptance');
3043
+ if (!isPluginRevisionTaskSource(certificate.source)) {
3044
+ throw new Error('owner-instruction revisions carry no regression acceptance');
3045
+ }
2190
3046
  const binding = { planId: prepared.plan.id, planDigest: prepared.plan.digest, artifactSha256: certificate.plan.artifactSha256,
2191
3047
  growthRunDigest: certificate.source.growthRunDigest, referenceDigest: certificate.source.referenceDigest,
2192
3048
  parentDigest: controlPlaneDigest(certificate.parent), candidateVerificationDigest: controlPlaneDigest(certificate),
2193
3049
  sourceDigest: prepared.candidate.sourceDigest,
2194
- regressionAuthorityDigest: controlPlaneDigest(prepared.plan.sourceRevision.growthRun.revisionRegressionAcceptance) };
3050
+ regressionAuthorityDigest: controlPlaneDigest(acceptance) };
2195
3051
  this.withPreparedRevisionRegressionFence(binding, () => this.store.claimRevisionRegression(job.planId));
2196
3052
  try {
2197
3053
  const current = verifier();
@@ -2438,8 +3294,7 @@ export class PluginControlPlaneService extends Service {
2438
3294
  throw new ControlPlaneCliError('INVALID_ARGUMENT', 'ttlMs must be an integer within 900000..86400000');
2439
3295
  }
2440
3296
  const buildConfig = this.config.sourceBuild;
2441
- const repositoryBuild = buildConfig !== undefined && ('backend' in buildConfig
2442
- ? buildConfig.backend === 'linux-native' : ['repository', 'portable-repository'].includes(buildConfig.profile ?? ''));
3297
+ const repositoryBuild = buildConfig !== undefined && 'backend' in buildConfig && buildConfig.backend === 'linux-native';
2443
3298
  const maximumTimeoutMs = repositoryBuild ? 1_800_000 : 240_000;
2444
3299
  const timeoutMs = input.timeoutMs ?? 180_000;
2445
3300
  if (!Number.isSafeInteger(timeoutMs) || timeoutMs < 60_000 || timeoutMs > maximumTimeoutMs) {
@@ -2484,31 +3339,26 @@ export class PluginControlPlaneService extends Service {
2484
3339
  }
2485
3340
  // Only owner-bound creation jobs retain the exact package for the
2486
3341
  // independent verifier. Ordinary source tools never receive these bytes.
2487
- const capturePack = (creating || revising) && this.store.getOwnerTaskFailureReference(input.gapId) !== undefined;
3342
+ const capturePack = (creating || revising) && (this.store.getOwnerTaskFailureReference(input.gapId) !== undefined
3343
+ || sourceJob?.intent.ownerInstruction !== undefined);
2488
3344
  const common = { worktree: isolated.worktree, baseCommit, name, scope: generated?.scope ?? [`plugins/${name}`],
2489
3345
  environment, signal, assertCurrent, preparedAt: Date.now(), ...(creation === undefined ? {} : { creation }),
2490
3346
  ...(revision === undefined ? {} : { revision: { binding: revision, parentSource: parent.source,
2491
3347
  parentCertificate: parent.certificate, files: input.files } }), ...(capturePack ? { capturePack: true } : {}) };
2492
- const checked = 'backend' in configured
2493
- ? await (async () => {
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');
2506
- }
2507
- return runNativePreparedChecks({ ...common, config: { ...configured, timeoutMs: Math.min(timeoutMs, configured.timeoutMs) },
2508
- runner: native.runner, sourceJob: { id: sourceJob.id } });
2509
- })()
2510
- : await runDockerPreparedChecks({ ...common, config: { ...configured, timeoutMs: Math.min(timeoutMs, configured.timeoutMs) },
2511
- ...(sourceJob === undefined ? {} : { sourceJob: { id: sourceJob.id, containerName: sourceJob.intent.containerName } }) });
3348
+ if (!('backend' in configured))
3349
+ throw new ControlPlaneCliError('SOURCE_BOUNDARY', 'source build requires a native backend');
3350
+ const native = this.nativeSourceRunner;
3351
+ if (sourceJob === undefined || native === undefined || native.backend !== configured.backend
3352
+ || sourceJob.intent.nativeResource?.backend !== configured.backend || sourceJob.intent.nativeResource.key !== sourceJob.id) {
3353
+ throw new ControlPlaneCliError('SOURCE_BOUNDARY', 'native source preparation requires its bound durable source job');
3354
+ }
3355
+ const checked = configured.backend === 'linux-native' && native.backend === 'linux-native'
3356
+ ? await (linuxBuildInvoked = true, runLinuxPreparedChecks({ ...common, config: { ...configured, timeoutMs: Math.min(timeoutMs, configured.timeoutMs) },
3357
+ runner: native.runner, sourceJob: { id: sourceJob.id } }))
3358
+ : configured.backend === 'macos-seatbelt' && native.backend === 'macos-seatbelt' && (creating || revising)
3359
+ ? await runNativePreparedChecks({ ...common, config: { ...configured, timeoutMs: Math.min(timeoutMs, configured.timeoutMs) },
3360
+ runner: native.runner, sourceJob: { id: sourceJob.id } })
3361
+ : (() => { throw new ControlPlaneCliError('SOURCE_BOUNDARY', 'native source preparation backend is unavailable'); })();
2512
3362
  await assertCurrent();
2513
3363
  if (capturePack && checked.packArtifact === undefined)
2514
3364
  throw new ControlPlaneCliError('SOURCE_BOUNDARY', 'prepared creation artifact was not captured');
@@ -2535,6 +3385,11 @@ export class PluginControlPlaneService extends Service {
2535
3385
  throw new Error('work item creation owner unavailable');
2536
3386
  return this.withWorkItemGrowthFrame(creation.growthRun, input.gapId, gapOwner, false, callback);
2537
3387
  }
3388
+ if (revision?.growthRun?.protocol === 'assistant-growth/source-run/v3') {
3389
+ if (!gapOwner || !('receiptVersion' in gapOwner))
3390
+ throw new Error('work item revision owner unavailable');
3391
+ return this.withWorkItemRevisionFrame(revision.growthRun, input.gapId, gapOwner, false, callback);
3392
+ }
2538
3393
  return this.taskGaps.withCurrent(input.gapId, gapOwner, callback);
2539
3394
  };
2540
3395
  return commitSource(() => this.store.createSourcePlan({ gapId: input.gapId, repository, worktree: isolated.worktree, baseCommit,