@zq-silk/yui 0.15.12 → 0.16.0

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 (236) hide show
  1. package/ARCHITECTURE.md +3 -2
  2. package/ARCHITECTURE.zh-CN.md +3 -2
  3. package/dist/artifacts/artifactCommitLock.js +16 -55
  4. package/dist/artifacts/taskArtifactRepository.js +3 -3
  5. package/dist/cli/agentConfigurationPicker.js +1 -1
  6. package/dist/cli/commandCatalog.js +57 -41
  7. package/dist/cli/completion.js +18 -18
  8. package/dist/cli/completionWizard.js +1 -1
  9. package/dist/cli/dynamicCompletion.js +1 -1
  10. package/dist/cli/interactionPolicy.js +7 -3
  11. package/dist/cli/invocationAuthority.js +64 -0
  12. package/dist/cli/managedDiagnostics.js +1 -1
  13. package/dist/cli/parseRepeatable.js +35 -0
  14. package/dist/cli/updatePorts.js +26 -5
  15. package/dist/cli.js +1280 -1243
  16. package/dist/commands/agentCommands.js +4 -4
  17. package/dist/commands/capabilityCommands.js +1 -1
  18. package/dist/commands/configCommands.js +6 -57
  19. package/dist/commands/configOverview.js +2 -2
  20. package/dist/commands/durableJobCommands.js +12 -6
  21. package/dist/commands/globalRoleCommands.js +53 -29
  22. package/dist/commands/grantCommands.js +0 -3
  23. package/dist/commands/jobCommands.js +1 -4
  24. package/dist/commands/operatorCommands.js +3 -3
  25. package/dist/commands/projectCommands.js +1 -10
  26. package/dist/commands/resourcesCommands.js +10 -40
  27. package/dist/commands/roleConfiguration.js +2 -6
  28. package/dist/commands/taskActivationCommands.js +4 -8
  29. package/dist/commands/taskAgentCapabilities.js +9 -0
  30. package/dist/commands/taskCommandSupport.js +155 -0
  31. package/dist/commands/taskCommandTypes.js +1 -0
  32. package/dist/commands/taskCommands.js +271 -710
  33. package/dist/commands/taskCompletionGate.js +1 -1
  34. package/dist/commands/taskExecutionCommands.js +1 -1
  35. package/dist/commands/taskFactCommands.js +325 -0
  36. package/dist/commands/taskInputCommands.js +12 -42
  37. package/dist/commands/taskIntegrationCommands.js +17 -57
  38. package/dist/commands/taskNextActionCommand.js +7 -6
  39. package/dist/commands/taskPublicationAdoptCommand.js +3 -3
  40. package/dist/commands/taskPublicationCommands.js +1 -1
  41. package/dist/commands/taskPublicationVerifyCommand.js +1 -1
  42. package/dist/commands/taskRemoteDeliveryCommand.js +2 -94
  43. package/dist/commands/taskUpstreamCommands.js +19 -8
  44. package/dist/commands/workflowCommands.js +6 -1
  45. package/dist/completion/completionInstaller.js +26 -26
  46. package/dist/completion/completionState.js +26 -26
  47. package/dist/completion/fileCompletionManager.js +6 -11
  48. package/dist/config/configCatalog.js +0 -2
  49. package/dist/config/timeZone.js +14 -0
  50. package/dist/config/yuiConfig.js +0 -29
  51. package/dist/context/runContextPack.js +2 -3
  52. package/dist/context/taskCatalog.js +3 -5
  53. package/dist/context/taskContext.js +139 -151
  54. package/dist/context/wakeRunReferences.js +11 -0
  55. package/dist/controller/agentCapabilities.js +58 -0
  56. package/dist/controller/agentRuntimeObserver.js +6 -11
  57. package/dist/controller/clientRuntime.js +22 -14
  58. package/dist/controller/controller.js +9 -22
  59. package/dist/controller/fileSchedulerStoreAdapter.js +117 -212
  60. package/dist/controller/jobClient.js +4 -4
  61. package/dist/controller/jobControl.js +60 -25
  62. package/dist/controller/jobSupervisor.js +4 -4
  63. package/dist/controller/providerRetryDelivery.js +7 -7
  64. package/dist/controller/runtime.js +51 -44
  65. package/dist/controller/runtimeEventProcessor.js +0 -5
  66. package/dist/controller/sessionOwnerReconciliation.js +5 -0
  67. package/dist/controller/taskAgentError.js +35 -0
  68. package/dist/coordination/workMailboxQueue.js +2 -24
  69. package/dist/core/controllerClient.js +2 -2
  70. package/dist/core/fileLockOwner.js +74 -0
  71. package/dist/decision/decision.js +17 -0
  72. package/dist/doctor/doctor.js +18 -9
  73. package/dist/event/taskEvent.js +12 -0
  74. package/dist/execution/workItemExecutionProjection.js +2 -2
  75. package/dist/executor/agentCapabilityConfig.js +43 -0
  76. package/dist/executor/agentConfigurationCatalog.js +49 -11
  77. package/dist/executor/agentExecutor.js +5 -8
  78. package/dist/executor/fileRoleLaunchPlanner.js +11 -53
  79. package/dist/executor/taskAgentCapabilities.js +64 -0
  80. package/dist/integration/deliveryObligation.js +1 -69
  81. package/dist/integration/gitIntegrationService.js +60 -84
  82. package/dist/integration/integrationAttempt.js +3 -14
  83. package/dist/integration/integrationSourceApplication.js +4 -12
  84. package/dist/integration/manifestTags.js +2 -2
  85. package/dist/job/durableJob.js +4 -25
  86. package/dist/job/jobAssignmentScope.js +22 -0
  87. package/dist/job/jobOperation.js +16 -0
  88. package/dist/job/jobRunner.js +2 -1
  89. package/dist/job/stepDirectory.js +15 -0
  90. package/dist/kernel/builtinCapabilities.js +15 -11
  91. package/dist/kernel/kernelPorts.js +1 -17
  92. package/dist/lifecycle/exactRunTerminalization.js +1 -9
  93. package/dist/message/inputControlResolution.js +3 -4
  94. package/dist/message/message.js +17 -20
  95. package/dist/message/messageContinuation.js +1 -1
  96. package/dist/milestone/milestone.js +11 -0
  97. package/dist/observability/orchestrationMetrics.js +24 -1
  98. package/dist/observability/runtimeIdentity.js +1 -17
  99. package/dist/output/agentConfigurationPresentation.js +2 -0
  100. package/dist/output/timePresentation.js +1 -14
  101. package/dist/plugins/pluginService.js +12 -4
  102. package/dist/release/releaseWorkflowPorts.js +45 -96
  103. package/dist/release/runtimeRelease.js +9 -15
  104. package/dist/repository/projectMaintenanceLock.js +23 -64
  105. package/dist/repository/taskBaseFreshness.js +1 -1
  106. package/dist/repository/taskWorkspaceCoordinator.js +13 -22
  107. package/dist/repository/taskWorkspacePreparer.js +29 -25
  108. package/dist/repository/workspaceCleanupInspection.js +5 -5
  109. package/dist/resources/autoResourceGc.js +4 -77
  110. package/dist/resources/liveReferences.js +7 -54
  111. package/dist/resources/resourceDiscovery.js +5 -118
  112. package/dist/resources/resourceGc.js +233 -181
  113. package/dist/resources/resourceRegistrar.js +5 -4
  114. package/dist/resources/resourceRegistry.js +3 -37
  115. package/dist/resources/resourceRegistryStore.js +13 -36
  116. package/dist/resources/sqliteResourceRegistry.js +43 -26
  117. package/dist/review/deltaRecheck.js +1 -1
  118. package/dist/review/reviewAcceptance.js +1 -1
  119. package/dist/review/reviewDecision.js +1 -1
  120. package/dist/review/reviewRound.js +8 -7
  121. package/dist/review/taskFinalReviewContractResolution.js +1 -1
  122. package/dist/runtime/acpProtocol.js +4 -51
  123. package/dist/runtime/acpSession.js +10 -75
  124. package/dist/runtime/acpSessionConfiguration.js +1 -8
  125. package/dist/runtime/agentEndpoint.js +3 -2
  126. package/dist/runtime/agentFailureContext.js +43 -0
  127. package/dist/runtime/agentHost.js +15 -15
  128. package/dist/runtime/codexInteractiveHost.js +2 -2
  129. package/dist/runtime/index.js +0 -1
  130. package/dist/runtime/managedCaller.js +21 -1
  131. package/dist/runtime/providerErrors.js +38 -0
  132. package/dist/runtime/runtimeObservation.js +0 -5
  133. package/dist/runtime/runtimeSessionCandidate.js +2 -3
  134. package/dist/runtime/sessionOwnerIdentity.js +1 -1
  135. package/dist/runtime/structuredProviderHost.js +4 -41
  136. package/dist/runtime/tmuxAdapters.js +6 -8
  137. package/dist/scheduler/activeRoleRunDelivery.js +7 -7
  138. package/dist/scheduler/leaderWakeupProcessor.js +19 -6
  139. package/dist/scheduler/operatorEvent.js +12 -20
  140. package/dist/scheduler/operatorInputNotificationProcessor.js +1 -1
  141. package/dist/scheduler/ports.js +5 -8
  142. package/dist/scheduler/roleRunLiveness.js +4 -4
  143. package/dist/scheduler/roleRunStall.js +16 -23
  144. package/dist/scheduler/taskExecutionProjection.js +25 -33
  145. package/dist/scheduler/taskObservabilityProjection.js +0 -1
  146. package/dist/scheduler/taskWake.js +5 -10
  147. package/dist/scheduler/wakeReason.js +2 -0
  148. package/dist/scheduler/wakeupQueue.js +2 -4
  149. package/dist/setup/setupCommand.js +1 -1
  150. package/dist/storage/contextRecords.js +106 -0
  151. package/dist/storage/currentTaskStore.js +1 -1
  152. package/dist/storage/homeLayout.js +13 -17
  153. package/dist/storage/migrations/agentFailureContext.js +22 -0
  154. package/dist/storage/migrations/currentInputContract.js +86 -0
  155. package/dist/storage/migrations/currentRuntimeContract.js +228 -0
  156. package/dist/storage/migrations/historicalVerificationPlan.js +35 -0
  157. package/dist/storage/migrations/integrationContinuation.js +5 -4
  158. package/dist/storage/migrations/narrowAgentFailureContext.js +65 -0
  159. package/dist/storage/migrations/notificationOnlyWakes.js +74 -0
  160. package/dist/storage/migrations/verificationPlanV1.js +162 -0
  161. package/dist/storage/migrations/verificationPolicy.js +74 -0
  162. package/dist/storage/migrations/workItemHistory.js +46 -0
  163. package/dist/storage/persistenceWorker.js +12 -4
  164. package/dist/storage/recordValidation.js +87 -0
  165. package/dist/storage/sqliteSchema.js +142 -1
  166. package/dist/storage/sqliteStore.js +156 -162
  167. package/dist/storage/storageSchema.js +2 -14
  168. package/dist/storage/storageVersions.js +1 -1
  169. package/dist/storage/storeRpc.js +15 -7
  170. package/dist/storage/taskStore.js +3 -7
  171. package/dist/storage/upgrade/upgradeOrchestrator.js +25 -37
  172. package/dist/task/completionReadiness.js +5 -24
  173. package/dist/task/draftPlan.js +0 -53
  174. package/dist/task/nextAction.js +7 -13
  175. package/dist/task/remoteDeliveryService.js +89 -0
  176. package/dist/task/taskActivation.js +2 -25
  177. package/dist/task/taskActivationService.js +0 -2
  178. package/dist/task/taskRecordReference.js +0 -1
  179. package/dist/task/taskSubmission.js +3 -14
  180. package/dist/telemetry/sqliteTelemetryBatch.js +29 -0
  181. package/dist/telemetry/sqliteTelemetryStore.js +49 -49
  182. package/dist/telemetry/telemetryWiring.js +4 -3
  183. package/dist/tmux/tmuxManager.js +29 -20
  184. package/dist/verification/gateArtifact.js +15 -24
  185. package/dist/verification/gateArtifactStore.js +14 -7
  186. package/dist/verification/verificationGateService.js +29 -88
  187. package/dist/verification/verificationPlan.js +27 -72
  188. package/dist/web/assets/client/app.js +1 -1
  189. package/dist/web/assets/client/taskSurface.js +4 -4
  190. package/dist/web/webServer.js +4 -6
  191. package/dist/web/webSnapshot.js +3 -83
  192. package/dist/web/webTaskSurface.js +17 -46
  193. package/dist/workItem/workItem.js +1 -12
  194. package/docs/architecture/capabilities-and-resources.md +14 -2
  195. package/docs/architecture/capabilities-and-resources.zh-CN.md +11 -1
  196. package/docs/managed-turn-and-session-runtime.md +83 -10
  197. package/docs/managed-turn-and-session-runtime.zh-CN.md +65 -11
  198. package/docs/plugin-sdk.md +4 -2
  199. package/docs/provider-runtime.md +3 -1
  200. package/docs/provider-runtime.zh-CN.md +4 -2
  201. package/docs/release-workflow.md +190 -7
  202. package/docs/release-workflow.zh-CN.md +139 -5
  203. package/docs/roles-and-configuration.md +29 -0
  204. package/docs/roles-and-configuration.zh-CN.md +21 -0
  205. package/docs/sqlite-control-plane-design.md +16 -3
  206. package/docs/sqlite-control-plane-design.zh-CN.md +13 -2
  207. package/docs/task-dag-semantics.md +3 -3
  208. package/docs/task-dag-semantics.zh-CN.md +2 -2
  209. package/docs/task-delivery.md +91 -5
  210. package/docs/task-delivery.zh-CN.md +72 -4
  211. package/docs/task-discovery.md +10 -11
  212. package/docs/task-discovery.zh-CN.md +8 -9
  213. package/docs/testing/verification-levels.md +145 -9
  214. package/docs/testing/verification-levels.zh-CN.md +103 -8
  215. package/package.json +1 -1
  216. package/skills/yui-leader/SKILL.md +5 -0
  217. package/skills/yui-leader/references/integration.md +9 -5
  218. package/skills/yui-leader/references/planning.md +9 -3
  219. package/skills/yui-operator/SKILL.md +15 -10
  220. package/skills/yui-runtime/SKILL.md +6 -6
  221. package/skills/yui-runtime/references/recovery.md +47 -0
  222. package/dist/agent/agentRegistry.js +0 -10
  223. package/dist/agentRun/runIdentity.js +0 -22
  224. package/dist/commands/deliveryGuardPreflight.js +0 -30
  225. package/dist/commands/taskIntegrationQueueCommands.js +0 -226
  226. package/dist/commands/taskOverviewCommand.js +0 -377
  227. package/dist/completion/completionWizard.js +0 -125
  228. package/dist/context/dispatchContext.js +0 -110
  229. package/dist/context/wakeNotification.js +0 -144
  230. package/dist/integration/integrationCheckEvidenceReuse.js +0 -53
  231. package/dist/integration/integrationQueueEntry.js +0 -191
  232. package/dist/integration/integrationQueueService.js +0 -810
  233. package/dist/release/fakeReleasePorts.js +0 -55
  234. package/dist/runtime/sessionOwnerRegistry.js +0 -137
  235. package/dist/task/deliveryGuard.js +0 -226
  236. /package/dist/{commands/taskActor.js → task/taskAuthority.js} +0 -0
@@ -1,31 +1,30 @@
1
1
  import { execFile, spawn } from "node:child_process";
2
- import { createHash } from "node:crypto";
3
- import { existsSync } from "node:fs";
4
2
  import { lstat, mkdir, open, rm, stat } from "node:fs/promises";
5
3
  import { dirname, join, relative, resolve, sep } from "node:path";
6
4
  import { promisify } from "node:util";
7
5
  import { selectEnvironment } from "../agent/launchEnvironment.js";
8
6
  import { controllerSocketPath } from "../core/controllerEndpoint.js";
9
7
  import { durableJobIdempotencyKey } from "../job/durableJob.js";
10
- import { applyIntegrationSource, assertIntegrationCandidate, assertRecordedSourceCandidate } from "./integrationSourceApplication.js";
11
- import { planBootstrapJobSteps, planL2JobSteps } from "../verification/verificationPlan.js";
12
- import { assertNoAdHocFullSuiteChecks, checkResultsFromGateArtifact, checkResultsFromGateJob, gateIdentityForCandidate, lookupReusableGateArtifact, recordGateArtifactFromJob, recordGateArtifactFromStepOutcomes, resolveVerificationGate, runGateStepsInProcess } from "../verification/verificationGateService.js";
13
- import { recordGateArtifactPotentialReuse, recordGateArtifactReuse } from "../verification/gateArtifact.js";
14
- import { touchGateArtifact } from "../verification/gateArtifactStore.js";
8
+ import { readRuntimeIdentity } from "../release/runtimeRelease.js";
15
9
  import { NodeGitWorkspace } from "../repository/gitWorkspace.js";
16
- import { integrationWorkspaceRoot } from "../repository/taskWorkspacePreparer.js";
17
10
  import { acquireProjectMaintenanceLocks } from "../repository/projectMaintenanceLock.js";
18
11
  import { taskWorkspaceRefSegment } from "../repository/taskWorkspaceIdentity.js";
12
+ import { integrationWorkspaceRoot } from "../repository/taskWorkspacePreparer.js";
13
+ import { ResourceRegistrar } from "../resources/resourceRegistrar.js";
19
14
  import { FileTaskRuntimeIsolation } from "../runtime/taskRuntimeIsolation.js";
20
- import { managedIntegrationRuntimeRoot } from "../storage/homeLayout.js";
15
+ import { integrationTmuxSocketRoot, managedIntegrationRuntimeRoot } from "../storage/homeLayout.js";
21
16
  import { advanceTaskProjectCommit } from "../task/task.js";
22
17
  import { yuiTmuxServerName } from "../tmux/tmuxManager.js";
23
- import { recordIntegrationCheckJob, updateIntegrationAttempt } from "./integrationAttempt.js";
18
+ import { recordGateArtifactReuse } from "../verification/gateArtifact.js";
19
+ import { touchGateArtifact } from "../verification/gateArtifactStore.js";
20
+ import { beginGateVerification, checkResultsFromGateArtifact, checkResultsFromGateJob, gateIdentityForCandidate, lookupReusableGateArtifact, recordGateArtifactFromJob, recordGateArtifactFromStepOutcomes, resolveVerificationGate, runGateStepsInProcess } from "../verification/verificationGateService.js";
21
+ import { planBootstrapJobSteps, planL2JobSteps } from "../verification/verificationPlan.js";
24
22
  import { createManagedWorkspace } from "../worktree/managedWorkspace.js";
25
- import { ResourceRegistrar } from "../resources/resourceRegistrar.js";
26
- import { readRuntimeIdentity } from "../release/runtimeRelease.js";
27
- import { findReusableIntegrationCheckEvidence, INTEGRATION_RUNTIME_RELEASE_ENV } from "./integrationCheckEvidenceReuse.js";
23
+ import { recordIntegrationCheckJob, updateIntegrationAttempt } from "./integrationAttempt.js";
24
+ import { applyIntegrationSource, assertIntegrationCandidate, assertRecordedSourceCandidate } from "./integrationSourceApplication.js";
28
25
  const executeFile = promisify(execFile);
26
+ /** Preserve the exact execution-environment fence in durable Job input digests. */
27
+ const INTEGRATION_RUNTIME_RELEASE_ENV = "YUI_INTEGRATION_RUNTIME_RELEASE_ID";
29
28
  const INTEGRATION_OPERATIONAL_ENVIRONMENT_NAMES = [
30
29
  "PATH",
31
30
  "USER",
@@ -421,26 +420,7 @@ export class GitIntegrationService {
421
420
  }
422
421
  await assertIntegrationCandidate(path, attempt.candidateCommit, workspace.branch);
423
422
  await assertTargetReadyForChecks(repositoryPath, attempt.targetRef, attempt.beforeCommit);
424
- const { runtime, head, steps, environment, inputDigest, releaseId } = await this.#checkSpecification(attempt, managedWorkspace, gate);
425
- const ownedJobs = this.store.listDurableJobs(attempt.taskId).filter(job => job.owner.kind === "integration-attempt" && job.owner.integrationAttemptId === attempt.id);
426
- if (gate === undefined && releaseId !== null && attempt.checkInputDigest === undefined
427
- && ownedJobs.length === 0 && attempt.jobId === undefined) {
428
- const reusable = findReusableIntegrationCheckEvidence({
429
- taskId: attempt.taskId,
430
- projectId: attempt.projectId,
431
- currentAttemptId: attempt.id,
432
- candidateCommit: head,
433
- checkCommands: attempt.checkCommands,
434
- runtimeReleaseId: releaseId,
435
- attempts: this.store.listIntegrationAttempts(attempt.taskId),
436
- jobs: this.store.listDurableJobs(attempt.taskId),
437
- logExists: (homeRelativePath) => existsSync(join(this.home, homeRelativePath)),
438
- logPathFor: (job, relativeLogPath) => (join(job.artifactsLocator, "logs", relativeLogPath))
439
- });
440
- if (reusable !== null) {
441
- return this.#finalizeGateSuccess(updateIntegrationAttempt(attempt, { checkInputDigest: inputDigest }, this.now()), workspace, repositoryPath, head, [...reusable.checks]);
442
- }
443
- }
423
+ const { runtime, head, steps, environment, inputDigest } = await this.#checkSpecification(attempt, managedWorkspace, gate);
444
424
  if (attempt.checkInputDigest !== undefined && attempt.checkInputDigest !== inputDigest) {
445
425
  throw new Error("Integration check conditions changed since admission; inspect the original Job, then abort or start a new attempt.");
446
426
  }
@@ -457,6 +437,14 @@ export class GitIntegrationService {
457
437
  })
458
438
  }, this.now());
459
439
  this.store.saveIntegrationAttempt(attempt.taskId, persisted);
440
+ if (gate !== undefined) {
441
+ // Resumption can enter here after persisting admission but before the
442
+ // original call withdrew old proof or recorded its Job id.
443
+ beginGateVerification(this.store, gateIdentityForCandidate({
444
+ projectId: attempt.projectId, gate, level: "L2", commit: head,
445
+ targetRef: attempt.targetRef, baseHead: attempt.beforeCommit
446
+ }), gate.plan, this.now());
447
+ }
460
448
  // All domain admission and durable candidate identity precede Job effects.
461
449
  this.runtimeIsolation.activate(runtime);
462
450
  await prepareIntegrationRuntimeHome(runtime, this.home);
@@ -520,7 +508,7 @@ export class GitIntegrationService {
520
508
  baseHead: attempt.beforeCommit
521
509
  });
522
510
  try {
523
- const artifact = await recordGateArtifactFromJob(this.store, this.home, identity, gate.plan, job, this.now());
511
+ const artifact = await this.#publishGateEvidence(workspace, job.head, () => recordGateArtifactFromJob(this.store, this.home, identity, gate.plan, job, this.now()));
524
512
  if (checks.every((check) => check.outcome !== "failed")) {
525
513
  checks.push(...checkResultsFromGateArtifact(artifact));
526
514
  }
@@ -562,11 +550,10 @@ export class GitIntegrationService {
562
550
  /**
563
551
  * Issue 08: the VerificationPlan gate for a configured Project.
564
552
  *
565
- * Enforce mode rejects ad-hoc full-suite checks before the gate. Reuse mode
566
- * returns an existing successful artifact for the same identity tuple
553
+ * Returns an existing successful artifact for the same identity tuple
567
554
  * (project + exact commit + plan digest + toolchain digest + target
568
- * boundary); record mode always runs and only counts shadow potential
569
- * reuses. The gate itself runs as bootstrap + L2 DurableJob steps (or
555
+ * boundary), unless this attempt explicitly requests fresh checks.
556
+ * The gate itself runs as bootstrap + L2 DurableJob steps (or
570
557
  * in-process when no Controller job port is available) and records a
571
558
  * self-contained GateArtifact. The final CAS in
572
559
  * {@link #finalizeGateSuccess} still fences a target that moves during the
@@ -574,19 +561,26 @@ export class GitIntegrationService {
574
561
  */
575
562
  async #runVerificationGate(attempt, workspace, path, managedWorkspace, repositoryPath, gate) {
576
563
  const spec = await this.#checkSpecification(attempt, managedWorkspace, gate);
564
+ const unsettled = this.store.listIntegrationAttempts(attempt.taskId).find(other => {
565
+ if (other.id === attempt.id || other.projectId !== attempt.projectId
566
+ || other.targetRef !== attempt.targetRef || other.beforeCommit !== attempt.beforeCommit
567
+ || other.candidateCommit !== attempt.candidateCommit
568
+ || other.gatePlanDigest !== gate.planDigest || other.gateToolchainDigest !== gate.toolchainDigest)
569
+ return false;
570
+ if (other.status === "running" || other.status === "validating")
571
+ return true;
572
+ const job = other.jobId === undefined ? null : this.store.getDurableJob(other.taskId, other.jobId);
573
+ return job !== null && (job.status === "queued" || job.status === "running"
574
+ || (job.status === "unknown-needs-attention" && job.acknowledgedAt === undefined));
575
+ });
576
+ if (unsettled !== undefined) {
577
+ throw new Error(`Exact verification is unsettled in ${unsettled.taskId}/${unsettled.id}; continue or settle its original Job before requesting another check.`);
578
+ }
577
579
  attempt = updateIntegrationAttempt(attempt, {
578
580
  checkInputDigest: spec.inputDigest,
579
581
  gatePlanDigest: gate.planDigest, gateToolchainDigest: gate.toolchainDigest
580
582
  }, this.now());
581
583
  this.store.saveIntegrationAttempt(attempt.taskId, attempt);
582
- if (gate.mode === "enforce") {
583
- try {
584
- assertNoAdHocFullSuiteChecks(gate.plan, attempt.checkCommands);
585
- }
586
- catch (error) {
587
- return this.#fail(attempt, error instanceof Error ? error : new Error(String(error)), "verification-plan", workspace);
588
- }
589
- }
590
584
  const candidateCommit = await gitLine(["-C", path, "rev-parse", "HEAD^{commit}"]);
591
585
  const identity = gateIdentityForCandidate({
592
586
  projectId: attempt.projectId,
@@ -596,7 +590,7 @@ export class GitIntegrationService {
596
590
  targetRef: attempt.targetRef,
597
591
  baseHead: attempt.beforeCommit
598
592
  });
599
- if (gate.mode !== "record") {
593
+ if (!attempt.rerunChecks && attempt.checkCommands.length === 0) {
600
594
  const existing = await lookupReusableGateArtifact(this.store, identity);
601
595
  if (existing !== null) {
602
596
  touchGateArtifact(this.store, recordGateArtifactReuse(existing, this.now()));
@@ -604,31 +598,22 @@ export class GitIntegrationService {
604
598
  return this.#finalizeGateSuccess(attempt, workspace, repositoryPath, candidateCommit, checks);
605
599
  }
606
600
  }
607
- else {
608
- // Record mode: observe the potential reuse without skipping the gate.
609
- const existing = await lookupReusableGateArtifact(this.store, identity);
610
- if (existing !== null) {
611
- touchGateArtifact(this.store, recordGateArtifactPotentialReuse(existing, this.now()));
612
- }
613
- }
614
601
  if (this.jobPort !== undefined) {
615
602
  return this.#startCheckJob(attempt, workspace, path, managedWorkspace, repositoryPath, gate);
616
603
  }
617
- // Jobless fallback (queue processing without a Controller): run the
604
+ beginGateVerification(this.store, identity, gate.plan, this.now());
605
+ // An explicit local caller without a Controller Job port runs the
618
606
  // plan gate in-process and record the artifact directly.
619
607
  const runtime = this.#runtimePreparation(attempt, managedWorkspace);
620
608
  this.runtimeIsolation.activate(runtime);
621
609
  let cleanupReason = "failure";
622
610
  try {
623
611
  await prepareIntegrationRuntimeHome(runtime, this.home);
624
- const steps = [
625
- ...planBootstrapJobSteps(gate.plan),
626
- ...planL2JobSteps(gate.plan)
627
- ];
612
+ const steps = spec.steps;
628
613
  const outcomes = await runGateStepsInProcess(path, steps, spec.environment, integrationCheckDirectory(this.home, attempt.taskId, attempt.id), candidateCommit);
629
614
  const succeeded = outcomes.length === steps.length
630
615
  && outcomes.every((outcome) => outcome.exitCode === 0 && outcome.signal === null && !outcome.timedOut);
631
- const artifact = await recordGateArtifactFromStepOutcomes(this.store, identity, gate.plan, outcomes, succeeded, this.now());
616
+ const artifact = await this.#publishGateEvidence(workspace, candidateCommit, () => recordGateArtifactFromStepOutcomes(this.store, identity, gate.plan, outcomes, succeeded, this.now()));
632
617
  const checks = checkResultsFromGateArtifact(artifact);
633
618
  cleanupReason = succeeded ? "completion" : "failure";
634
619
  if (!succeeded) {
@@ -642,6 +627,13 @@ export class GitIntegrationService {
642
627
  this.runtimeIsolation.cleanup(runtime, cleanupReason);
643
628
  }
644
629
  }
630
+ /** Both executors publish proof only for the exact clean candidate they
631
+ * checked. A later delivery/CAS failure is separate from this evidence, but
632
+ * a mutated candidate must never enter the reusable-success cache. */
633
+ async #publishGateEvidence(workspace, candidateCommit, publish) {
634
+ await assertIntegrationCandidate(workspace.path, candidateCommit, workspace.branch);
635
+ return publish();
636
+ }
645
637
  async #finalizeGateSuccess(attempt, workspace, repositoryPath, candidateCommit, checks) {
646
638
  await assertIntegrationCandidate(workspace.path, candidateCommit, workspace.branch);
647
639
  const validating = updateIntegrationAttempt(attempt, {
@@ -654,7 +646,7 @@ export class GitIntegrationService {
654
646
  const committed = this.#recordCommitted(validating);
655
647
  return this.#terminalResult("committed", committed, workspace);
656
648
  }
657
- #runtimePreparation(attempt, workspace) {
649
+ #runtimePreparation(_attempt, workspace) {
658
650
  return this.runtimeIsolation.preflight({
659
651
  workspace,
660
652
  allowExactActive: true
@@ -668,11 +660,12 @@ export class GitIntegrationService {
668
660
  const head = attempt.candidateCommit;
669
661
  if (head === undefined)
670
662
  throw new Error("Integration check specification requires a candidate.");
671
- const steps = gate === undefined
672
- ? attempt.checkCommands.map((command, index) => ({
673
- name: `check-${index + 1}`, command, timeoutMs: 30 * 60_000
674
- }))
675
- : [...planBootstrapJobSteps(gate.plan), ...planL2JobSteps(gate.plan)]
663
+ const explicitChecks = attempt.checkCommands.map((command, index) => ({
664
+ name: `check-${index + 1}`, command, timeoutMs: 30 * 60_000
665
+ }));
666
+ const steps = gate === undefined ? explicitChecks
667
+ : [...planBootstrapJobSteps(gate.plan, managedWorkspace.root),
668
+ ...planL2JobSteps(gate.plan, managedWorkspace.root), ...explicitChecks]
676
669
  .map(step => ({ ...step, timeoutMs: 30 * 60_000 }));
677
670
  const releaseId = integrationRuntimeReleaseIdentity(this.home);
678
671
  const environment = Object.freeze({
@@ -684,7 +677,7 @@ export class GitIntegrationService {
684
677
  projectId: attempt.projectId, head, workspace: managedWorkspace.root,
685
678
  env: environment, steps
686
679
  });
687
- return { runtime, head, steps, environment, inputDigest, releaseId };
680
+ return { runtime, head, steps, environment, inputDigest };
688
681
  }
689
682
  async #recoverValidating(attempt, workspace, managedWorkspace, repositoryPath) {
690
683
  if (attempt.candidateCommit === undefined || attempt.checks === undefined) {
@@ -805,11 +798,6 @@ function integrationSummary(attempt) {
805
798
  ? `Upstream ${attempt.source.remoteCommit} was already represented; Task head was unchanged.`
806
799
  : `Rebased Task changes onto upstream ${attempt.source.remoteCommit}.`;
807
800
  }
808
- if (attempt.source.kind === "historical-change-sets") {
809
- return unchanged
810
- ? "Historical Integration was already represented; Task head was unchanged."
811
- : "Historical Integration advanced the Task head.";
812
- }
813
801
  if (attempt.source.strategy === "manual") {
814
802
  const decision = attempt.resolution;
815
803
  if (decision?.action !== "manual-resolution") {
@@ -1036,18 +1024,6 @@ function defaultIntegrationRuntimeIsolation(home, homeId) {
1036
1024
  }
1037
1025
  });
1038
1026
  }
1039
- /**
1040
- * Short `/tmp` directory that holds ONLY the integration check's tmux socket
1041
- * endpoint. tmux uses a `sockaddr_un` path whose length budget cannot absorb a
1042
- * deep Home path, so this single IPC endpoint is the one approved exception to
1043
- * the unified-Home contract. All other integration runtime state (data, cache,
1044
- * ordinary temp) lives in the Home partition from `managedIntegrationRuntimeRoot`.
1045
- */
1046
- function integrationTmuxSocketRoot(home) {
1047
- const uid = typeof process.getuid === "function" ? process.getuid() : 0;
1048
- const homeDigest = createHash("sha256").update(resolve(home)).digest("hex").slice(0, 16);
1049
- return join("/tmp", `yi-${uid.toString(36)}-${homeDigest}`);
1050
- }
1051
1027
  /**
1052
1028
  * Reuse is enabled only for an immutable installed release. Development
1053
1029
  * checkouts have no content-addressed release identity, so they return.
@@ -12,6 +12,7 @@ export function createIntegrationAttempt(input, now) {
12
12
  source: input.source,
13
13
  beforeCommit: input.beforeCommit,
14
14
  checkCommands: normalizedUniqueText(input.checkCommands ?? [], "Integration check command"),
15
+ rerunChecks: input.rerunChecks ?? false,
15
16
  status: "running",
16
17
  createdAt: timestamp,
17
18
  updatedAt: timestamp
@@ -143,6 +144,8 @@ export function validateIntegrationAttempt(attempt) {
143
144
  requireCommit(attempt.beforeCommit, "Integration before commit");
144
145
  validateIntegrationSource(attempt.taskId, attempt.source);
145
146
  normalizedUniqueText(attempt.checkCommands, "Integration check command");
147
+ if (typeof attempt.rerunChecks !== "boolean")
148
+ throw new Error("Integration rerunChecks must be explicit.");
146
149
  if (attempt.candidateCommit !== undefined) {
147
150
  requireCommit(attempt.candidateCommit, "Integration candidate commit");
148
151
  }
@@ -239,20 +242,6 @@ function validateIntegrationSource(taskId, source) {
239
242
  }
240
243
  return;
241
244
  }
242
- if (source.kind === "historical-change-sets") {
243
- if (!Array.isArray(source.changeSetIds) || source.changeSetIds.length === 0) {
244
- throw new Error("Historical Integration source requires ChangeSet ids.");
245
- }
246
- const seen = new Set();
247
- for (const changeSetId of source.changeSetIds) {
248
- validateTaskRecordReference({ taskId, localId: changeSetId }, "changeSet");
249
- if (seen.has(changeSetId)) {
250
- throw new Error(`Historical Integration ChangeSet is duplicated: ${changeSetId}.`);
251
- }
252
- seen.add(changeSetId);
253
- }
254
- return;
255
- }
256
245
  throw new Error("Integration source is invalid.");
257
246
  }
258
247
  function normalizeConflictReport(report) {
@@ -33,9 +33,6 @@ export async function applyIntegrationSource(input) {
33
33
  });
34
34
  return pending;
35
35
  };
36
- if (source.kind === "historical-change-sets") {
37
- throw new Error("Historical Integration source has no current application proof; preserve evidence and abort explicitly.");
38
- }
39
36
  if (source.kind === "work-item" && source.strategy === "manual") {
40
37
  if (current.candidateCommit !== undefined) {
41
38
  await assertIntegrationCandidate(path, current.candidateCommit);
@@ -61,15 +58,11 @@ export async function applyIntegrationSource(input) {
61
58
  const branch = workspace.branch.startsWith("refs/") ? workspace.branch : `refs/heads/${workspace.branch}`;
62
59
  const active = await activeGitOperation(path);
63
60
  if (current.sourceProgress === undefined) {
64
- // Historical unfinished operations can be adopted only when Git still
65
- // carries their exact source/target identity. Finished old operations
66
- // without a receipt are deliberately not inferred from a clean tree.
61
+ // Only a pristine candidate can establish this attempt's first receipt.
62
+ // An unfinished operation without that receipt belongs to explicit recovery.
67
63
  const head = await line(path, "rev-parse", "HEAD");
68
64
  if (active !== undefined) {
69
- if (kind === "cherry-pick") {
70
- throw new Error("Historical cherry-pick has no durable completed-step cursor; preserve it and choose explicit recovery.");
71
- }
72
- await assertOperation(path, kind, commits[0], current.beforeCommit, branch, source);
65
+ throw new Error("Active Git operation has no attempt-owned progress receipt; preserve it and choose explicit recovery.");
73
66
  }
74
67
  else if (head !== current.beforeCommit || current.status === "conflicted") {
75
68
  throw new Error("Integration source completion is unproven: no attempt-owned Git receipt. Preserve the candidate; do not replay.");
@@ -79,8 +72,7 @@ export async function applyIntegrationSource(input) {
79
72
  }
80
73
  save({ sourceProgress: {
81
74
  workspace: resolve(path), branch, sourceDigest, completedSteps: 0,
82
- head: current.beforeCommit,
83
- ...(active === undefined ? {} : { activeAction: actionId() })
75
+ head: current.beforeCommit
84
76
  } });
85
77
  }
86
78
  let progress = current.sourceProgress;
@@ -77,7 +77,7 @@ function isCommandPath(lowerPath, segments, lowerFile) {
77
77
  || lowerFile.startsWith("cli")
78
78
  || lowerPath.includes("/bin/");
79
79
  }
80
- function isContractPath(lowerPath, segments, lowerFile) {
80
+ function isContractPath(lowerPath, _segments, lowerFile) {
81
81
  return lowerFile === "index.ts"
82
82
  || lowerFile === "index.js"
83
83
  || lowerFile === "index.mjs"
@@ -87,7 +87,7 @@ function isContractPath(lowerPath, segments, lowerFile) {
87
87
  || lowerPath.includes("/contract/")
88
88
  || lowerPath.includes("/contracts/");
89
89
  }
90
- function isTestPath(lowerPath, segments, lowerFile) {
90
+ function isTestPath(_lowerPath, segments, lowerFile) {
91
91
  if (segments.includes("test") || segments.includes("tests") || segments.includes("__tests__")) {
92
92
  return true;
93
93
  }
@@ -16,27 +16,9 @@ export function isDurableJobTerminal(status) {
16
16
  }
17
17
  export function createDurableJob(input, now) {
18
18
  const timestamp = now.toISOString();
19
- const contentKey = input.retryOf === undefined
20
- ? durableJobIdempotencyKey({
21
- owner: input.owner,
22
- projectId: input.projectId,
23
- head: input.head,
24
- steps: input.steps,
25
- workspace: input.workspace,
26
- env: input.env
27
- })
28
- : retryDurableJobIdempotencyKey(durableJobIdempotencyKey({
29
- owner: input.owner,
30
- projectId: input.projectId,
31
- head: input.head,
32
- steps: input.steps,
33
- workspace: input.workspace,
34
- env: input.env
35
- }), input.retryOf);
36
- const idempotencyKey = input.operation === undefined ? contentKey
37
- : createHash("sha256").update(JSON.stringify([
38
- input.operation.actorId, input.operation.requestId
39
- ])).digest("hex");
19
+ const idempotencyKey = createHash("sha256").update(JSON.stringify([
20
+ input.operation.actorId, input.operation.requestId
21
+ ])).digest("hex");
40
22
  return validateDurableJob({
41
23
  schemaVersion: CURRENT_DURABLE_JOB_SCHEMA_VERSION,
42
24
  id: input.id,
@@ -49,10 +31,7 @@ export function createDurableJob(input, now) {
49
31
  steps: input.steps.map((step) => ({ ...step })),
50
32
  idempotencyKey,
51
33
  operation: {
52
- requestId: input.operation?.requestId ?? idempotencyKey,
53
- inputDigest: input.operation?.inputDigest ?? idempotencyKey,
54
- actorId: input.operation?.actorId ?? "internal:job",
55
- authorityRef: input.operation?.authorityRef ?? "internal:job",
34
+ ...input.operation,
56
35
  targetId: input.workspace,
57
36
  capability: "job.start",
58
37
  implementation: JOB_RUNNER_IMPLEMENTATION,
@@ -0,0 +1,22 @@
1
+ import { resolve } from "node:path";
2
+ export class JobAssignmentScopeError extends Error {
3
+ }
4
+ /** Authentication identifies the current Session; it does not grant another
5
+ * Assignment's workspace. Share this check across admission, management and
6
+ * the last pre-spawn boundary. Existing running Jobs settle independently. */
7
+ export function assertJobAssignmentScope(store, target, caller) {
8
+ if (caller.scope !== "task" || caller.role === "leader")
9
+ return;
10
+ const run = caller.role === undefined ? null : store.getActiveRun(target.taskId, caller.role);
11
+ const workspace = run?.workspace ?? run?.effective.workspace;
12
+ const item = run?.workItemId === undefined ? null : store.getWorkItem(target.taskId, run.workItemId);
13
+ if (run?.status !== "active" || run.reviewRoundId !== undefined
14
+ || target.owner.kind !== "work-item" || target.owner.workItemId !== run.workItemId
15
+ || item?.status !== "open" || item.assignee !== caller.role
16
+ || !item.writeProjectIds.includes(target.projectId)
17
+ || !run.effective.writeProjectIds.includes(target.projectId)
18
+ || workspace === undefined || resolve(workspace.root) !== resolve(target.workspace)
19
+ || !workspace.entries.some(entry => entry.projectId === target.projectId && entry.access === "write")) {
20
+ throw new JobAssignmentScopeError("Job is outside the caller's current Assignment. Use the exact assigned WorkItem workspace and writable Project; this Job owner cannot represent a Review or replica Lane.");
21
+ }
22
+ }
@@ -0,0 +1,16 @@
1
+ /** A read model over the one Job, never separately persisted.
2
+ * confirmed means the selected runner was observed, not that every action
3
+ * performed by an arbitrary command succeeded. Inspect original step receipts.
4
+ */
5
+ export function inspectJobOperation(job) {
6
+ return {
7
+ operationRef: { taskId: job.taskId, jobId: job.id },
8
+ ...job.operation,
9
+ state: job.status === "queued" ? "pending"
10
+ : job.status === "running" ? "running"
11
+ : job.status === "unknown-needs-attention" ? "unknown" : "finished",
12
+ outcome: job.result?.outcome,
13
+ result: job.result,
14
+ checkpoint: job.checkpoint
15
+ };
16
+ }
@@ -11,6 +11,7 @@ import { spawn } from "node:child_process";
11
11
  import { chmodSync, closeSync, existsSync, mkdirSync, openSync, readFileSync, utimesSync, writeFileSync } from "node:fs";
12
12
  import { join, resolve } from "node:path";
13
13
  import { fileURLToPath } from "node:url";
14
+ import { jobStepDirectory } from "./stepDirectory.js";
14
15
  import { writeTextFileAtomically } from "../storage/durableFile.js";
15
16
  import { readLinuxProcessStartIdentity } from "../controller/domainIdentity.js";
16
17
  const DEFAULT_STEP_TIMEOUT_MS = 30 * 60_000;
@@ -129,7 +130,7 @@ export async function runDurableJobRunner(specPath) {
129
130
  // Issue 08: a structured argv step executes without a shell so its
130
131
  // tokens can never be reinterpreted; a legacy command step keeps the
131
132
  // shell form. A per-step cwd/env override applies to either form.
132
- const stepCwd = step.cwd ?? spec.workspace;
133
+ const stepCwd = jobStepDirectory(spec.workspace, step.cwd);
133
134
  const stepEnv = step.env === undefined
134
135
  ? spec.env
135
136
  : { ...spec.env, ...step.env };
@@ -0,0 +1,15 @@
1
+ import { realpathSync } from "node:fs";
2
+ import { isAbsolute, relative, resolve, sep } from "node:path";
3
+ /** Recheck at execution, including directories created by a bootstrap step. */
4
+ export function jobStepDirectory(workspace, override) {
5
+ const root = realpathSync(workspace);
6
+ const cwd = override ?? workspace;
7
+ if (!isAbsolute(cwd))
8
+ throw new Error("Job step cwd must be absolute.");
9
+ const actual = realpathSync(cwd);
10
+ const nested = relative(root, actual);
11
+ if (nested === ".." || nested.startsWith(`..${sep}`) || isAbsolute(nested)) {
12
+ throw new Error(`Job step cwd is outside its managed workspace: ${resolve(cwd)}.`);
13
+ }
14
+ return actual;
15
+ }
@@ -1,13 +1,14 @@
1
- import { updateTaskMetadataCommand, sendTaskMessageCommand } from "../commands/taskCommands.js";
1
+ import { listArtifactsCapability, readArtifactCapability, saveArtifactCapability } from "../artifacts/artifactCapability.js";
2
2
  import { runConfigCommand } from "../commands/configCommands.js";
3
- import { readTaskContext, readTaskContextDelta, inspectTaskContext, withContextObservations } from "../context/taskContext.js";
3
+ import { sendTaskMessageCommand, updateTaskMetadataCommand } from "../commands/taskCommands.js";
4
4
  import { CONFIG_DOMAINS } from "../config/configCatalog.js";
5
+ import { inspectTaskContext, readTaskContext, readTaskContextDelta, withContextObservations } from "../context/taskContext.js";
5
6
  import { createJobCallAuthority, parseDurableJobStartParams } from "../controller/jobControl.js";
6
- import { inspectJobOperation } from "./kernelPorts.js";
7
- import { CapabilityRegistry, } from "./capabilityRegistry.js";
8
- import { createProjectResources } from "../resources/projectResourceService.js";
9
- import { saveArtifactCapability, readArtifactCapability, listArtifactsCapability } from "../artifacts/artifactCapability.js";
7
+ import { inspectJobOperation } from "../job/jobOperation.js";
8
+ import { assertJobAssignmentScope } from "../job/jobAssignmentScope.js";
10
9
  import { createPluginService } from "../plugins/pluginService.js";
10
+ import { createProjectResources } from "../resources/projectResourceService.js";
11
+ import { CapabilityRegistry, } from "./capabilityRegistry.js";
11
12
  const text = { type: "string", minLength: 1 };
12
13
  const strings = { type: "object", additionalProperties: { type: "string" } };
13
14
  const object = (properties, required = Object.keys(properties)) => ({
@@ -26,7 +27,6 @@ const definitions = [
26
27
  effect: "local-mutation", requiredPermissions: ["task:read"], source: "sendTaskMessageCommand",
27
28
  inputSchema: object({ taskId: text, body: text,
28
29
  intent: { enum: ["record", "discuss", "develop"] },
29
- wakePolicy: { enum: ["leader", "none"] },
30
30
  recipient: object({ roleName: text, workItemId: text, reviewRoundId: text }, ["roleName"])
31
31
  }, ["taskId", "body"]), outputSchema: recordOutput
32
32
  },
@@ -170,7 +170,8 @@ const definitions = [
170
170
  ] },
171
171
  env: strings,
172
172
  steps: { type: "array", minItems: 1, items: object({
173
- name: text, command: text, timeoutMs: { type: "integer" }
173
+ name: text, command: text, timeoutMs: { type: "integer" },
174
+ argv: { type: "array", minItems: 1, items: text }, cwd: text, env: strings
174
175
  }, ["name", "command"]) },
175
176
  retryOf: text
176
177
  }, ["taskId", "projectId", "head", "workspace", "owner", "env", "steps"]),
@@ -247,7 +248,7 @@ export function createBuiltinCapabilities(host, store, jobs, signal = () => unde
247
248
  // gains develop from an intent argument; only a global Operator Session
248
249
  // (user-authority) may carry one. sendTaskMessageCommand enforces this,
249
250
  // so the capability just forwards the optional intent unchanged.
250
- const result = sendTaskMessageCommand(store, taskId, params.body, params.wakePolicy, { environment: callerEnvironment(caller) }, params.recipient, params.intent);
251
+ const result = sendTaskMessageCommand(store, taskId, params.body, { environment: callerEnvironment(caller) }, params.recipient, params.intent);
251
252
  signal(taskId);
252
253
  return result.message;
253
254
  }
@@ -338,11 +339,11 @@ export function createBuiltinCapabilities(host, store, jobs, signal = () => unde
338
339
  ...(patch.tags?.length === 0 ? { tags: null } : {})
339
340
  }, {
340
341
  environment: callerEnvironment(caller),
341
- runtime: { notifyStateChanged: signal, reconcileTask: signal }
342
+ runtime: { notifyMailboxChanged: () => signal(taskId), reconcileTask: signal }
342
343
  });
343
344
  }
344
345
  if (name === "job.get") {
345
- const job = jobs.getJob(taskId, params.jobId);
346
+ const job = jobs.getJob(taskId, params.jobId, caller);
346
347
  if (!job)
347
348
  throw new Error(`Job not found: ${taskId}/${params.jobId}.`);
348
349
  const operation = inspectJobOperation(job);
@@ -379,6 +380,9 @@ export function createBuiltinCapabilities(host, store, jobs, signal = () => unde
379
380
  if (sessions?.sessions[sessions.activeAgentId]?.effective.executionAuthority !== "delivery") {
380
381
  throw new Error("Planning Sessions cannot start delivery Jobs.");
381
382
  }
383
+ if (descriptor?.name === "job.start" && input !== undefined) {
384
+ assertJobAssignmentScope(store, input, caller);
385
+ }
382
386
  }
383
387
  if (permission === "task:read" || permission === "job:start")
384
388
  continue;
@@ -1,7 +1,7 @@
1
1
  import { createDurableJobControl } from "../controller/jobControl.js";
2
2
  import { JOB_RUNNER_IMPLEMENTATION } from "../job/durableJob.js";
3
- import { InstanceHost } from "./instanceHost.js";
4
3
  import { createBuiltinCapabilities } from "./builtinCapabilities.js";
4
+ import { InstanceHost } from "./instanceHost.js";
5
5
  /** Called once by the existing Controller root. Does not open a Store, start
6
6
  * another Controller, or provide arbitrary persistence to plugin code.
7
7
  * T02 registers contributions on this Host and wraps this same Job control.
@@ -29,19 +29,3 @@ export function createKernelPorts(store, runner, signal = () => undefined, conte
29
29
  }
30
30
  };
31
31
  }
32
- /** A read model over the one Job, never separately persisted.
33
- * confirmed means the selected runner was observed, not that every action
34
- * performed by an arbitrary command succeeded. Inspect original step receipts.
35
- */
36
- export function inspectJobOperation(job) {
37
- return {
38
- operationRef: { taskId: job.taskId, jobId: job.id },
39
- ...job.operation,
40
- state: job.status === "queued" ? "pending"
41
- : job.status === "running" ? "running"
42
- : job.status === "unknown-needs-attention" ? "unknown" : "finished",
43
- outcome: job.result?.outcome,
44
- result: job.result,
45
- checkpoint: job.checkpoint
46
- };
47
- }
@@ -8,7 +8,6 @@ import { boundedRunFailureDiagnostic } from "../domain/agentResultTransport.js";
8
8
  import { updateExecutionLane, updateWorkItemExecutionLane } from "../execution/workItemExecution.js";
9
9
  import { managedProviderTurnId } from "../runtime/providerRuntimeIdentity.js";
10
10
  import { latestRunDurableProgressAt } from "../scheduler/roleRunStall.js";
11
- import { markTaskWakeConsumed } from "../scheduler/taskWake.js";
12
11
  import { workItemExecutionGroupById, updateWorkItemExecutionGroup } from "../workItem/workItem.js";
13
12
  /**
14
13
  * Validate every immutable identity and frozen Project head needed before a
@@ -81,7 +80,7 @@ export function validateExactRunReviewRound(store, run, options = {}) {
81
80
  }
82
81
  }
83
82
  const task = store.getTask(run.taskId);
84
- const taskScope = (round.scope ?? "work-item") === "task";
83
+ const taskScope = round.scope === "task";
85
84
  const item = taskScope || round.workItemId === undefined
86
85
  ? null
87
86
  : store.getWorkItem(run.taskId, round.workItemId);
@@ -410,13 +409,6 @@ export function terminalizeExactTaskRun(store, input, now) {
410
409
  roleName: terminal.roleName
411
410
  }), dispatchIdentity);
412
411
  settleRoleRunDispatch(store, dispatchIdentity, dispatchToken);
413
- if (terminal.roleName === "leader") {
414
- const wake = store.listTaskWakes(input.taskId)
415
- .find((candidate) => candidate.runId === terminal.id && candidate.status === "dispatched");
416
- if (wake !== undefined) {
417
- store.saveTaskWake(input.taskId, markTaskWakeConsumed(wake, now));
418
- }
419
- }
420
412
  if (terminal.executionGroupId !== undefined && terminal.executionLaneId !== undefined) {
421
413
  store.clearActiveExecutionLaneRun(input.taskId, terminal.executionGroupId, terminal.executionLaneId);
422
414
  }
@@ -36,9 +36,8 @@ function resolveInputControl(sessions, kind, expectedTarget, roleName) {
36
36
  detail: `Agent plan '${active.adapterId}' has no native interrupt; Yui will not `
37
37
  + "stop the owned process, kill, restart, or detach it." };
38
38
  }
39
- // A Global Role now carries the same optional providerBinding as a Task Role;
40
- // both read it the same way. Its absence is "no current Turn", never an error.
41
- const binding = "providerBinding" in sessions ? sessions.providerBinding ?? null : null;
39
+ // Both scopes use the same explicit null for an uncontrolled Session.
40
+ const binding = sessions.providerBinding;
42
41
  const turn = binding?.run ?? null;
43
42
  if (binding === null || turn === null || !PRESENT_TURN_STATUSES.has(turn.status)) {
44
43
  return { outcome: "no-active-turn", code: "NO_ACTIVE_TURN",
@@ -78,7 +77,7 @@ function resolveInputControl(sessions, kind, expectedTarget, roleName) {
78
77
  ...(turn.nativeTurnId === undefined ? {} : { nativeTurnId: turn.nativeTurnId }),
79
78
  attemptId: turn.attemptId,
80
79
  authority: { epoch: binding.authority.epoch, owner: "controller",
81
- holderId: binding.authority.holderId ?? "controller" }
80
+ holderId: binding.authority.holderId }
82
81
  } };
83
82
  }
84
83
  /**