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

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 (86) hide show
  1. package/README.md +54 -20
  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-feedback.d.ts +16 -1
  6. package/lib/creation-capability-feedback.d.ts.map +1 -1
  7. package/lib/creation-capability-feedback.js +82 -5
  8. package/lib/creation-capability-feedback.js.map +1 -1
  9. package/lib/creation-capability-journal.d.ts.map +1 -1
  10. package/lib/creation-capability-journal.js +2 -0
  11. package/lib/creation-capability-journal.js.map +1 -1
  12. package/lib/creation-capability-runtime.d.ts.map +1 -1
  13. package/lib/creation-capability-runtime.js +2 -0
  14. package/lib/creation-capability-runtime.js.map +1 -1
  15. package/lib/creation-capability-types.d.ts +1 -0
  16. package/lib/creation-capability-types.d.ts.map +1 -1
  17. package/lib/linux-source-build.d.ts +13 -2
  18. package/lib/linux-source-build.d.ts.map +1 -1
  19. package/lib/linux-source-build.js.map +1 -1
  20. package/lib/live-qualification-authority.js +2 -2
  21. package/lib/live-qualification-authority.js.map +1 -1
  22. package/lib/owner-instruction-drain.d.ts +60 -0
  23. package/lib/owner-instruction-drain.d.ts.map +1 -0
  24. package/lib/owner-instruction-drain.js +101 -0
  25. package/lib/owner-instruction-drain.js.map +1 -0
  26. package/lib/revision-capability-feedback.d.ts +165 -0
  27. package/lib/revision-capability-feedback.d.ts.map +1 -0
  28. package/lib/revision-capability-feedback.js +316 -0
  29. package/lib/revision-capability-feedback.js.map +1 -0
  30. package/lib/revision-capability-journal.d.ts +23 -0
  31. package/lib/revision-capability-journal.d.ts.map +1 -1
  32. package/lib/revision-capability-journal.js +124 -15
  33. package/lib/revision-capability-journal.js.map +1 -1
  34. package/lib/revision-capability-runtime.d.ts +19 -1
  35. package/lib/revision-capability-runtime.d.ts.map +1 -1
  36. package/lib/revision-capability-runtime.js +80 -7
  37. package/lib/revision-capability-runtime.js.map +1 -1
  38. package/lib/revision-capability-types.d.ts +50 -4
  39. package/lib/revision-capability-types.d.ts.map +1 -1
  40. package/lib/service.d.ts +174 -3
  41. package/lib/service.d.ts.map +1 -1
  42. package/lib/service.js +732 -68
  43. package/lib/service.js.map +1 -1
  44. package/lib/source-adoption-authority.js +2 -2
  45. package/lib/source-adoption-authority.js.map +1 -1
  46. package/lib/source-approval-authority.js +3 -3
  47. package/lib/source-approval-authority.js.map +1 -1
  48. package/lib/source-build.d.ts +33 -53
  49. package/lib/source-build.d.ts.map +1 -1
  50. package/lib/source-build.js +28 -408
  51. package/lib/source-build.js.map +1 -1
  52. package/lib/source-job-types.d.ts +111 -4
  53. package/lib/source-job-types.d.ts.map +1 -1
  54. package/lib/source-jobs.d.ts +68 -4
  55. package/lib/source-jobs.d.ts.map +1 -1
  56. package/lib/source-jobs.js +296 -46
  57. package/lib/source-jobs.js.map +1 -1
  58. package/lib/source-release-authority.js +2 -2
  59. package/lib/source-release-authority.js.map +1 -1
  60. package/lib/source-revision.d.ts +29 -3
  61. package/lib/source-revision.d.ts.map +1 -1
  62. package/lib/source-revision.js +40 -9
  63. package/lib/source-revision.js.map +1 -1
  64. package/lib/source-workspace.d.ts +11 -4
  65. package/lib/source-workspace.d.ts.map +1 -1
  66. package/lib/source-workspace.js +51 -17
  67. package/lib/source-workspace.js.map +1 -1
  68. package/lib/sqlite.d.ts +6 -7
  69. package/lib/sqlite.d.ts.map +1 -1
  70. package/lib/sqlite.js +255 -17
  71. package/lib/sqlite.js.map +1 -1
  72. package/lib/store.d.ts +142 -3
  73. package/lib/store.d.ts.map +1 -1
  74. package/lib/store.js +732 -63
  75. package/lib/store.js.map +1 -1
  76. package/lib/systemd-host-authority.js +4 -4
  77. package/lib/systemd-host-authority.js.map +1 -1
  78. package/lib/task-observation-authority.js +2 -2
  79. package/lib/task-observation-authority.js.map +1 -1
  80. package/lib/version.d.ts +1 -1
  81. package/lib/version.js +1 -1
  82. package/lib/work-item-creation-contract.d.ts +3 -1
  83. package/lib/work-item-creation-contract.d.ts.map +1 -1
  84. package/lib/work-item-creation-contract.js +10 -1
  85. package/lib/work-item-creation-contract.js.map +1 -1
  86. package/package.json +11 -8
@@ -2,18 +2,36 @@ import { normalizeSourceCreationGrant } from './source-creation.js';
2
2
  import { createHash, randomUUID } from 'node:crypto';
3
3
  import { isAbsolute, join, resolve } from 'node:path';
4
4
  import { realpath } from 'node:fs/promises';
5
- import { SourceGrowthRunUnavailableError, isSourceOwnerContinuation, validateAnySourceGrowthRunBinding as validateSourceGrowthRunBinding } from '@dsh-enhanced/assistant-growth-contract';
5
+ import { SourceGrowthRunUnavailableError, isSourceOwnerContinuation, validateAnySourceGrowthRunBinding as validateSourceGrowthRunBinding, revisionBenefitContractDigest, validateRevisionBenefitContract } from '@dsh-enhanced/assistant-growth-contract';
6
6
  import { automationDefinitionDigest } from '@dsh-enhanced/assistant-automations';
7
7
  import { controlPlaneDigest, expectedSourceRelease } from './store.js';
8
8
  import { awaitSourceSignal, inspectSourceContext } from './source-context.js';
9
9
  import { assertPluginModificationAllowed, removeSourceJobWorktree, validateScopedPluginFiles } from './source-workspace.js';
10
10
  import { assertManagedVersionPaths } from './source-versioning.js';
11
11
  import { resolveSourceBaseline, validateSourceBaselineConfig } from './source-baseline.js';
12
- import { removeSourceJobContainer, validateSourceBuildConfig } from './source-build.js';
13
- import { inspectSourceRevisionContext, validateSourceRevisionGrant } from './source-revision.js';
12
+ import { validateSourceBuildConfig } from './source-build.js';
13
+ import { inspectSourceRevisionContext, normalizeSourceRevisionGrant, validateSourceRevisionGrant } from './source-revision.js';
14
14
  import { inspectSourceCreationContext, validateSourceCreationFiles, validateSourceCreationGrant } from './source-creation.js';
15
15
  import { inheritedEnvironment } from './trust.js';
16
16
  import { SOURCE_BUILD_CHECK_FAILED } from './source-build-diagnostics.js';
17
+ const ZERO_REVIEW_WINDOW = Object.freeze({ verificationMs: 0, regressionMs: 0, adoptionMs: 0 });
18
+ const LINUX_REVIEW_WINDOW_MS = 1_800_000;
19
+ const ADOPTION_WINDOW_MS = 180_000;
20
+ /**
21
+ * The exact Host-side review/adoption windows a source job may allocate. It is
22
+ * computed from live capability presence only when an owner instruction is first
23
+ * enqueued; afterwards the frozen copy is authoritative and any difference
24
+ * fails closed instead of silently resizing the review budget.
25
+ */
26
+ export function instructionReviewWindowFor(input) {
27
+ if (!input.linuxBackend)
28
+ return ZERO_REVIEW_WINDOW;
29
+ return Object.freeze({
30
+ verificationMs: input.verification ? LINUX_REVIEW_WINDOW_MS : 0,
31
+ regressionMs: input.regression ? LINUX_REVIEW_WINDOW_MS : 0,
32
+ adoptionMs: input.adoption ? ADOPTION_WINDOW_MS : 0,
33
+ });
34
+ }
17
35
  export const SOURCE_JOB_OWNER = 'plugin-control-plane-source';
18
36
  const EXECUTOR = 'plugin-control-plane-source-check';
19
37
  const CATALOG = controlPlaneDigest({ executor: EXECUTOR, version: 1, operation: 'isolated-check-pending-plan' });
@@ -24,7 +42,7 @@ export { SourceGrowthRunUnavailableError } from '@dsh-enhanced/assistant-growth-
24
42
  class SourceGrowthNativeMismatchError extends Error {
25
43
  }
26
44
  export function validateSourceJobsConfig(value, build) {
27
- const allowed = new Set(['authorityId', 'expiresAt', 'maxSubmissions', 'allowedModes', 'repository', 'baseline', 'creation', 'revision', 'ownerRouteId', 'principalId', 'workspace', 'preset', 'budgetId', 'budgetAmount']);
45
+ const allowed = new Set(['authorityId', 'expiresAt', 'maxSubmissions', 'allowedModes', 'repository', 'baseline', 'creation', 'revision', 'revisionBenefit', 'allowReviewOnlyFirstAdoption', 'ownerRouteId', 'principalId', 'workspace', 'preset', 'budgetId', 'budgetAmount']);
28
46
  if (value === null || typeof value !== 'object' || Array.isArray(value) || build === undefined
29
47
  || Object.keys(value).some(key => !allowed.has(key))
30
48
  || ![value.authorityId, value.ownerRouteId, value.principalId, value.repository, value.workspace, value.preset].every(text => typeof text === 'string' && text !== '' && text.normalize('NFC').trim() === text && text.length <= 4096 && !/[\p{Cc}]/u.test(text))
@@ -48,6 +66,14 @@ export function validateSourceJobsConfig(value, build) {
48
66
  validateSourceCreationGrant(value.creation);
49
67
  if (value.revision !== undefined)
50
68
  validateSourceRevisionGrant(value.revision);
69
+ if (value.revisionBenefit !== undefined)
70
+ validateRevisionBenefitContract(value.revisionBenefit);
71
+ if (value.allowReviewOnlyFirstAdoption !== undefined && typeof value.allowReviewOnlyFirstAdoption !== 'boolean') {
72
+ throw new Error('plugin-control-plane: invalid review-only adoption flag');
73
+ }
74
+ if (value.revisionBenefit !== undefined && value.revision?.benefit?.contractDigest !== revisionBenefitContractDigest(value.revisionBenefit)) {
75
+ throw new Error('plugin-control-plane: revisionBenefit contract does not match the revision grant benefit pin');
76
+ }
51
77
  if ('backend' in build && build.backend === 'macos-seatbelt'
52
78
  && (!value.allowedModes || value.allowedModes.some(mode => mode === 'modify')
53
79
  || value.creation === undefined && value.revision === undefined)) {
@@ -56,7 +82,9 @@ export function validateSourceJobsConfig(value, build) {
56
82
  }
57
83
  export function normalizeSourceJobsConfig(value, build) {
58
84
  validateSourceJobsConfig(value, build);
59
- return { ...structuredClone(value), ...(value.creation ? { creation: normalizeSourceCreationGrant(value.creation) } : {}) };
85
+ return { ...structuredClone(value),
86
+ ...(value.creation ? { creation: normalizeSourceCreationGrant(value.creation) } : {}),
87
+ ...(value.revision ? { revision: normalizeSourceRevisionGrant(value.revision) } : {}) };
60
88
  }
61
89
  function projection(job, diagnostics) {
62
90
  return { id: job.id, name: job.intent.name, gapId: job.intent.gapId, baseCommit: job.intent.baseCommit,
@@ -76,12 +104,14 @@ export class SourceJobRuntime {
76
104
  authorityDigest;
77
105
  unregister;
78
106
  unregisterContinuation;
107
+ unsubscribeWake;
79
108
  closing;
80
109
  active = false;
81
110
  generation = randomUUID();
82
111
  continuationNonce;
83
112
  continuing = new Set();
84
113
  continuationCursor = 0;
114
+ preferRequestDrain = true;
85
115
  continuationStatus;
86
116
  continuationTransition = 0;
87
117
  /** Process-local, bounded build diagnostics; the failure code itself is durable. */
@@ -121,6 +151,12 @@ export class SourceJobRuntime {
121
151
  execute: input => this.track(this.executeContinuations(input)),
122
152
  });
123
153
  this.active = true;
154
+ if (this.options.drainOwnerInstructionRequests !== undefined) {
155
+ const automations = this.options.ports.automations;
156
+ if (!automations.onSchedulerWake)
157
+ throw new Error('owner instruction drain requires native scheduler wake');
158
+ this.unsubscribeWake = automations.onSchedulerWake(() => this.reconcileContinuations());
159
+ }
124
160
  // Recovery is admitted exclusively through a production cron occurrence.
125
161
  // Reading this local durable query intentionally performs no authority call.
126
162
  this.reconcileContinuations();
@@ -130,6 +166,10 @@ export class SourceJobRuntime {
130
166
  // Registration is transactional from this runtime's point of view: an
131
167
  // unregistration fault cannot leave the other executor registered or
132
168
  // obscure the original registration failure.
169
+ try {
170
+ this.unsubscribeWake?.();
171
+ }
172
+ catch { /* original error wins */ }
133
173
  try {
134
174
  this.unregisterContinuation?.();
135
175
  }
@@ -192,17 +232,40 @@ export class SourceJobRuntime {
192
232
  }
193
233
  return Promise.allSettled(this.growthFlights).then(() => undefined);
194
234
  }
195
- /** A revision is adoptable only when its independent review, and any configured regression, verified. */
235
+ /** A revision is adoptable only when its independent review, any configured regression, and any configured benefit outcome verified. */
196
236
  revisionAdoptionReady(job, planId) {
197
237
  if (this.options.store.getRevisionVerificationStatus(planId) !== 'verified')
198
238
  return false;
199
- if (job.intent.revision?.growthRun.revisionRegressionAcceptance === undefined)
200
- return true;
201
- return this.options.verifyPreparedRevisionRegression !== undefined
202
- && this.options.store.getRevisionRegressionStatus(planId) === 'verified';
239
+ if (job.intent.revision?.growthRun?.revisionRegressionAcceptance !== undefined
240
+ && (!(this.options.verifyPreparedRevisionRegression !== undefined)
241
+ || this.options.store.getRevisionRegressionStatus(planId) !== 'verified')) {
242
+ return false;
243
+ }
244
+ const benefit = job.intent.revision?.grant.benefit;
245
+ if (benefit !== undefined) {
246
+ // Default contract semantics: a pinned benefit requires a recorded
247
+ // non-inferior outcome before adoption. Removing the benefit contract
248
+ // while the grant keeps its pin fails closed instead of silently
249
+ // degrading to review-only adoption.
250
+ if (this.options.reconcileRevisionBenefits === undefined)
251
+ return false;
252
+ const outcome = this.options.store.inspectRevisionBenefitOutcome(planId);
253
+ if (outcome === undefined) {
254
+ // Only an explicit owner opt-in may adopt once before candidate
255
+ // evidence can exist; the reconcile pass then proves non-inferior or
256
+ // rolls the revision back at its bounded observation deadline.
257
+ return this.options.allowReviewOnlyFirstAdoption === true;
258
+ }
259
+ if (outcome.contractDigest !== benefit.contractDigest || outcome.status !== 'non-inferior')
260
+ return false;
261
+ }
262
+ return true;
203
263
  }
204
264
  async continuePrepared(job, signal) {
205
- if (!job.planId || !this.options.store.getOwnerTaskFailureReference(job.intent.gapId))
265
+ // Owner-instruction revisions continue on their frozen instruction; task
266
+ // revisions on their durable task-failure source. Both re-authorize below.
267
+ if (!job.planId || (job.intent.ownerInstruction === undefined
268
+ && !this.options.store.getOwnerTaskFailureReference(job.intent.gapId)))
206
269
  return;
207
270
  if (this.continuing.has(job.id))
208
271
  return;
@@ -216,7 +279,8 @@ export class SourceJobRuntime {
216
279
  }
217
280
  async continuePreparedLocked(job, signal) {
218
281
  this.assertMode(job.intent.mode);
219
- if (!job.planId || !this.options.store.getOwnerTaskFailureReference(job.intent.gapId))
282
+ if (!job.planId || (job.intent.ownerInstruction === undefined
283
+ && !this.options.store.getOwnerTaskFailureReference(job.intent.gapId)))
220
284
  return;
221
285
  const planId = job.planId;
222
286
  const current = this.options.store.getSourceJob(job.id);
@@ -239,20 +303,26 @@ export class SourceJobRuntime {
239
303
  this.assertOwner(job);
240
304
  };
241
305
  if (initial.mode === 'prepared-revise') {
242
- if (job.intent.mode !== 'revise-created' || !job.intent.revision?.growthRun.revisionAcceptance || !initial.sourceRevision?.growthRun.revisionAcceptance)
306
+ // Task-failure revisions continue only while their frozen acceptance pins
307
+ // are fresh; owner-instruction revisions carry no growth run and are
308
+ // re-authorized by assertOwner (instruction, parent and owner checks).
309
+ const revisionPins = job.intent.revision?.growthRun?.revisionAcceptance !== undefined
310
+ && initial.sourceRevision?.growthRun?.revisionAcceptance !== undefined;
311
+ if (job.intent.mode !== 'revise-created'
312
+ || (job.intent.ownerInstruction === undefined && !revisionPins))
243
313
  return;
244
314
  await assertCurrent();
245
315
  if (this.options.store.getRevisionVerificationStatus(planId) === undefined && this.options.verifyPreparedRevision)
246
316
  await this.options.verifyPreparedRevision(job, signal);
247
317
  if (this.options.store.getRevisionVerificationStatus(planId) === 'verified'
248
318
  && this.options.store.getRevisionRegressionStatus(planId) === undefined
249
- && job.intent.revision.growthRun.revisionRegressionAcceptance && this.options.verifyPreparedRevisionRegression) {
319
+ && job.intent.revision?.growthRun?.revisionRegressionAcceptance && this.options.verifyPreparedRevisionRegression) {
250
320
  await assertCurrent();
251
321
  await this.options.verifyPreparedRevisionRegression(job, signal);
252
322
  }
253
323
  // Verified acceptance and the optional independent parent regression end the review lane.
254
324
  // Replacing the stable adopted entry is a separately authorized continuation.
255
- if (this.revisionAdoptionReady(job, planId)
325
+ if (job.intent.ownerInstruction === undefined && this.revisionAdoptionReady(job, planId)
256
326
  && this.options.revisionAdoptionEligible?.(planId) && this.options.adoptVerifiedRevision) {
257
327
  await assertCurrent();
258
328
  await this.options.adoptVerifiedRevision(job, signal);
@@ -260,8 +330,13 @@ export class SourceJobRuntime {
260
330
  return;
261
331
  }
262
332
  if (initial.mode === 'prepared-create') {
333
+ const initialRun = initial.creation?.growthRun;
263
334
  if (job.intent.mode !== 'create'
264
- || !job.intent.creation?.growthRun?.creationAcceptance || !initial.creation?.growthRun?.creationAcceptance)
335
+ || !job.intent.creation?.growthRun
336
+ || job.intent.creation.growthRun.protocol === 'assistant-growth/source-run/v3'
337
+ || !job.intent.creation.growthRun.creationAcceptance
338
+ || !initialRun || initialRun.protocol === 'assistant-growth/source-run/v3'
339
+ || !initialRun.creationAcceptance)
265
340
  return;
266
341
  await assertCurrent();
267
342
  if (this.options.store.getCreationVerificationStatus(planId) === undefined && this.options.verifyPreparedCreation) {
@@ -306,11 +381,22 @@ export class SourceJobRuntime {
306
381
  const authority = { id: config.authorityId, digest: this.authorityDigest, expiresAt: config.expiresAt };
307
382
  return { authority, creation: structuredClone(config.creation), remaining: this.options.store.inspectSourceJobQuota({ authority: { ...authority, maxSubmissions: config.maxSubmissions }, creation: config.creation }) };
308
383
  }
384
+ inspectWorkItemRevisionReadiness(owner) {
385
+ if (!this.available() || !this.modeAllowed('revise-created') || ('expiresAt' in this.options.build && Date.now() >= this.options.build.expiresAt) || this.options.config.revision?.workItemFailures !== true || Date.now() >= this.options.config.revision.expiresAt)
386
+ return undefined;
387
+ const current = this.receipt();
388
+ if (!isSourceOwnerContinuation(current, owner))
389
+ return undefined;
390
+ const config = this.options.config;
391
+ const authority = { id: config.authorityId, digest: this.authorityDigest, expiresAt: config.expiresAt };
392
+ return { authority, revision: structuredClone(config.revision), remaining: this.options.store.inspectSourceJobRevisionQuota({ authority: { ...authority, maxSubmissions: config.maxSubmissions }, revision: config.revision }) };
393
+ }
309
394
  available() { return this.active && !this.abort.signal.aborted && Date.now() < this.options.config.expiresAt; }
310
395
  close() {
311
396
  return this.closing ??= (async () => {
312
397
  this.active = false;
313
398
  try {
399
+ this.unsubscribeWake?.();
314
400
  const current = this.options.ports.automations.inspectSystemOwnedActivation({ owner: SOURCE_JOB_OWNER, automationId: CONTINUATION_AUTOMATION });
315
401
  if (current?.activationNonce === this.continuationNonce)
316
402
  this.options.ports.automations.reconcileSystem({ owner: SOURCE_JOB_OWNER,
@@ -358,7 +444,7 @@ export class SourceJobRuntime {
358
444
  withGapSource(gapId, owner, callback, growthRun) {
359
445
  const reference = this.options.store.getOwnerTaskFailureReference(gapId);
360
446
  if (reference?.schemaVersion === 2) {
361
- if (growthRun?.protocol !== 'assistant-growth/source-run/v2' || !this.options.withWorkItemGrowthRunFence)
447
+ if ((growthRun?.protocol !== 'assistant-growth/source-run/v2' && growthRun?.protocol !== 'assistant-growth/source-run/v3') || !this.options.withWorkItemGrowthRunFence)
362
448
  throw new SourceGrowthRunUnavailableError('work item growth source fence unavailable');
363
449
  return this.options.withWorkItemGrowthRunFence(growthRun, gapId, owner, false, callback);
364
450
  }
@@ -375,6 +461,67 @@ export class SourceJobRuntime {
375
461
  if (!this.modeAllowed(mode))
376
462
  throw new Error('source job mode is not authorized');
377
463
  }
464
+ /**
465
+ * Fail-closed re-validation of a Host-recorded owner instruction at enqueue
466
+ * and at every continuation. The instruction, not the caller, freezes the
467
+ * target, files and owner; the gap must be the synthetic instruction gap.
468
+ */
469
+ #assertOwnerInstruction(binding, input) {
470
+ if (!binding)
471
+ throw new Error('revision requires an authenticated owner task failure or owner instruction');
472
+ const stored = this.options.store.getOwnerInstruction(binding.instructionId);
473
+ if (!stored || controlPlaneDigest(stored) !== binding.digest)
474
+ throw new Error('owner instruction changed');
475
+ if (stored.expiresAt <= Date.now())
476
+ throw new Error('owner instruction expired');
477
+ if (stored.planId !== input.parentPlanId || stored.pluginName !== input.name)
478
+ throw new Error('owner instruction target changed');
479
+ if (stored.filesDigest !== controlPlaneDigest(input.files))
480
+ throw new Error('owner instruction files changed');
481
+ let gapKey;
482
+ try {
483
+ gapKey = this.options.store.getGap(input.gapId).idempotencyKey;
484
+ }
485
+ catch {
486
+ throw new Error('owner instruction gap changed');
487
+ }
488
+ if (gapKey !== `owner-instruction:${stored.instructionId}`) {
489
+ throw new Error('owner instruction gap changed');
490
+ }
491
+ const freeze = this.options.store.getOwnerInstructionReviewFreeze(stored.instructionId);
492
+ if (freeze === undefined)
493
+ throw new Error('owner instruction review freeze is unavailable');
494
+ this.#assertInstructionReviewFreeze(freeze, undefined);
495
+ return Object.freeze({ stored, freeze });
496
+ }
497
+ /** Live capability identity; only used to validate an already frozen budget. */
498
+ instructionReviewWindow() {
499
+ return instructionReviewWindowFor({
500
+ linuxBackend: 'backend' in this.options.build && this.options.build.backend === 'linux-native',
501
+ verification: this.options.verifyPreparedRevision !== undefined,
502
+ regression: this.options.verifyPreparedRevisionRegression !== undefined,
503
+ adoption: this.options.adoptVerifiedRevision !== undefined,
504
+ });
505
+ }
506
+ /**
507
+ * A frozen review budget may only be consumed while the current reviewer
508
+ * capability is exactly the one frozen at enqueue. Any absence or difference
509
+ * fails closed: this guard never substitutes live config for the freeze.
510
+ */
511
+ #assertInstructionReviewFreeze(freeze, expected) {
512
+ if (this.options.verifyPreparedRevision === undefined)
513
+ throw new Error('owner instruction revision requires an available independent review');
514
+ if (freeze.budget.authority.expiresAt <= Date.now())
515
+ throw new Error('owner instruction review authority expired');
516
+ if (controlPlaneDigest(this.instructionReviewWindow()) !== controlPlaneDigest(freeze.budget.window)) {
517
+ throw new Error('owner instruction review budget changed');
518
+ }
519
+ if (expected !== undefined && (expected.reviewFreezeDigest === undefined || expected.reviewWindow === undefined
520
+ || expected.reviewFreezeDigest !== freeze.digest
521
+ || controlPlaneDigest(expected.reviewWindow) !== controlPlaneDigest(freeze.budget.window))) {
522
+ throw new Error('owner instruction review freeze changed');
523
+ }
524
+ }
378
525
  assertOwner(job) {
379
526
  this.assertMode(job.intent.mode);
380
527
  const current = this.receipt();
@@ -387,8 +534,27 @@ export class SourceJobRuntime {
387
534
  || job.intent.authority.id !== this.options.config.authorityId
388
535
  || controlPlaneDigest(job.intent.owner) !== job.intent.ownerDigest
389
536
  || (taskBacked ? !isSourceOwnerContinuation(current, job.intent.owner)
390
- : controlPlaneDigest(current) !== job.intent.ownerDigest))
537
+ : (job.intent.ownerInstruction !== undefined
538
+ ? !isSourceOwnerContinuation(current, job.intent.owner)
539
+ : controlPlaneDigest(current) !== job.intent.ownerDigest)))
391
540
  throw new Error('source job authority changed or expired');
541
+ // Re-validate the frozen instruction on every continuation exactly like
542
+ // enqueue; a revoked, changed or expired instruction cannot proceed.
543
+ if (job.intent.ownerInstruction !== undefined) {
544
+ const stored = this.options.store.getOwnerInstruction(job.intent.ownerInstruction.instructionId);
545
+ if (taskBacked || stored === undefined || controlPlaneDigest(stored) !== job.intent.ownerInstruction.digest
546
+ || stored.expiresAt <= Date.now() || stored.planId !== job.intent.revision?.parent.planId
547
+ || stored.pluginName !== job.intent.name || stored.filesDigest !== controlPlaneDigest(job.intent.files)
548
+ || stored.ownerDigest !== job.intent.ownerDigest
549
+ || this.options.store.getGap(job.intent.gapId).idempotencyKey !== `owner-instruction:${stored.instructionId}`) {
550
+ throw new Error('owner instruction changed or expired');
551
+ }
552
+ // Recovery consumes only the review supplier/budget frozen at enqueue.
553
+ const freeze = this.options.store.getOwnerInstructionReviewFreeze(stored.instructionId);
554
+ if (freeze === undefined)
555
+ throw new Error('owner instruction review freeze is unavailable');
556
+ this.#assertInstructionReviewFreeze(freeze, job.intent.ownerInstruction);
557
+ }
392
558
  if (job.intent.revision) {
393
559
  const parent = this.options.inspectRevisionParent?.(job.intent.revision.parent.planId);
394
560
  if (!parent || parent.name !== job.intent.name || controlPlaneDigest(parent.parent) !== controlPlaneDigest(job.intent.revision.parent))
@@ -408,6 +574,8 @@ export class SourceJobRuntime {
408
574
  validateSourceGrowthRunBinding(growthRun);
409
575
  if (growthRun.protocol === 'assistant-growth/source-run/v2' && (mode !== 'create' || this.options.config.creation?.workItemFailures !== true || this.options.store.getOwnerTaskFailureReference(gapId)?.schemaVersion !== 2))
410
576
  throw new Error('work item growth requires explicit create authority');
577
+ if (growthRun.protocol === 'assistant-growth/source-run/v3' && (mode !== 'revise-created' || this.options.config.revision?.workItemFailures !== true || this.options.store.getOwnerTaskFailureReference(gapId)?.schemaVersion !== 2 || Date.now() >= this.options.config.revision.expiresAt))
578
+ throw new Error('work item revision growth requires explicit current revision authority');
411
579
  if (!this.options.assertGrowthRun)
412
580
  throw new SourceGrowthRunUnavailableError('source growth run producer unavailable');
413
581
  this.options.assertGrowthRun(growthRun, gapId, owner, generation);
@@ -416,8 +584,14 @@ export class SourceJobRuntime {
416
584
  this.assertMode(input.mode);
417
585
  if (input.growthRun?.protocol === 'assistant-growth/source-run/v2' && input.mode !== 'create')
418
586
  throw new Error('work item growth only permits create');
587
+ if (input.growthRun?.protocol === 'assistant-growth/source-run/v3' && input.mode !== 'revise-created')
588
+ throw new Error('work item revision growth only permits revise-created');
419
589
  if (input.mode !== 'create' && input.mode !== 'revise-created' && input.growthRun !== undefined)
420
590
  throw new Error('modify source job cannot carry a growth run');
591
+ if (input.ownerInstruction !== undefined && input.mode !== 'revise-created')
592
+ throw new Error('owner instructions only authorize revise-created jobs');
593
+ if (input.growthRun !== undefined && input.ownerInstruction !== undefined)
594
+ throw new Error('an owner instruction is not a growth run');
421
595
  if (input.mode === 'create' || input.mode === 'revise-created')
422
596
  validateSourceCreationFiles(input.files);
423
597
  else {
@@ -429,7 +603,8 @@ export class SourceJobRuntime {
429
603
  }
430
604
  if ((input.mode === 'revise-created') !== (typeof input.parentPlanId === 'string' && input.parentPlanId.length > 0))
431
605
  throw new Error('source revision parent is missing or outside revision mode');
432
- return this.track(this.enqueueOwned({ ...input, files: structuredClone(input.files), owner: structuredClone(input.owner) }));
606
+ return this.track(this.enqueueOwned({ ...input, files: structuredClone(input.files), owner: structuredClone(input.owner),
607
+ ...(input.ownerInstruction === undefined ? {} : { ownerInstruction: structuredClone(input.ownerInstruction) }) }));
433
608
  }
434
609
  async enqueueOwned(input) {
435
610
  this.assertMode(input.mode);
@@ -457,11 +632,19 @@ export class SourceJobRuntime {
457
632
  throw new Error('source job repository mismatch');
458
633
  const currentOwner = this.assertCaller(input.owner);
459
634
  const reference = this.options.store.getOwnerTaskFailureReference(input.gapId);
460
- if (input.mode === 'revise-created' && !reference)
461
- throw new Error('revision requires an authenticated owner task failure');
462
- const owner = reference?.owner ?? currentOwner;
635
+ if (input.mode === 'revise-created' && reference !== undefined && input.ownerInstruction !== undefined) {
636
+ throw new Error('owner instruction cannot replace an authenticated owner task failure');
637
+ }
638
+ // A revise-created job without a task failure is legal only through a
639
+ // Host-recorded owner instruction; every frozen field must match it.
640
+ const ownerInstruction = input.mode === 'revise-created' && !reference
641
+ ? this.#assertOwnerInstruction(input.ownerInstruction, input) : undefined;
642
+ const owner = ownerInstruction?.stored.ownerReceipt ?? reference?.owner ?? currentOwner;
463
643
  if (reference && !isSourceOwnerContinuation(currentOwner, owner))
464
644
  throw new Error('source job task owner changed');
645
+ if (ownerInstruction !== undefined && !isSourceOwnerContinuation(currentOwner, ownerInstruction.stored.ownerReceipt)) {
646
+ throw new Error('owner instruction owner changed');
647
+ }
465
648
  this.withGapSource(input.gapId, currentOwner, () => this.assertGrowthRun(input.growthRun, input.mode, input.gapId, owner, true), input.growthRun);
466
649
  const parent = input.mode === 'revise-created' ? this.options.inspectRevisionParent?.(input.parentPlanId) : undefined;
467
650
  if (input.mode === 'revise-created' && (!parent || parent.name !== input.name))
@@ -474,13 +657,19 @@ export class SourceJobRuntime {
474
657
  const prior = this.options.store.getSourceJob(id);
475
658
  if (prior !== undefined) {
476
659
  const frozen = prior.intent;
660
+ const instructionReviewMatches = ownerInstruction === undefined || (frozen.ownerInstruction?.digest === controlPlaneDigest(ownerInstruction.stored)
661
+ && frozen.ownerInstruction.reviewFreezeDigest === ownerInstruction.freeze.digest
662
+ && frozen.ownerInstruction.reviewWindow !== undefined
663
+ && controlPlaneDigest(frozen.ownerInstruction.reviewWindow) === controlPlaneDigest(ownerInstruction.freeze.budget.window));
477
664
  if (frozen.authority.digest !== this.authorityDigest || frozen.ownerDigest !== controlPlaneDigest(owner) || frozen.trustDigest !== controlPlaneDigest(trust)
478
665
  || frozen.mode !== input.mode || (input.mode === 'create' && (controlPlaneDigest(frozen.creation?.grant) !== controlPlaneDigest(config.creation)
479
666
  || (frozen.creation?.growthRun === undefined) !== (input.growthRun === undefined)
480
667
  || (input.growthRun !== undefined && controlPlaneDigest(frozen.creation?.growthRun) !== controlPlaneDigest(input.growthRun))))
481
668
  || (input.mode === 'revise-created' && (controlPlaneDigest(frozen.revision?.grant) !== controlPlaneDigest(config.revision)
482
669
  || controlPlaneDigest(frozen.revision?.parent) !== controlPlaneDigest(parent.parent)
483
- || controlPlaneDigest(frozen.revision?.growthRun) !== controlPlaneDigest(input.growthRun)))
670
+ || (frozen.revision?.growthRun === undefined) !== (input.growthRun === undefined)
671
+ || (input.growthRun !== undefined && controlPlaneDigest(frozen.revision?.growthRun) !== controlPlaneDigest(input.growthRun))
672
+ || !instructionReviewMatches))
484
673
  || frozen.repository !== input.repository || frozen.name !== input.name || frozen.gapId !== input.gapId || frozen.baseCommit !== input.expectedBaseCommit
485
674
  || frozen.ttlMs !== input.ttlMs || controlPlaneDigest(frozen.files) !== controlPlaneDigest(input.files))
486
675
  throw new Error('source job idempotency conflict');
@@ -511,13 +700,18 @@ export class SourceJobRuntime {
511
700
  ...(input.mode === 'create' ? { mode: 'create', creation: { grant: structuredClone(config.creation), generatorDigest: source.generatorDigest,
512
701
  ...(input.growthRun === undefined ? {} : { growthRun: structuredClone(input.growthRun) }) } } : {}),
513
702
  ...(input.mode === 'revise-created' ? { mode: 'revise-created', revision: { grant: structuredClone(config.revision),
514
- generatorDigest: source.generatorDigest, parent: structuredClone(parent.parent), growthRun: structuredClone(input.growthRun) } } : {}),
703
+ generatorDigest: source.generatorDigest, parent: structuredClone(parent.parent),
704
+ // Task-failure revisions pin a growth run; owner-instruction revisions have none.
705
+ ...(input.growthRun === undefined ? {}
706
+ : { growthRun: structuredClone(input.growthRun) }) } } : {}),
515
707
  gapId: gap.id, gapRevision: gap.revision, gapDigest: controlPlaneDigest(gap), baseCommit: source.baseCommit,
516
708
  files: structuredClone(input.files), ttlMs: input.ttlMs, build: structuredClone(this.options.build),
517
709
  worktree: join(this.options.statePath, 'source-worktrees', `worktree-job-${token}`),
518
- ...('backend' in this.options.build
519
- ? { nativeResource: { backend: this.options.build.backend, key: id } }
520
- : { containerName: `dsh-source-job-${token}` }),
710
+ ...(ownerInstruction === undefined ? {} : {
711
+ ownerInstruction: { instructionId: ownerInstruction.stored.instructionId, digest: controlPlaneDigest(ownerInstruction.stored),
712
+ reviewFreezeDigest: ownerInstruction.freeze.digest, reviewWindow: structuredClone(ownerInstruction.freeze.budget.window) }
713
+ }),
714
+ nativeResource: { backend: this.options.build.backend, key: id },
521
715
  };
522
716
  await assertCurrent();
523
717
  // Synchronous acceptance and registration: no Agent signal is persisted.
@@ -589,7 +783,24 @@ export class SourceJobRuntime {
589
783
  throw error;
590
784
  }
591
785
  }
592
- linuxRevisionReviewTimeoutMs() {
786
+ /**
787
+ * Per-job review/adoption window. A supplied `job` whose revision is authorized
788
+ * by an owner instruction uses ONLY the window frozen in its intent. The
789
+ * job-less overload remains live because continuationTimeoutMs() sizes the
790
+ * shared continuation occurrence: that outer wall-clock cap may shrink with
791
+ * live config but can never enlarge the frozen inner verifier authority/budget.
792
+ * A smaller live window only truncates the continuation earlier (settled
793
+ * unknown); the per-job definition and the verifier budget stay frozen.
794
+ */
795
+ linuxRevisionReviewTimeoutMs(job) {
796
+ // An owner-instruction revision uses only the review window frozen in its
797
+ // intent: a live-config change must fail closed, never resize the budget.
798
+ if (job?.intent.ownerInstruction !== undefined) {
799
+ const window = job.intent.ownerInstruction.reviewWindow;
800
+ if (window === undefined)
801
+ throw new Error('owner instruction review window is unavailable');
802
+ return window.verificationMs + window.regressionMs + window.adoptionMs;
803
+ }
593
804
  // A Linux revision can run two independently bounded reviews after its
594
805
  // repository build. This outer window never changes their own budgets or
595
806
  // the frozen source expiry; legacy backend definitions retain their identity.
@@ -603,7 +814,7 @@ export class SourceJobRuntime {
603
814
  const config = this.options.config;
604
815
  return { name: `Source check: ${job.intent.name}`, schedule: { kind: 'at', at: new Date(job.dispatchAt ?? job.createdAt + 1000).toISOString() },
605
816
  workspace: config.workspace, agentPreset: config.preset, timeoutMs: job.intent.build.timeoutMs + 120_000
606
- + (job.intent.mode === 'revise-created' ? this.linuxRevisionReviewTimeoutMs() : 0)
817
+ + (job.intent.mode === 'revise-created' ? this.linuxRevisionReviewTimeoutMs(job) : 0)
607
818
  + (job.intent.mode === 'create' && job.intent.creation?.growthRun?.protocol === 'assistant-growth/source-run/v2'
608
819
  && 'backend' in this.options.build
609
820
  && this.options.build.backend === 'linux-native' && this.options.verifyPreparedCreation ? 1_800_000 : 0)
@@ -693,7 +904,8 @@ export class SourceJobRuntime {
693
904
  hasContinuations() {
694
905
  return !!(this.options.approvePrepared || this.options.verifyPreparedRevisionRegression || this.options.verifyPreparedRevision
695
906
  || this.options.verifyPreparedCreation || this.options.adoptVerifiedCreation || this.options.adoptVerifiedRevision
696
- || this.options.releasePrepared || this.options.advanceReleased || this.options.adoptReleased);
907
+ || this.options.releasePrepared || this.options.advanceReleased || this.options.adoptReleased
908
+ || this.options.drainOwnerInstructionRequests);
697
909
  }
698
910
  continuationJobs() {
699
911
  if (!this.hasContinuations())
@@ -713,11 +925,15 @@ export class SourceJobRuntime {
713
925
  return false;
714
926
  }
715
927
  if (plan.mode === 'prepared-revise') {
716
- if (job.intent.mode !== 'revise-created' || plan.status !== 'pending-approval' || !plan.sourceRevision?.growthRun.revisionAcceptance)
928
+ // Task-failure revisions require fresh acceptance pins; owner-instruction
929
+ // revisions are re-authorized through their frozen instruction row.
930
+ if (job.intent.mode !== 'revise-created' || plan.status !== 'pending-approval')
931
+ return false;
932
+ if (job.intent.ownerInstruction === undefined && !plan.sourceRevision?.growthRun?.revisionAcceptance)
717
933
  return false;
718
934
  const status = this.options.store.getRevisionVerificationStatus(plan.id);
719
935
  const verify = status === undefined && this.options.verifyPreparedRevision !== undefined;
720
- const regression = status === 'verified' && plan.sourceRevision.growthRun.revisionRegressionAcceptance !== undefined
936
+ const regression = status === 'verified' && plan.sourceRevision?.growthRun?.revisionRegressionAcceptance !== undefined
721
937
  && this.options.store.getRevisionRegressionStatus(plan.id) === undefined && this.options.verifyPreparedRevisionRegression !== undefined;
722
938
  const adoption = this.options.revisionAdoptionEligible !== undefined && this.options.adoptVerifiedRevision !== undefined
723
939
  && this.revisionAdoptionReady(job, plan.id) && this.options.revisionAdoptionEligible(plan.id) === true;
@@ -732,8 +948,13 @@ export class SourceJobRuntime {
732
948
  return plan.expiresAt > Date.now();
733
949
  }
734
950
  if (plan.mode === 'prepared-create') {
951
+ const planRun = plan.creation?.growthRun;
735
952
  if (job.intent.mode !== 'create' || plan.status !== 'pending-approval'
736
- || !job.intent.creation?.growthRun?.creationAcceptance || !plan.creation?.growthRun?.creationAcceptance)
953
+ || !job.intent.creation?.growthRun
954
+ || job.intent.creation.growthRun.protocol === 'assistant-growth/source-run/v3'
955
+ || !job.intent.creation.growthRun.creationAcceptance
956
+ || !planRun || planRun.protocol === 'assistant-growth/source-run/v3'
957
+ || !planRun.creationAcceptance)
737
958
  return false;
738
959
  const verification = this.options.store.getCreationVerificationStatus(plan.id);
739
960
  const verify = verification === undefined && this.options.verifyPreparedCreation !== undefined;
@@ -778,6 +999,16 @@ export class SourceJobRuntime {
778
999
  return this.options.advanceReleased !== undefined;
779
1000
  return plan.status === 'release-complete' && this.options.adoptReleased !== undefined;
780
1001
  }
1002
+ /**
1003
+ * Outer wall-clock cap for the shared continuation automation definition, the
1004
+ * owner-instruction request drain and the non-adoption continuation signal.
1005
+ * This is NOT the frozen review budget: an owner-instruction job's inner
1006
+ * verifier authority/budget and its own definition timeout are pinned by the
1007
+ * intent plus the write-once instruction freeze. A live config change here can
1008
+ * only truncate the continuation earlier (settled unknown); it can never
1009
+ * enlarge the frozen inner budget. Narrowing the review budget for one request
1010
+ * would require a verifier request/response protocol v2.
1011
+ */
781
1012
  continuationTimeoutMs() {
782
1013
  const release = this.options.releaseTimeoutMs ?? 40_000, adoption = this.options.adoptionTimeoutMs ?? 60_000;
783
1014
  const needed = (this.options.adoptReleased ? adoption : 0) + (this.options.advanceReleased ? release + 40_000 : this.options.releasePrepared ? 40_000 : 10_000);
@@ -804,7 +1035,9 @@ export class SourceJobRuntime {
804
1035
  reconcileContinuations(force = false) {
805
1036
  if (!this.active || !this.hasContinuations())
806
1037
  return;
807
- const definition = this.continuationDefinition(), desiredStatus = this.continuationJobs().length ? 'active' : 'paused';
1038
+ const definition = this.continuationDefinition();
1039
+ const requests = this.available() && this.options.hasOwnerInstructionRequests?.() === true;
1040
+ const desiredStatus = requests || this.continuationJobs().length ? 'active' : 'paused';
808
1041
  if (!force && desiredStatus === this.continuationStatus)
809
1042
  return;
810
1043
  this.options.ports.automations.reconcileSystem({ owner: SOURCE_JOB_OWNER, automationId: CONTINUATION_AUTOMATION,
@@ -822,7 +1055,22 @@ export class SourceJobRuntime {
822
1055
  throw new Error('source continuation dispatch changed');
823
1056
  const jobs = this.continuationJobs();
824
1057
  let unsettled = false;
825
- if (jobs.length) {
1058
+ const drain = this.options.drainOwnerInstructionRequests;
1059
+ const hasRequests = drain !== undefined && this.available() && this.options.hasOwnerInstructionRequests?.() === true;
1060
+ if (hasRequests && (!jobs.length || this.preferRequestDrain)) {
1061
+ const signal = AbortSignal.any([this.abort.signal, input.signal, AbortSignal.timeout(Math.max(1, Math.min(this.continuationTimeoutMs(), this.options.config.expiresAt - Date.now())))]);
1062
+ signal.throwIfAborted();
1063
+ try {
1064
+ await drain(signal);
1065
+ signal.throwIfAborted();
1066
+ }
1067
+ catch {
1068
+ unsettled = true;
1069
+ }
1070
+ this.preferRequestDrain = false;
1071
+ }
1072
+ else if (jobs.length) {
1073
+ this.preferRequestDrain = true;
826
1074
  // One budget-admitted occurrence advances one durable row. The cursor
827
1075
  // is intentionally in-memory; a restart returning to the first row is
828
1076
  // safe because every release transition remains independently claimed.
@@ -840,6 +1088,12 @@ export class SourceJobRuntime {
840
1088
  }
841
1089
  }
842
1090
  this.reconcileContinuations();
1091
+ // P1-4 benefit evaluation rides the same cron occurrence; a failure here
1092
+ // never changes the continuation verdict.
1093
+ try {
1094
+ this.options.reconcileRevisionBenefits?.();
1095
+ }
1096
+ catch { /* owner-visible via benefit ledger absence */ }
843
1097
  if (unsettled)
844
1098
  return { outcome: 'unknown', failureClass: 'infrastructure', failurePhase: 'host-execution', failureCode: 'source-continuation-unsettled', sideEffectState: 'unknown', retryability: 'unsafe' };
845
1099
  return { outcome: 'succeeded', failureClass: 'none', failurePhase: 'none', failureCode: 'none', sideEffectState: 'possible', retryability: 'unsafe' };
@@ -906,8 +1160,8 @@ export class SourceJobRuntime {
906
1160
  const signal = AbortSignal.any([input.signal, this.abort.signal,
907
1161
  ...(growthController ? [growthController.signal] : []),
908
1162
  AbortSignal.timeout(Math.max(1, Math.min(job.intent.build.timeoutMs + 120_000
909
- + (job.intent.mode === 'revise-created' && this.linuxRevisionReviewTimeoutMs()
910
- ? this.linuxRevisionReviewTimeoutMs() : this.options.verifyPreparedCreation ? 1_800_000 : 0)
1163
+ + (job.intent.mode === 'revise-created' && this.linuxRevisionReviewTimeoutMs(job)
1164
+ ? this.linuxRevisionReviewTimeoutMs(job) : this.options.verifyPreparedCreation ? 1_800_000 : 0)
911
1165
  + (this.options.advanceReleased ? this.options.releaseTimeoutMs ?? 40_000 : 0) + (this.options.adoptReleased ? this.options.adoptionTimeoutMs ?? 60_000 : 0), job.expiresAt - Date.now())))]);
912
1166
  try {
913
1167
  this.assertOwner(job);
@@ -1005,15 +1259,11 @@ export class SourceJobRuntime {
1005
1259
  const trust = await this.options.trust();
1006
1260
  if (controlPlaneDigest(trust) !== job.intent.trustDigest)
1007
1261
  throw new Error('source job trust changed');
1008
- if ('backend' in job.intent.build) {
1009
- if (job.intent.nativeResource?.backend !== job.intent.build.backend || job.intent.nativeResource.key !== job.id
1010
- || controlPlaneDigest(this.options.build) !== controlPlaneDigest(job.intent.build)
1011
- || await this.options.inspectNativeQuiescence?.(job.id) !== 'quiescent') {
1012
- throw new Error('native source job has no valid durable quiescence receipt');
1013
- }
1014
- }
1015
- else {
1016
- await removeSourceJobContainer(job.intent.build, { id: job.id, containerName: job.intent.containerName });
1262
+ if (job.intent.nativeResource === undefined || !('backend' in job.intent.build)
1263
+ || job.intent.nativeResource.backend !== job.intent.build.backend || job.intent.nativeResource.key !== job.id
1264
+ || controlPlaneDigest(this.options.build) !== controlPlaneDigest(job.intent.build)
1265
+ || await this.options.inspectNativeQuiescence?.(job.id) !== 'quiescent') {
1266
+ throw new Error('native source job has no valid durable quiescence receipt');
1017
1267
  }
1018
1268
  await removeSourceJobWorktree({ stateRoot: join(this.options.statePath, 'source-worktrees'), repository: job.intent.repository, worktree: job.intent.worktree, baseCommit: job.intent.baseCommit, environment: inheritedEnvironment(trust) });
1019
1269
  this.inspect(input);