@zq-silk/yui 0.16.1 → 1.0.0-alpha

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 (193) hide show
  1. package/ARCHITECTURE.md +12 -0
  2. package/ARCHITECTURE.zh-CN.md +8 -0
  3. package/README.md +9 -4
  4. package/dist/agent/agent.js +4 -9
  5. package/dist/agent/executionComponents.js +4 -4
  6. package/dist/agent/managedRuntimeEnvironment.js +0 -4
  7. package/dist/agentRun/agentRun.js +13 -23
  8. package/dist/artifacts/managedGit.js +3 -56
  9. package/dist/brief/taskBrief.js +3 -3
  10. package/dist/cli/agentConfigurationPicker.js +24 -21
  11. package/dist/cli/commandCatalog.js +18 -10
  12. package/dist/cli/roleWizard.js +41 -30
  13. package/dist/cli/updateCommand.js +35 -13
  14. package/dist/cli/updateOrchestrator.js +73 -44
  15. package/dist/cli/updatePorts.js +76 -86
  16. package/dist/cli/upgradeCommand.js +4 -8
  17. package/dist/cli.js +39 -33
  18. package/dist/commands/controllerCommands.js +2 -2
  19. package/dist/commands/executionAuditCommands.js +2 -2
  20. package/dist/commands/projectCommands.js +1 -1
  21. package/dist/commands/releaseCommands.js +9 -38
  22. package/dist/commands/taskCommands.js +91 -81
  23. package/dist/commands/taskCompletionGate.js +0 -117
  24. package/dist/commands/taskRoleRuntimeStatus.js +2 -2
  25. package/dist/commands/taskUpstreamCommands.js +93 -54
  26. package/dist/context/runContextPack.js +30 -54
  27. package/dist/context/runInputContract.js +9 -0
  28. package/dist/context/sessionBootstrapManifest.js +11 -19
  29. package/dist/controller/agentHostObservation.js +4 -2
  30. package/dist/controller/agentRuntimeObserver.js +4 -4
  31. package/dist/controller/clientRuntime.js +19 -44
  32. package/dist/controller/controller.js +24 -10
  33. package/dist/controller/fileSchedulerStoreAdapter.js +91 -24
  34. package/dist/controller/globalInputDelivery.js +31 -8
  35. package/dist/controller/globalRuntimeAttention.js +34 -0
  36. package/dist/controller/jobSupervisor.js +3 -3
  37. package/dist/controller/operatorNotification.js +31 -0
  38. package/dist/controller/providerRetryAdmission.js +3 -1
  39. package/dist/controller/providerRetryDelivery.js +136 -119
  40. package/dist/controller/runtime.js +2 -28
  41. package/dist/controller/sessionOwnerReconciliation.js +42 -24
  42. package/dist/controller/structuredProviderObservation.js +28 -1
  43. package/dist/controller/updateReconciliation.js +72 -20
  44. package/dist/coordination/workMailbox.js +4 -4
  45. package/dist/core/controllerIdentity.js +25 -0
  46. package/dist/core/controllerProcessIdentity.js +2 -2
  47. package/dist/core/controllerServer.js +4 -2
  48. package/dist/core/protocol.js +1 -1
  49. package/dist/doctor/doctor.js +2 -1
  50. package/dist/domain/validation.js +6 -0
  51. package/dist/event/taskEvent.js +3 -3
  52. package/dist/execution/workItemExecution.js +2 -2
  53. package/dist/executor/agentAdapter.js +28 -96
  54. package/dist/executor/agentConfigurationCatalog.js +13 -68
  55. package/dist/executor/agentConfigurationFields.js +120 -0
  56. package/dist/executor/agentConfigurationProbe.js +55 -80
  57. package/dist/executor/agentExecutor.js +11 -8
  58. package/dist/executor/effectiveLaunch.js +3 -3
  59. package/dist/executor/executorRegistry.js +0 -9
  60. package/dist/executor/fileRoleLaunchPlanner.js +12 -11
  61. package/dist/grant/capabilityGrant.js +2 -2
  62. package/dist/input/inputRequest.js +4 -4
  63. package/dist/integration/changeSet.js +3 -3
  64. package/dist/integration/integrationAttempt.js +3 -3
  65. package/dist/integration/integrationSourceApplication.js +1 -1
  66. package/dist/interaction/operatorPresentation.js +2 -1
  67. package/dist/job/durableJob.js +1 -1
  68. package/dist/job/jobRunner.js +2 -2
  69. package/dist/message/message.js +8 -6
  70. package/dist/message/messageContinuation.js +8 -9
  71. package/dist/milestone/milestone.js +3 -3
  72. package/dist/observability/executionAudit.js +1 -10
  73. package/dist/observability/runtimeIdentity.js +0 -23
  74. package/dist/output/agentConfigurationPresentation.js +4 -2
  75. package/dist/profile/agentProfile.js +3 -3
  76. package/dist/release/releaseHandover.js +2 -2
  77. package/dist/release/releaseIdempotencyStore.js +0 -23
  78. package/dist/release/releaseWorkflowPorts.js +8 -4
  79. package/dist/release/runtimeRelease.js +9 -7
  80. package/dist/repository/gitWorkspace.js +66 -19
  81. package/dist/repository/project.js +3 -3
  82. package/dist/repository/taskWorkspacePreparer.js +75 -68
  83. package/dist/resources/liveReferences.js +1 -1
  84. package/dist/resources/resourceRegistry.js +90 -44
  85. package/dist/resources/sqliteResourceRegistry.js +5 -7
  86. package/dist/review/reviewRound.js +5 -5
  87. package/dist/role/role.js +7 -8
  88. package/dist/runtime/acpProtocol.js +2 -3
  89. package/dist/runtime/acpSession.js +21 -5
  90. package/dist/runtime/agentDriverObservation.js +1 -1
  91. package/dist/runtime/agentEndpoint.js +4 -1
  92. package/dist/runtime/agentHost.js +25 -65
  93. package/dist/runtime/agentHostCleanup.js +85 -0
  94. package/dist/runtime/agentHostProtocol.js +3 -3
  95. package/dist/runtime/builtinAgentDrivers.js +46 -119
  96. package/dist/runtime/builtinTranscriptObserver.js +10 -6
  97. package/dist/runtime/codexAppServerRuntime.js +55 -89
  98. package/dist/runtime/codexInteractiveHost.js +1 -1
  99. package/dist/runtime/jsonLineChannel.js +35 -7
  100. package/dist/runtime/launchBroker.js +1 -1
  101. package/dist/runtime/processExitObservation.js +1 -1
  102. package/dist/runtime/providerContinuationReconciliationService.js +86 -38
  103. package/dist/runtime/providerRetry.js +15 -0
  104. package/dist/runtime/providerRuntimeIdentity.js +3 -3
  105. package/dist/runtime/providerRuntimeReconciler.js +12 -2
  106. package/dist/runtime/runtimeCoherence.js +7 -3
  107. package/dist/runtime/runtimeObservation.js +3 -3
  108. package/dist/runtime/sessionOwnerIdentity.js +1 -1
  109. package/dist/runtime/structuredProviderHost.js +106 -81
  110. package/dist/runtime/taskRuntimeIsolation.js +3 -3
  111. package/dist/runtime/tmuxAdapters.js +3 -3
  112. package/dist/scheduler/activeRoleRunDelivery.js +5 -1
  113. package/dist/scheduler/operatorInputNotificationProcessor.js +7 -17
  114. package/dist/scheduler/taskWake.js +1 -1
  115. package/dist/storage/baselineSchema.js +606 -0
  116. package/dist/storage/homeLayout.js +5 -16
  117. package/dist/storage/recordValidation.js +16 -4
  118. package/dist/storage/sqliteSchema.js +106 -1741
  119. package/dist/storage/sqliteStore.js +16 -14
  120. package/dist/storage/storageSchema.js +8 -7
  121. package/dist/storage/storageVersions.js +23 -16
  122. package/dist/storage/taskStore.js +3 -29
  123. package/dist/storage/upgrade/upgradeOrchestrator.js +14 -112
  124. package/dist/task/nextAction.js +33 -23
  125. package/dist/task/task.js +17 -8
  126. package/dist/task/taskActivation.js +5 -4
  127. package/dist/telemetry/sqliteTelemetryStore.js +2 -2
  128. package/dist/verification/gateArtifact.js +5 -3
  129. package/dist/verification/verificationPlan.js +6 -7
  130. package/dist/web/assets/assetManifest.js +2 -0
  131. package/dist/web/assets/client/app.js +72 -3
  132. package/dist/web/assets/client/components.js +2 -1
  133. package/dist/web/assets/client/i18n.js +2 -0
  134. package/dist/web/assets/client/taskSummary.js +345 -0
  135. package/dist/web/assets/client/taskSurface.js +44 -26
  136. package/dist/web/assets/client/view.js +33 -0
  137. package/dist/web/assets/shell.js +1 -0
  138. package/dist/web/assets/styles/cards.js +21 -0
  139. package/dist/web/webServer.js +39 -5
  140. package/dist/web/webSessions.js +165 -0
  141. package/dist/web/webSnapshot.js +23 -0
  142. package/dist/web/webTaskSurface.js +23 -0
  143. package/dist/workItem/workItem.js +6 -6
  144. package/dist/workspace/cleanupInspection.js +1 -9
  145. package/dist/worktree/managedWorkspace.js +3 -3
  146. package/docs/managed-turn-and-session-runtime.md +50 -7
  147. package/docs/managed-turn-and-session-runtime.zh-CN.md +36 -5
  148. package/docs/observability/README.md +47 -0
  149. package/docs/observability/README.zh-CN.md +37 -0
  150. package/docs/project-refresh.md +9 -0
  151. package/docs/project-refresh.zh-CN.md +8 -0
  152. package/docs/provider-retry.md +35 -0
  153. package/docs/release-workflow.md +70 -214
  154. package/docs/release-workflow.zh-CN.md +53 -157
  155. package/docs/roles-and-configuration.md +30 -0
  156. package/docs/roles-and-configuration.zh-CN.md +20 -0
  157. package/docs/sqlite-control-plane-design.md +48 -289
  158. package/docs/sqlite-control-plane-design.zh-CN.md +37 -53
  159. package/docs/storage-baseline.md +132 -0
  160. package/docs/storage-baseline.zh-CN.md +106 -0
  161. package/docs/task-delivery.md +110 -2
  162. package/docs/task-delivery.zh-CN.md +82 -2
  163. package/docs/task-discovery.md +9 -0
  164. package/docs/task-discovery.zh-CN.md +6 -0
  165. package/docs/testing/verification-levels.md +42 -164
  166. package/docs/testing/verification-levels.zh-CN.md +29 -115
  167. package/i18n/README.zh-CN.md +7 -4
  168. package/package.json +1 -1
  169. package/skills/yui-leader/SKILL.md +11 -0
  170. package/skills/yui-leader/references/execution.md +22 -0
  171. package/skills/yui-leader/references/planning.md +6 -2
  172. package/skills/yui-operator/SKILL.md +45 -13
  173. package/skills/yui-operator/references/task-delivery.md +99 -0
  174. package/skills/yui-runtime/SKILL.md +5 -3
  175. package/skills/yui-runtime/references/publication.md +56 -5
  176. package/skills/yui-runtime/references/recovery.md +12 -2
  177. package/dist/runtime/agentHostCompatibility.js +0 -127
  178. package/dist/storage/migrations/agentFailureContext.js +0 -22
  179. package/dist/storage/migrations/agentRunContract.js +0 -159
  180. package/dist/storage/migrations/artifactsToGit.js +0 -338
  181. package/dist/storage/migrations/collapseWorktreeLayout.js +0 -963
  182. package/dist/storage/migrations/currentInputContract.js +0 -86
  183. package/dist/storage/migrations/currentRuntimeContract.js +0 -228
  184. package/dist/storage/migrations/historicalVerificationPlan.js +0 -35
  185. package/dist/storage/migrations/integrationContinuation.js +0 -105
  186. package/dist/storage/migrations/narrowAgentFailureContext.js +0 -65
  187. package/dist/storage/migrations/notificationOnlyWakes.js +0 -74
  188. package/dist/storage/migrations/removeRuntimeGeneration.js +0 -207
  189. package/dist/storage/migrations/submitIntent.js +0 -126
  190. package/dist/storage/migrations/unifyHomeLayout.js +0 -925
  191. package/dist/storage/migrations/verificationPlanV1.js +0 -162
  192. package/dist/storage/migrations/verificationPolicy.js +0 -74
  193. package/dist/storage/migrations/workItemHistory.js +0 -46
@@ -61,71 +61,110 @@ async function integrateUpstream(args, store, home, options) {
61
61
  }
62
62
  const service = new GitIntegrationService(store.rootDirectory(), store, git, now, options.environment, undefined, options.jobPort);
63
63
  const results = [];
64
- for (const binding of task.projectBindings) {
65
- if (projectRef !== undefined && binding.projectId !== projectRef)
66
- continue;
67
- const project = store.getProject(binding.projectId);
68
- if (project === null)
69
- throw usageError(`Project not found: ${binding.projectId}.`);
70
- if (project.remoteUrl === undefined) {
71
- throw usageError(`Project has no remote URL: ${project.id}.`);
72
- }
73
- if (binding.baseCommit === undefined || binding.currentCommit === undefined) {
74
- throw usageError(`Task Project has no activated commit boundary: ${task.id}/${project.id}.`);
75
- }
76
- const entry = workspaceProjectEntry(workspace, project.id);
77
- if (entry === undefined) {
78
- throw usageError(`Task workspace has no entry for Project: ${project.id}.`);
79
- }
80
- const head = (await git.inspect(entry.path, entry.branch)).baseCommit;
81
- if (head !== binding.currentCommit) {
82
- throw usageError(`Task Project current commit diverged from its main clone: ${task.id}/${project.id}.`);
64
+ let failure;
65
+ const selected = task.projectBindings.filter(binding => projectRef === undefined || binding.projectId === projectRef);
66
+ if (projectRef !== undefined && selected.length === 0) {
67
+ throw usageError(`Task Project not found: ${task.id}/${projectRef}.`);
68
+ }
69
+ for (const binding of selected) {
70
+ let phase = "prepare";
71
+ let integrationId;
72
+ try {
73
+ const project = store.getProject(binding.projectId);
74
+ if (project === null)
75
+ throw usageError(`Project not found: ${binding.projectId}.`);
76
+ if (project.remoteUrl === undefined) {
77
+ throw usageError(`Project has no remote URL: ${project.id}.`);
78
+ }
79
+ if (binding.baseCommit === undefined || binding.currentCommit === undefined) {
80
+ throw usageError(`Task Project has no activated commit boundary: ${task.id}/${project.id}.`);
81
+ }
82
+ const entry = workspaceProjectEntry(workspace, project.id);
83
+ if (entry === undefined) {
84
+ throw usageError(`Task workspace has no entry for Project: ${project.id}.`);
85
+ }
86
+ const head = (await git.inspect(entry.path, entry.branch)).baseCommit;
87
+ if (head !== binding.currentCommit) {
88
+ throw usageError(`Task Project current commit diverged from its main clone: ${task.id}/${project.id}.`);
89
+ }
90
+ phase = "resolve-remote";
91
+ const remote = await git.resolveRemoteHead({
92
+ remoteUrl: project.remoteUrl,
93
+ branch: binding.baseRef
94
+ });
95
+ const attempt = store.transaction((tx) => {
96
+ const created = createIntegrationAttempt({
97
+ id: tx.nextIntegrationAttemptId(task.id),
98
+ taskId: task.id,
99
+ projectId: project.id,
100
+ targetRef: entry.branch,
101
+ beforeCommit: binding.currentCommit,
102
+ source: {
103
+ kind: "upstream",
104
+ branch: remote.branch,
105
+ remoteCommit: remote.commit,
106
+ taskBaseCommit: binding.baseCommit,
107
+ strategy: "rebase"
108
+ },
109
+ checkCommands: parsed.many.get("--check") ?? [],
110
+ rerunChecks: parsed.one.has("--rerun-checks")
111
+ }, now());
112
+ tx.saveIntegrationAttempt(task.id, created);
113
+ return created;
114
+ });
115
+ integrationId = attempt.id;
116
+ phase = "integrate";
117
+ const result = await service.integrate(task.id, attempt.id);
118
+ results.push(result);
119
+ // Each Project has an independent candidate and Job. --latest admits all
120
+ // requested Projects, then the Agent continues the returned exact attempts.
121
+ if (result.status !== "committed" && result.status !== "checks-running")
122
+ break;
83
123
  }
84
- const remote = await git.resolveRemoteHead({
85
- remoteUrl: project.remoteUrl,
86
- branch: binding.baseRef
87
- });
88
- const attempt = store.transaction((tx) => {
89
- const created = createIntegrationAttempt({
90
- id: tx.nextIntegrationAttemptId(task.id),
91
- taskId: task.id,
92
- projectId: project.id,
93
- targetRef: entry.branch,
94
- beforeCommit: binding.currentCommit,
95
- source: {
96
- kind: "upstream",
97
- branch: remote.branch,
98
- remoteCommit: remote.commit,
99
- taskBaseCommit: binding.baseCommit,
100
- strategy: "rebase"
101
- },
102
- checkCommands: parsed.many.get("--check") ?? [],
103
- rerunChecks: parsed.one.has("--rerun-checks")
104
- }, now());
105
- tx.saveIntegrationAttempt(task.id, created);
106
- return created;
107
- });
108
- const result = await service.integrate(task.id, attempt.id);
109
- results.push(result);
110
- // Each Project has an independent candidate and Job. --latest admits all
111
- // requested Projects, then the Agent continues the returned exact attempts.
112
- if (result.status !== "committed" && result.status !== "checks-running")
124
+ catch (error) {
125
+ // Earlier Projects may already have advanced HEAD or admitted a Job.
126
+ // Preserve those receipts and the exact uncertain attempt; never turn a
127
+ // partially executed batch into a usage error or replay it automatically.
128
+ failure = {
129
+ projectId: binding.projectId,
130
+ phase,
131
+ message: error instanceof Error ? error.message : String(error),
132
+ ...(integrationId === undefined ? {} : { integrationId }),
133
+ effect: integrationId === undefined ? "not-started" : "inspect-required"
134
+ };
113
135
  break;
136
+ }
114
137
  }
115
- if (projectRef !== undefined && results.length === 0) {
116
- throw usageError(`Task Project not found: ${task.id}/${projectRef}.`);
117
- }
138
+ const attempted = new Set([
139
+ ...results.map(result => result.attempt.projectId),
140
+ ...(failure === undefined ? [] : [failure.projectId])
141
+ ]);
142
+ const remainingProjectIds = selected.map(binding => binding.projectId)
143
+ .filter(id => !attempted.has(id));
144
+ const stage = failure !== undefined
145
+ || results.some(result => result.status !== "committed" && result.status !== "checks-running")
146
+ ? "integration-blocked"
147
+ : results.some(result => result.status === "checks-running")
148
+ ? "checks-running" : "integrated";
118
149
  const lines = results.flatMap(result => {
119
150
  const { attempt, status } = result;
120
151
  return [
121
- ` ${attempt.id} ${attempt.projectId}: ${attempt.beforeCommit.slice(0, 12)} -> ${(attempt.afterCommit ?? attempt.candidateCommit ?? attempt.beforeCommit).slice(0, 12)} (${status})`,
152
+ ` ${attempt.id} ${attempt.projectId}: ${status} (rebase)`,
153
+ ` Expected HEAD: ${attempt.beforeCommit}; committed HEAD: ${attempt.afterCommit ?? "not committed"}`,
154
+ ` Candidate: ${attempt.candidateCommit ?? "not prepared"}`,
122
155
  ...(status === "checks-running" ? [
123
156
  ` Job ${result.job.id}; continue with yui task integration continue ${task.id}/${attempt.id}`
124
157
  ] : [])
125
158
  ];
126
159
  });
127
160
  return {
128
- output: `Upstream Integration results for Task ${task.id}:\n${lines.join("\n")}\n`,
129
- data: { taskId: task.id, integrations: results }
161
+ output: `Upstream Integration results for Task ${task.id} (${stage}):\n${lines.join("\n")}\n`
162
+ + (failure === undefined ? "" : (` ${failure.projectId} stopped during ${failure.phase}: ${failure.message}\n`
163
+ + (failure.integrationId === undefined
164
+ ? " No Integration was started for this Project.\n"
165
+ : ` Inspect yui task integration show ${task.id}/${failure.integrationId}; effects require inspection, not batch replay.\n`)))
166
+ + (remainingProjectIds.length === 0 ? "" : ` Not attempted: ${remainingProjectIds.join(", ")}\n`),
167
+ data: { taskId: task.id, stage, strategy: "rebase", integrations: results,
168
+ remainingProjectIds, ...(failure === undefined ? {} : { failure }) }
130
169
  };
131
170
  }
@@ -1,7 +1,8 @@
1
1
  import { providerRetryProjection } from "../runtime/providerRetry.js";
2
2
  import { operationalTaskRecords } from "../task/taskRecordRetirement.js";
3
3
  import { TASK_COMPLETION_PUBLISHED_TREE_AUTHORIZED_EVENT } from "../task/publicationReference.js";
4
- import { RUN_INPUT_MAX_DELTAS } from "./runInputContract.js";
4
+ import { RUN_INPUT_MAX_DELTAS, requireRunContextSnapshotRef } from "./runInputContract.js";
5
+ import { requireIdentity } from "../domain/validation.js";
5
6
  import { assertWorkItemDependenciesCompleted } from "../workItem/dependencyGate.js";
6
7
  import { governingWorkItemCandidate, workItemExecutionGroupById } from "../workItem/workItem.js";
7
8
  import { formatGitArtifactRef, validateGitArtifactRef } from "../artifacts/gitArtifactRef.js";
@@ -209,26 +210,14 @@ export function contextSnapshotDeltaRefIds(store, snapshot) {
209
210
  }
210
211
  export function buildRunContextPack(store, taskId, runId) {
211
212
  const run = requireExactRun(store, taskId, runId);
212
- const current = collectAuthorizedContext(store, run);
213
- let pointers = current.map(({ ref }) => ref);
214
- let snapshotRef;
215
- if (run.inputs[0].input.contextSnapshotRef !== undefined) {
216
- const expected = run.inputs[0].input.contextSnapshotRef;
217
- const snapshot = store.getContextSnapshot(taskId, expected.id);
218
- if (snapshot === null)
219
- throw new Error(`AgentRun Context Snapshot is missing: ${expected.id}.`);
220
- validateContextSnapshot(snapshot);
221
- if (snapshot.digest !== expected.digest || snapshot.taskId !== taskId) {
222
- throw new Error(`AgentRun Context Snapshot identity drifted: ${expected.id}.`);
223
- }
224
- pointers = snapshot.refs;
225
- snapshotRef = contextSnapshotRef(snapshot);
226
- }
213
+ const snapshot = readRunContextSnapshot(store, run);
214
+ const pointers = snapshot.refs;
227
215
  if (pointers.length > RUN_CONTEXT_PACK_MAX_REFS) {
228
216
  throw new Error(`AgentRun Context exceeds ${RUN_CONTEXT_PACK_MAX_REFS} authorized refs.`);
229
217
  }
230
218
  const view = contextView(run);
231
- const writableProjectIds = writableProjects(store, run, view);
219
+ const writableProjectIds = run.effective.executionAuthority === "planning"
220
+ ? [] : run.effective.writeProjectIds;
232
221
  const summaries = pointers.map((ref) => Object.freeze({
233
222
  refId: ref.refId,
234
223
  store: ref.store,
@@ -246,7 +235,7 @@ export function buildRunContextPack(store, taskId, runId) {
246
235
  adapterId: run.effective.adapterId,
247
236
  workspace: run.effective.workspace.root
248
237
  }),
249
- ...(snapshotRef === undefined ? {} : { snapshot: snapshotRef }),
238
+ snapshot: contextSnapshotRef(snapshot),
250
239
  input: run.inputs[0].input,
251
240
  authority: Object.freeze({ view, readableRefs: pointers, writableProjectIds }),
252
241
  pointers,
@@ -302,17 +291,15 @@ function readLiveTaskState(store, taskId) {
302
291
  });
303
292
  }
304
293
  export function expandRunContextRef(store, taskId, runId, refId, refStore) {
305
- const pack = buildRunContextPack(store, taskId, runId);
306
- const authorized = pack.pointers.filter((ref) => (ref.refId === refId && (refStore === undefined || ref.store === refStore)));
307
- const selector = refStore === undefined ? refId : `${refStore}/${refId}`;
294
+ requireIdentity(refStore, "Context ref store");
295
+ const run = requireExactRun(store, taskId, runId);
296
+ const snapshot = readRunContextSnapshot(store, run);
297
+ const authorized = snapshot.refs.filter((ref) => (ref.refId === refId && ref.store === refStore));
298
+ const selector = `${refStore}/${refId}`;
308
299
  if (authorized.length !== 1) {
309
300
  throw new Error(`AgentRun Context ref is not uniquely authorized: ${selector}.`);
310
301
  }
311
- const run = requireExactRun(store, taskId, runId);
312
- const snapshotRef = run.inputs[0].input.contextSnapshotRef;
313
- const materialized = snapshotRef === undefined
314
- ? collectAuthorizedContext(store, run).find(({ ref }) => (contextRefIdentity(ref) === contextRefIdentity(authorized[0])))
315
- : store.getContextSnapshot(taskId, snapshotRef.id)?.resources.find(({ ref }) => (contextRefIdentity(ref) === contextRefIdentity(authorized[0])));
302
+ const materialized = snapshot.resources.find(({ ref }) => (contextRefIdentity(ref) === contextRefIdentity(authorized[0])));
316
303
  if (materialized === undefined || materialized.ref.digest !== authorized[0].digest) {
317
304
  throw new Error(`AgentRun Context ref is unavailable or drifted: ${selector}.`);
318
305
  }
@@ -329,15 +316,12 @@ export function expandRunContextRef(store, taskId, runId, refId, refStore) {
329
316
  /** Fail-closed delta cursor resolution for one immutable AgentRun lineage. */
330
317
  export function buildRunContextDelta(store, taskId, runId, after) {
331
318
  const pack = buildRunContextPack(store, taskId, runId);
332
- if (after === pack.digest || after === pack.snapshot?.digest) {
319
+ if (after === pack.digest || after === pack.snapshot.digest) {
333
320
  return Object.freeze({ schemaVersion: 1, after, cursor: pack.digest, refs: [] });
334
321
  }
335
322
  const run = requireExactRun(store, taskId, runId);
336
- const snapshotRef = run.inputs[0].input.contextSnapshotRef;
337
- const snapshot = snapshotRef === undefined
338
- ? null
339
- : store.getContextSnapshot(taskId, snapshotRef.id);
340
- const parentDigest = snapshot?.parentRef?.digest;
323
+ const snapshot = readRunContextSnapshot(store, run);
324
+ const parentDigest = snapshot.parentRef?.digest;
341
325
  if (!((parentDigest !== undefined && after === parentDigest)
342
326
  || (parentDigest === undefined && after === "none"))) {
343
327
  throw new Error("AgentRun Context delta cursor is outside the frozen Snapshot lineage.");
@@ -356,6 +340,19 @@ function requireExactRun(store, taskId, runId) {
356
340
  }
357
341
  return run;
358
342
  }
343
+ export function readRunContextSnapshot(store, run) {
344
+ const expected = requireRunContextSnapshotRef(run.inputs[0].input);
345
+ const snapshot = store.getContextSnapshot(run.taskId, expected.id);
346
+ if (snapshot === null)
347
+ throw new Error(`AgentRun Context Snapshot is missing: ${expected.id}.`);
348
+ if (snapshot.id !== expected.id || snapshot.taskId !== run.taskId
349
+ || expected.taskId !== run.taskId || snapshot.digest !== expected.digest
350
+ || snapshot.sequence !== expected.sequence || snapshot.scope !== expected.scope
351
+ || snapshot.scopeRef !== expected.scopeRef) {
352
+ throw new Error(`AgentRun Context Snapshot identity drifted: ${expected.id}.`);
353
+ }
354
+ return validateContextSnapshot(snapshot);
355
+ }
359
356
  function collectAuthorizedContext(store, run) {
360
357
  const task = store.getTask(run.taskId);
361
358
  if (task === null)
@@ -575,16 +572,7 @@ function collectSourceRunContext(store, run, sourceRunIds) {
575
572
  }
576
573
  /** The frozen refs, not the Group's changing lane state, define provenance. */
577
574
  export function synthesisSourceRunIds(store, run) {
578
- const ref = run.inputs[0].input.contextSnapshotRef;
579
- if (ref === undefined)
580
- throw new Error(`Synthesis AgentRun has no Context Snapshot: ${run.id}.`);
581
- const snapshot = store.getContextSnapshot(run.taskId, ref.id);
582
- if (snapshot === null || snapshot.digest !== ref.digest
583
- || snapshot.sequence !== ref.sequence || snapshot.scope !== ref.scope
584
- || snapshot.scopeRef !== ref.scopeRef || snapshot.taskId !== run.taskId) {
585
- throw new Error(`Synthesis Context Snapshot is missing or drifted: ${ref.id}.`);
586
- }
587
- validateContextSnapshot(snapshot);
575
+ const snapshot = readRunContextSnapshot(store, run);
588
576
  return snapshot.resources.filter(({ ref: entry, value }) => (entry.store === "source-run"
589
577
  && value.executionGroupId === run.sourceExecutionGroupId)).map(({ ref: entry }) => entry.refId);
590
578
  }
@@ -621,18 +609,6 @@ function contextView(run) {
621
609
  return "operator";
622
610
  return "worker";
623
611
  }
624
- function writableProjects(store, run, view) {
625
- if (run.effective.executionAuthority === "planning")
626
- return Object.freeze([]);
627
- if (view === "leader")
628
- return Object.freeze(store.getTask(run.taskId)?.projectBindings.map(({ projectId }) => projectId) ?? []);
629
- if (view === "reviewer") {
630
- return Object.freeze(run.workspace?.entries.filter(({ access }) => access === "write").map(({ projectId }) => projectId) ?? []);
631
- }
632
- if (run.workItemId === undefined)
633
- return Object.freeze([]);
634
- return Object.freeze(store.getWorkItem(run.taskId, run.workItemId)?.writeProjectIds ?? []);
635
- }
636
612
  function completionActions(view, authority) {
637
613
  if (authority === "planning")
638
614
  return Object.freeze(["finish-turn", "request-input", "request-activation"]);
@@ -33,6 +33,13 @@ export function validateRunInput(value) {
33
33
  }
34
34
  return value;
35
35
  }
36
+ /** Execution must never substitute current Task facts for missing frozen evidence. */
37
+ export function requireRunContextSnapshotRef(input) {
38
+ if (input.contextSnapshotRef === undefined) {
39
+ throw new Error("AgentRun Context Snapshot is required for this execution. Inspect the Run and retire or explicitly retry it; original evidence is never reconstructed.");
40
+ }
41
+ return validateContextSnapshotRef(input.contextSnapshotRef);
42
+ }
36
43
  export function createRunInputEnvelope(context, input) {
37
44
  validateRunInput(input);
38
45
  const normalized = normalizeEnvelopeContext(context, input);
@@ -65,6 +72,8 @@ export function serializeRunInputEnvelope(value) {
65
72
  throw new Error("AgentRun input protocol is unsupported.");
66
73
  }
67
74
  const normalized = normalizeEnvelope(value);
75
+ if (normalized.purpose !== "global")
76
+ requireRunContextSnapshotRef(normalized);
68
77
  const subject = Object.entries(normalized.subject)
69
78
  .map(([key, id]) => `${key}:${id}`)
70
79
  .join(",");
@@ -4,7 +4,6 @@ import { dirname, join, resolve } from "node:path";
4
4
  import { writeTextFileAtomically } from "../storage/durableFile.js";
5
5
  import { SESSION_BOOTSTRAP_MANIFEST_SCHEMA_VERSION, SESSION_CONTEXT_PROTOCOL, sessionManifestCompatibilityDigest } from "./sessionProtocolIdentity.js";
6
6
  export { SESSION_BOOTSTRAP_MANIFEST_SCHEMA_VERSION, SESSION_CONTEXT_PROTOCOL, sessionManifestCompatibilityDigest } from "./sessionProtocolIdentity.js";
7
- const ordinarySessionCli = ["#!/bin/sh", "exec yui \"$@\"", ""].join("\n");
8
7
  /** Portable read pointers accompany every managed input, including the first
9
8
  * notification on a fresh Session. No Task content or credentials are copied. */
10
9
  export function withSessionContextPointer(text, environment) {
@@ -24,9 +23,8 @@ export function withSessionContextPointer(text, environment) {
24
23
  /**
25
24
  * A managed Session wrapper answers exactly one question: which installation
26
25
  * runs this command. It therefore carries only the resolved entry point, never
27
- * a package or build identity. Version identity changes on every release while
28
- * a Session legitimately outlives it, so embedding it here would turn an
29
- * ordinary update into a broken Session.
26
+ * a package or build identity. Current Sessions may survive a compatible
27
+ * installation relocation; this is not a cross-version runtime adapter.
30
28
  */
31
29
  function renderSessionCli(entryPoint) {
32
30
  return [
@@ -108,8 +106,8 @@ export function materializeSessionBootstrap(input) {
108
106
  // Provider command runners may rebuild PATH independently of the managed
109
107
  // process environment, so a bare `yui` could resolve to another install or
110
108
  // Home. The wrapper pins the resolved entry point instead. Package identity
111
- // stays out of it: the compatible continuity preflight and the Session's
112
- // caller key already authorize the command.
109
+ // stays out of it: current Session authority and the CLI/Controller protocol
110
+ // boundary authorize the command.
113
111
  const sessionCliContent = renderSessionCli(input.entryPoint);
114
112
  const sessionCliDigest = digest(sessionCliContent);
115
113
  const sessionCliPath = resolve(join(home, "runtime", "session-cli", `yui-${sessionCliDigest}.sh`));
@@ -185,11 +183,9 @@ function quoteShellWord(value) {
185
183
  return `'${value.replaceAll("'", "'\"'\"'")}'`;
186
184
  }
187
185
  /**
188
- * Retargets known managed wrappers to the current resolved control plane after
189
- * a compatible update. Only a valid Session Manifest may nominate a wrapper,
190
- * and only Yui's own two-line wrapper shapes are changed. A wrapper written by
191
- * an earlier release may still pin a control-plane digest; retargeting it here
192
- * is what removes that stale pin from a live Home.
186
+ * Retargets current managed wrappers to the resolved installation entry point.
187
+ * Only a valid Session Manifest may nominate a wrapper, and only the exact
188
+ * two-argument shell form generated here is changed.
193
189
  */
194
190
  export function refreshManagedSessionCliWrappers(homeInput, entryPoint) {
195
191
  const home = resolve(homeInput);
@@ -217,8 +213,7 @@ export function refreshManagedSessionCliWrappers(homeInput, entryPoint) {
217
213
  wrapperPaths.add(wrapperPath);
218
214
  }
219
215
  catch {
220
- // Historical or incomplete manifests are audit material. They must not
221
- // block current Sessions or an otherwise compatible package update.
216
+ // An invalid Manifest cannot nominate a wrapper for mutation.
222
217
  skipped += 1;
223
218
  }
224
219
  }
@@ -234,7 +229,7 @@ export function refreshManagedSessionCliWrappers(homeInput, entryPoint) {
234
229
  current += 1;
235
230
  continue;
236
231
  }
237
- if (content !== ordinarySessionCli && !isManagedSessionCli(content)) {
232
+ if (!isManagedSessionCli(content)) {
238
233
  skipped += 1;
239
234
  continue;
240
235
  }
@@ -244,12 +239,9 @@ export function refreshManagedSessionCliWrappers(homeInput, entryPoint) {
244
239
  }
245
240
  return Object.freeze({ refreshed, current, skipped });
246
241
  }
247
- /**
248
- * Both wrapper shapes Yui has written: the current entry-point-only form and
249
- * the earlier form that also pinned a control-plane digest.
250
- */
242
+ /** Match exactly the two shell-quoted words emitted by renderSessionCli. */
251
243
  function isManagedSessionCli(content) {
252
- return /^#!\/bin\/sh\nexec [^\n]+(?: '--yui-control' '[a-f0-9]{64}')? "\$@"\n$/u.test(content);
244
+ return /^#!\/bin\/sh\nexec '(?:[^'\n]|'"'"')*' '(?:[^'\n]|'"'"')*' "\$@"\n$/u.test(content);
253
245
  }
254
246
  function writeImmutableText(path, content) {
255
247
  writeTextFileAtomically(path, content);
@@ -16,7 +16,7 @@ export function resolveAgentHostObservation(store, event) {
16
16
  "conversation.observed", "goal.updated", "goal.cleared",
17
17
  "turn.accepted", "turn.completed", "turn.failed", "turn.cancelled",
18
18
  "input.accepted", "input.rejected", "input.delivery-unknown",
19
- "operation.started", "operation.completed", "operation.failed", "activity.observed"
19
+ "operation.started", "operation.completed", "operation.failed", "activity.observed", "observer.health"
20
20
  ];
21
21
  if (!hostKinds.includes(kind)
22
22
  || (kind === "host.observed" ? input.authority !== "host"
@@ -44,7 +44,9 @@ export function resolveAgentHostObservation(store, event) {
44
44
  const directStart = kind === "turn.accepted" && fence.receiptId === `direct:${fence.nativeTurnId}`;
45
45
  const additionalInput = kind.startsWith("input.") && (fence.receiptId?.startsWith("native-input:") || fence.receiptId?.startsWith("turn-input:")
46
46
  || fence.receiptId?.startsWith("steer:"));
47
- const terminal = ["turn.completed", "turn.failed", "turn.cancelled", "session.ended", "session.failed"].includes(kind);
47
+ // Post-execution diagnostics may describe an already-terminal exact input.
48
+ // This permits evidence collection, never a terminal transition.
49
+ const terminal = ["turn.completed", "turn.failed", "turn.cancelled", "session.ended", "session.failed", "observer.health"].includes(kind);
48
50
  if (fence.taskId === undefined) {
49
51
  if (host.startupRunId !== undefined)
50
52
  throw new RuntimeHookRunFenceError("Global Host cannot carry a startup Run.");
@@ -102,7 +102,7 @@ export class AgentRuntimeObserver {
102
102
  ? tokenObservationIdentity("baseline", fence, source.sourceId, baselineKey)
103
103
  : usageObservationIdentity(fence, source.sourceId, latestOccurrence);
104
104
  this.inbox.enqueueObservation(createRuntimeObservation({
105
- schemaVersion: 4,
105
+ schemaVersion: 1,
106
106
  eventId: identity.eventId,
107
107
  semanticKey: identity.semanticKey,
108
108
  kind: "activity.observed",
@@ -137,7 +137,7 @@ export class AgentRuntimeObserver {
137
137
  const health = JSON.stringify([sample.status, sample.detail ?? null]);
138
138
  if (state.health !== health) {
139
139
  this.inbox.enqueueObservation(createRuntimeObservation({
140
- schemaVersion: 4,
140
+ schemaVersion: 1,
141
141
  eventId: observationId("health", fence, source.sourceId, health),
142
142
  semanticKey: observationId("health", fence, source.sourceId, health),
143
143
  kind: "observer.health",
@@ -183,7 +183,7 @@ export class AgentRuntimeObserver {
183
183
  usages.forEach((occurrence, usageIndex) => {
184
184
  const identity = usageObservationIdentity(fence, source.sourceId, occurrence);
185
185
  this.inbox.enqueueObservation(createRuntimeObservation({
186
- schemaVersion: 4,
186
+ schemaVersion: 1,
187
187
  eventId: identity.eventId,
188
188
  semanticKey: identity.semanticKey,
189
189
  kind: "activity.observed",
@@ -213,7 +213,7 @@ export class AgentRuntimeObserver {
213
213
  });
214
214
  if (activityChanged && state.cursor !== undefined) {
215
215
  this.inbox.enqueueObservation(createRuntimeObservation({
216
- schemaVersion: 4,
216
+ schemaVersion: 1,
217
217
  eventId: observationId("activity", fence, source.sourceId, sample.activityId),
218
218
  semanticKey: observationId("activity", fence, source.sourceId, sample.activityId),
219
219
  kind: "activity.observed",
@@ -2,6 +2,7 @@ import { spawn } from "node:child_process";
2
2
  import { fileURLToPath } from "node:url";
3
3
  import { findLiveControllerProcessForHome, inspectLiveControllerProcess } from "../core/controllerProcessIdentity.js";
4
4
  import { readHomeFilesystemId } from "../core/homeFilesystemIdentity.js";
5
+ import { parseControllerIdentity } from "../core/controllerIdentity.js";
5
6
  import { AGENT_OPERATIONAL_ENVIRONMENT_NAMES, nativeAgentEnvironmentNames, operationalAgentEnvironment, selectEnvironment, YUI_MANAGED_RUNTIME_ENVIRONMENT_NAMES } from "../agent/launchEnvironment.js";
6
7
  import { callController, ControllerClientError, readControllerDiscovery, stopOrphanedFileTaskController } from "../core/controllerClient.js";
7
8
  import { FILE_TASK_CONTROLLER_PROTOCOL_VERSION } from "../core/protocol.js";
@@ -53,7 +54,7 @@ export async function ensureFileTaskController(home, options = {}) {
53
54
  await waitForForeignHandover(home, options);
54
55
  try {
55
56
  const status = await call(home, "controller.status", {});
56
- assertCompatibleControllerStatus(status, options.expectedVersion);
57
+ assertCompatibleControllerStatus(status, options.expectedIdentity);
57
58
  return status;
58
59
  }
59
60
  catch (error) {
@@ -78,7 +79,7 @@ export async function ensureFileTaskController(home, options = {}) {
78
79
  for (;;) {
79
80
  try {
80
81
  const status = await call(home, "controller.status", {});
81
- assertCompatibleControllerStatus(status, options.expectedVersion);
82
+ assertCompatibleControllerStatus(status, options.expectedIdentity);
82
83
  return status;
83
84
  }
84
85
  catch (error) {
@@ -95,10 +96,10 @@ export async function ensureFileTaskController(home, options = {}) {
95
96
  }
96
97
  /** Start and await readiness for a captured Controller identity. */
97
98
  export async function ensureFileTaskControllerIdentity(home, identity, options = {}) {
98
- assertExpectedControllerRuntimeProcessIdentity(identity);
99
+ const captured = parseControllerIdentity(identity);
99
100
  const status = await ensureFileTaskController(home, {
100
101
  ...options,
101
- expectedVersion: identity.version
102
+ expectedIdentity: captured
102
103
  });
103
104
  // A same-version status is not an identity proof: another Controller binary
104
105
  // may own the Home after a restart or a stale discovery race. Authenticate
@@ -109,39 +110,24 @@ export async function ensureFileTaskControllerIdentity(home, identity, options =
109
110
  const actual = await call(home, "controller.identity", {}, {
110
111
  timeoutMs: options.requestTimeoutMs
111
112
  });
112
- assertControllerRuntimeProcessIdentity(actual, identity);
113
+ assertControllerRuntimeProcessIdentity(actual, captured);
113
114
  return status;
114
115
  }
115
- function assertExpectedControllerRuntimeProcessIdentity(identity) {
116
- if (typeof identity.executablePath !== "string"
117
- || identity.executablePath.length === 0
118
- || !Array.isArray(identity.args)
119
- || identity.args.some((arg) => typeof arg !== "string")
120
- || typeof identity.version !== "string"
121
- || identity.version.length === 0) {
122
- throw new Error("Expected Controller runtime identity is malformed; refusing to start or accept a Controller.");
123
- }
124
- }
125
- function assertControllerRuntimeProcessIdentity(actual, expected) {
126
- if (!isJsonRecord(actual)) {
127
- throw new Error("Authenticated Controller runtime identity is malformed; refusing to accept readiness.");
128
- }
116
+ function assertControllerRuntimeProcessIdentity(value, expected) {
117
+ const actual = parseControllerIdentity(value);
129
118
  const actualArgs = actual.args;
130
- if (typeof actual.executablePath !== "string"
131
- || !Array.isArray(actualArgs)
132
- || actualArgs.some((arg) => typeof arg !== "string")
133
- || typeof actual.version !== "string") {
134
- throw new Error("Authenticated Controller runtime identity is malformed; refusing to accept readiness.");
135
- }
136
119
  const argsMatch = actualArgs.length === expected.args.length
137
120
  && actualArgs.every((arg, index) => arg === expected.args[index]);
138
121
  if (actual.executablePath !== expected.executablePath
139
122
  || !argsMatch
140
- || actual.version !== expected.version) {
141
- throw new Error("Authenticated Controller runtime identity does not match the captured executable, argv, and version; refusing readiness.");
123
+ || actual.version !== expected.version
124
+ || actual.controllerProtocolVersion !== expected.controllerProtocolVersion
125
+ || actual.storageVersion !== expected.storageVersion
126
+ || actual.minimumStorageVersion !== expected.minimumStorageVersion) {
127
+ throw new Error("Authenticated Controller runtime identity does not match the captured executable, argv, package, protocol and storage versions; refusing readiness.");
142
128
  }
143
129
  }
144
- function assertCompatibleControllerStatus(status, expectedVersion) {
130
+ function assertCompatibleControllerStatus(status, expectedIdentity) {
145
131
  const statusRecord = isJsonRecord(status) && status.running === true ? status : null;
146
132
  const actual = statusRecord?.protocolVersion;
147
133
  if (statusRecord === null || actual !== FILE_TASK_CONTROLLER_PROTOCOL_VERSION) {
@@ -149,22 +135,11 @@ function assertCompatibleControllerStatus(status, expectedVersion) {
149
135
  + "Run `yui controller restart` before writing new task records.");
150
136
  }
151
137
  const actualVersion = statusRecord.version;
152
- if (expectedVersion !== undefined && actualVersion !== expectedVersion) {
153
- throw new Error(`Controller version is incompatible (expected ${expectedVersion}, found ${typeof actualVersion === "string" ? actualVersion : "unknown"}). `
154
- + "Run `yui controller restart` before writing new task records.");
155
- }
156
- // Ordinary callers must authenticate the complete control-plane identity.
157
- // An update/upgrade restore instead targets a previously captured Controller
158
- // binary, which may predate the current storage-layout status fields; that
159
- // path authenticates its executable, argv, and version immediately after
160
- // readiness in ensureFileTaskControllerIdentity.
161
- if (expectedVersion === undefined) {
162
- const identity = yuiVersionIdentity();
163
- assertControllerStatusIdentity(status, {
164
- ...identity,
165
- version: typeof actualVersion === "string" ? actualVersion : identity.version
166
- });
167
- }
138
+ const current = yuiVersionIdentity();
139
+ assertControllerStatusIdentity(status, expectedIdentity === undefined ? {
140
+ ...current,
141
+ version: typeof actualVersion === "string" ? actualVersion : current.version
142
+ } : parseControllerIdentity(expectedIdentity));
168
143
  }
169
144
  function spawnDetachedFileTaskController(_home, environment) {
170
145
  const child = spawn(process.execPath, [fileURLToPath(new URL("./controllerMain.js", import.meta.url))], {