@zq-silk/yui 0.6.0 → 0.6.2

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 (150) hide show
  1. package/README.md +5 -5
  2. package/dist/agent/managedRuntimeEnvironment.js +2 -1
  3. package/dist/cli/commandCatalog.js +251 -13
  4. package/dist/cli/updateOrchestrator.js +8 -0
  5. package/dist/cli/updatePorts.js +76 -22
  6. package/dist/cli.js +264 -20
  7. package/dist/commands/configCommands.js +83 -9
  8. package/dist/commands/controllerCommands.js +103 -0
  9. package/dist/commands/deliveryGuardPreflight.js +30 -0
  10. package/dist/commands/durableJobCommands.js +231 -0
  11. package/dist/commands/executionAuditCommands.js +193 -0
  12. package/dist/commands/grantCommands.js +374 -0
  13. package/dist/commands/projectCommands.js +119 -81
  14. package/dist/commands/releaseCommands.js +444 -0
  15. package/dist/commands/resourcesCommands.js +274 -0
  16. package/dist/commands/sessionCommands.js +104 -0
  17. package/dist/commands/taskActor.js +117 -0
  18. package/dist/commands/taskChangeSetCommands.js +60 -0
  19. package/dist/commands/taskCommands.js +618 -202
  20. package/dist/commands/taskCompletionGate.js +78 -1
  21. package/dist/commands/taskContextCommand.js +33 -6
  22. package/dist/commands/taskInputCommands.js +1 -1
  23. package/dist/commands/taskIntegrationCommands.js +136 -33
  24. package/dist/commands/taskIntegrationQueueCommands.js +228 -0
  25. package/dist/commands/taskNextActionCommand.js +100 -0
  26. package/dist/commands/taskOverlapCommands.js +120 -0
  27. package/dist/commands/taskOverviewCommand.js +36 -8
  28. package/dist/commands/telemetryCommands.js +330 -0
  29. package/dist/commands/workflowCommands.js +415 -0
  30. package/dist/config/yuiConfig.js +62 -0
  31. package/dist/controller/clientRuntime.js +42 -1
  32. package/dist/controller/controller.js +402 -56
  33. package/dist/controller/controllerMain.js +25 -2
  34. package/dist/controller/domainIdentity.js +16 -8
  35. package/dist/controller/fileSchedulerStoreAdapter.js +423 -31
  36. package/dist/controller/handoverCandidate.js +168 -0
  37. package/dist/controller/jobClient.js +102 -0
  38. package/dist/controller/jobControl.js +613 -0
  39. package/dist/controller/jobSupervisor.js +498 -0
  40. package/dist/controller/providerHookRunFence.js +34 -5
  41. package/dist/controller/resourceCleanupLinux.js +18 -9
  42. package/dist/controller/resourceInventoryLinux.js +90 -39
  43. package/dist/controller/runtime.js +165 -15
  44. package/dist/controller/runtimeEventInbox.js +234 -57
  45. package/dist/controller/runtimeEventProcessor.js +297 -58
  46. package/dist/controller/sessionOwnerReconciliation.js +321 -0
  47. package/dist/core/controllerServer.js +416 -27
  48. package/dist/core/controllerTelemetry.js +167 -0
  49. package/dist/doctor/doctor.js +113 -16
  50. package/dist/domain/validation.js +9 -0
  51. package/dist/execution/executionGroup.js +40 -3
  52. package/dist/executor/agentExecutor.js +6 -3
  53. package/dist/executor/effectiveLaunch.js +52 -0
  54. package/dist/executor/executorRegistry.js +50 -0
  55. package/dist/executor/fileRoleLaunchPlanner.js +61 -6
  56. package/dist/grant/capabilityGrant.js +282 -0
  57. package/dist/integration/changeSet.js +16 -3
  58. package/dist/integration/changeSetManifest.js +46 -0
  59. package/dist/integration/gitIntegrationService.js +528 -147
  60. package/dist/integration/integrationAttempt.js +54 -5
  61. package/dist/integration/integrationQueueEntry.js +221 -0
  62. package/dist/integration/integrationQueueService.js +955 -0
  63. package/dist/integration/manifestTags.js +99 -0
  64. package/dist/integration/overlapDiagnostics.js +211 -0
  65. package/dist/job/durableJob.js +449 -0
  66. package/dist/job/jobRunner.js +350 -0
  67. package/dist/lifecycle/exactRunTerminalization.js +24 -2
  68. package/dist/lifecycle/providerErrorClass.js +126 -0
  69. package/dist/message/message.js +16 -3
  70. package/dist/observability/executionAudit.js +545 -0
  71. package/dist/observability/faultClassification.js +160 -0
  72. package/dist/observability/runtimeIdentity.js +367 -0
  73. package/dist/release/fakeReleasePorts.js +55 -0
  74. package/dist/release/releaseHandover.js +475 -0
  75. package/dist/release/releaseIdempotencyStore.js +165 -0
  76. package/dist/release/releaseWorkflow.js +459 -0
  77. package/dist/release/releaseWorkflowEngine.js +688 -0
  78. package/dist/release/releaseWorkflowPorts.js +1720 -0
  79. package/dist/release/runtimeRelease.js +495 -0
  80. package/dist/release/workflowFileLock.js +218 -0
  81. package/dist/repository/gitWorkspace.js +177 -1
  82. package/dist/repository/projectMaintenanceLock.js +315 -0
  83. package/dist/repository/taskWorkspaceCoordinator.js +87 -17
  84. package/dist/repository/taskWorkspacePreparer.js +1091 -517
  85. package/dist/resources/autoResourceGc.js +116 -0
  86. package/dist/resources/liveReferences.js +574 -0
  87. package/dist/resources/resourceDiscovery.js +477 -0
  88. package/dist/resources/resourceGc.js +645 -0
  89. package/dist/resources/resourceRegistrar.js +256 -0
  90. package/dist/resources/resourceRegistry.js +150 -0
  91. package/dist/resources/resourceRegistryStore.js +41 -0
  92. package/dist/resources/resourceTypes.js +42 -0
  93. package/dist/resources/sqliteResourceRegistry.js +111 -0
  94. package/dist/review/reviewConfig.js +10 -0
  95. package/dist/review/reviewFinding.js +240 -0
  96. package/dist/review/reviewFindingLedger.js +545 -0
  97. package/dist/review/reviewOutcomeClassifier.js +61 -0
  98. package/dist/review/reviewRound.js +56 -4
  99. package/dist/run/agentRun.js +80 -4
  100. package/dist/run/providerRetry.js +84 -0
  101. package/dist/run/providerRetryConfig.js +63 -0
  102. package/dist/run/yieldReceipt.js +65 -0
  103. package/dist/runtime/exactControlPlane.js +79 -2
  104. package/dist/runtime/index.js +4 -0
  105. package/dist/runtime/sessionOwnerIdentity.js +269 -0
  106. package/dist/runtime/sessionOwnerRegistry.js +132 -0
  107. package/dist/runtime/sessionReconciliation.js +93 -0
  108. package/dist/runtime/sessionTerminationGuard.js +211 -0
  109. package/dist/runtime/taskRuntimeIsolation.js +13 -0
  110. package/dist/runtime/tmuxAdapters.js +34 -1
  111. package/dist/scheduler/actionability.js +155 -0
  112. package/dist/scheduler/activeRoleRunDelivery.js +14 -5
  113. package/dist/scheduler/activeTaskProgress.js +60 -0
  114. package/dist/scheduler/leaderWakeupProcessor.js +22 -11
  115. package/dist/scheduler/roleRunStall.js +135 -29
  116. package/dist/scheduler/taskExecutionProjection.js +11 -0
  117. package/dist/storage/compatibleTaskStore.js +112 -5
  118. package/dist/storage/migration/productionRegistry.js +736 -1
  119. package/dist/storage/sqliteSchema.js +264 -3
  120. package/dist/storage/sqliteStore.js +487 -13
  121. package/dist/storage/storeRpc.js +21 -0
  122. package/dist/storage/taskStore.js +974 -21
  123. package/dist/storage/upgrade/homeClassification.js +120 -2
  124. package/dist/storage/upgrade/migrationReceipt.js +67 -0
  125. package/dist/storage/upgrade/pseudoLayoutRepair.js +241 -0
  126. package/dist/storage/upgrade/recordVersions.js +10 -1
  127. package/dist/storage/upgrade/sqliteMigrationTarget.js +58 -6
  128. package/dist/storage/upgrade/sqliteRecordMigrationTarget.js +290 -0
  129. package/dist/storage/upgrade/sqliteStateMigration.js +258 -2
  130. package/dist/storage/upgrade/upgradeOrchestrator.js +482 -16
  131. package/dist/task/deliveryGuard.js +226 -0
  132. package/dist/task/nextAction.js +738 -0
  133. package/dist/task/repairWave.js +137 -0
  134. package/dist/task/taskRecordReference.js +6 -1
  135. package/dist/telemetry/sqliteTelemetryStore.js +387 -0
  136. package/dist/telemetry/telemetryCompaction.js +251 -0
  137. package/dist/telemetry/telemetryConfig.js +64 -0
  138. package/dist/telemetry/telemetryRouter.js +32 -0
  139. package/dist/telemetry/telemetryStore.js +19 -0
  140. package/dist/telemetry/telemetryWiring.js +33 -0
  141. package/dist/tmux/tmuxManager.js +20 -1
  142. package/dist/tmux/tmuxSocketEndpoint.js +20 -0
  143. package/dist/verification/gateArtifact.js +216 -0
  144. package/dist/verification/gateArtifactStore.js +87 -0
  145. package/dist/verification/verificationGateService.js +414 -0
  146. package/dist/verification/verificationPlan.js +308 -0
  147. package/dist/workspace/gitChangeSetCapture.js +12 -2
  148. package/dist/workspace/workItemChangeSetManager.js +60 -3
  149. package/package.json +1 -1
  150. package/skills/yui-leader/SKILL.md +8 -0
@@ -0,0 +1,321 @@
1
+ import { existsSync } from "node:fs";
2
+ import { join } from "node:path";
3
+ import { createTaskEvent } from "../event/taskEvent.js";
4
+ import { createSessionOwnerIdentity, discoverProviderRootByLaunchEnv, isLinuxProcessLive, listLaunchFencedProcesses, listOwnedProcessTree, readLinuxProcessIdentity, reconcileSessionOwners, terminateSessionOwners } from "../runtime/index.js";
5
+ import { tmuxSocketDirectory } from "../tmux/tmuxSocketEndpoint.js";
6
+ import { yuiTmuxServerName, yuiTmuxSessionName, yuiTmuxTarget } from "../tmux/tmuxManager.js";
7
+ /**
8
+ * Production I/O adapters for Issue 03 Session owner reconciliation. The pure
9
+ * logic lives in src/runtime; this module only binds it to the durable store,
10
+ * /proc, and the exact tmux namespace of one Home.
11
+ */
12
+ export class SessionOwnerReconciliation {
13
+ #home;
14
+ #store;
15
+ #environment;
16
+ #tmux;
17
+ #onWarning;
18
+ constructor(deps) {
19
+ this.#home = deps.home;
20
+ this.#store = deps.store;
21
+ this.#environment = deps.environment ?? process.env;
22
+ this.#tmux = deps.tmux;
23
+ this.#onWarning = deps.onWarning ?? (() => undefined);
24
+ }
25
+ /**
26
+ * Persists the exact physical owner identity after a host created a new
27
+ * Provider process. The Provider root is attributed by the exact
28
+ * YUI_LAUNCH_ID environment fence; the tmux pane PID is only a weaker
29
+ * fallback when the fence cannot be read.
30
+ */
31
+ recordHostOwner(input) {
32
+ const discovered = discoverProviderRootByLaunchEnv(input.launchId);
33
+ const fallback = discovered === undefined && input.panePid !== undefined
34
+ ? readLinuxProcessIdentity(input.panePid)
35
+ : undefined;
36
+ const root = discovered ?? (fallback === undefined
37
+ ? undefined
38
+ : { pid: fallback.pid, identity: fallback });
39
+ if (root === undefined) {
40
+ this.#onWarning(`Session owner identity could not attribute a Provider root for launch ${input.launchId}; `
41
+ + "no owner record was written.");
42
+ return;
43
+ }
44
+ const owner = input.owner;
45
+ const taskId = owner.scope === "task" ? owner.taskId : undefined;
46
+ this.#store.saveSessionOwner(createSessionOwnerIdentity({
47
+ owner: {
48
+ scope: owner.scope,
49
+ ...(taskId === undefined ? {} : { taskId }),
50
+ roleName: owner.roleName
51
+ },
52
+ agentId: input.agentId,
53
+ adapterId: input.adapterId,
54
+ launchId: input.launchId,
55
+ ...(input.nativeSessionId === undefined
56
+ ? {}
57
+ : { nativeSessionId: input.nativeSessionId }),
58
+ tmux: {
59
+ serverName: yuiTmuxServerName(this.#home),
60
+ socketPath: join(tmuxSocketDirectory(this.#environment), yuiTmuxServerName(this.#home)),
61
+ sessionName: owner.scope === "task"
62
+ ? yuiTmuxSessionName(this.#home, owner.taskId)
63
+ : yuiTmuxSessionName(this.#home, "operator"),
64
+ windowName: owner.roleName,
65
+ ...(input.panePid === undefined ? {} : { panePid: input.panePid })
66
+ },
67
+ providerRoot: {
68
+ pid: root.pid,
69
+ startIdentity: root.identity.startIdentity,
70
+ ...(root.identity.processGroupId === undefined
71
+ ? {}
72
+ : { processGroupId: root.identity.processGroupId }),
73
+ ...(root.identity.processSessionId === undefined
74
+ ? {}
75
+ : { processSessionId: root.identity.processSessionId }),
76
+ attribution: discovered === undefined ? "pane-pid" : "launch-env"
77
+ },
78
+ ...(input.runtimeRoot === undefined ? {} : { runtimeRoot: input.runtimeRoot }),
79
+ recordedAt: new Date()
80
+ }));
81
+ }
82
+ /** Read-only bidirectional reconciliation; never mutates physical state. */
83
+ report() {
84
+ return reconcileSessionOwners({
85
+ records: this.#store.listSessionOwners(),
86
+ durable: durableSessionFacts(this.#store),
87
+ taskStatus: (taskId) => this.#store.getTask(taskId)?.status,
88
+ observe: (record) => observeSessionOwnerPhysical(record),
89
+ inspectPane: (taskId, roleName) => {
90
+ if (this.#tmux === undefined || taskId === undefined)
91
+ return undefined;
92
+ try {
93
+ const pane = this.#tmux.inspectPane(taskId, roleName);
94
+ return { target: pane.target, dead: pane.dead };
95
+ }
96
+ catch {
97
+ return undefined;
98
+ }
99
+ },
100
+ lastStopOutcome: (taskId, roleName, launchId) => (lastSessionTerminationOutcome(this.#store, taskId, roleName, launchId)),
101
+ now: new Date()
102
+ });
103
+ }
104
+ /**
105
+ * Stops one owner with physical exit proof. Removes the owner records only
106
+ * after every root is proven absent; a blocked result preserves them.
107
+ */
108
+ async terminateOwner(owner, options = {}) {
109
+ const records = this.#store.listSessionOwnersForOwner(owner);
110
+ const ports = {
111
+ gracefulStop: async (target) => {
112
+ if (this.#tmux === undefined)
113
+ return false;
114
+ const hostId = target.scope === "task" ? target.taskId : "operator";
115
+ try {
116
+ this.#tmux.killRole(hostId, target.roleName);
117
+ }
118
+ catch {
119
+ return this.#tmux.probeRoleStatus(hostId, target.roleName) === "exited";
120
+ }
121
+ return this.#tmux.probeRoleStatus(hostId, target.roleName) === "exited";
122
+ },
123
+ processIdentity: readLinuxProcessIdentity,
124
+ procEntryExists: (pid) => {
125
+ try {
126
+ return existsSync(`/proc/${pid}`);
127
+ }
128
+ catch {
129
+ return false;
130
+ }
131
+ },
132
+ listLaunchFencedProcesses: (launchId) => listLaunchFencedProcesses(launchId),
133
+ signalProcess: (pid, signal) => process.kill(pid, signal),
134
+ signalProcessGroup: (processGroupId, signal) => {
135
+ try {
136
+ process.kill(-processGroupId, signal);
137
+ }
138
+ catch (error) {
139
+ if (error.code !== "ESRCH")
140
+ throw error;
141
+ }
142
+ },
143
+ sleep: (milliseconds) => new Promise((resolve) => setTimeout(resolve, milliseconds)),
144
+ emit: (event) => this.#recordTerminationEvent(event),
145
+ now: () => new Date()
146
+ };
147
+ const result = await terminateSessionOwners(owner, records, ports, options);
148
+ if (result.outcome === "stop-confirmed") {
149
+ for (const record of result.confirmed) {
150
+ this.#store.removeSessionOwner(record.launchId);
151
+ }
152
+ }
153
+ return result;
154
+ }
155
+ #recordTerminationEvent(event) {
156
+ const owner = event.owner;
157
+ if (owner.scope !== "task")
158
+ return;
159
+ try {
160
+ this.#store.saveEvent(owner.taskId, createTaskEvent(this.#store.nextEventId(owner.taskId), owner.taskId, "runtime.session-termination", {
161
+ roleName: owner.roleName,
162
+ launchId: event.launchId ?? "",
163
+ nativeSessionId: event.nativeSessionId ?? "",
164
+ outcome: event.stage,
165
+ ...(event.detail === undefined ? {} : { detail: event.detail })
166
+ }, event.at));
167
+ }
168
+ catch (error) {
169
+ this.#onWarning(`Session termination event could not be persisted: ${error instanceof Error ? error.message : String(error)}`);
170
+ }
171
+ }
172
+ }
173
+ /** Projects every durable Role session generation for reconciliation. */
174
+ export function durableSessionFacts(store) {
175
+ const facts = [];
176
+ for (const task of store.listTasks()) {
177
+ for (const set of store.listRoleSessionSets(task.id)) {
178
+ for (const [agentId, session] of Object.entries(set.sessions)) {
179
+ facts.push({
180
+ scope: "task",
181
+ taskId: task.id,
182
+ roleName: set.owner.roleName,
183
+ agentId,
184
+ adapterId: session.adapterId,
185
+ ...(session.launchId === undefined ? {} : { launchId: session.launchId }),
186
+ ...(session.nativeSessionId === undefined
187
+ ? {}
188
+ : { nativeSessionId: session.nativeSessionId }),
189
+ status: session.status,
190
+ inHistory: false
191
+ });
192
+ }
193
+ for (const history of set.history ?? []) {
194
+ facts.push({
195
+ scope: "task",
196
+ taskId: task.id,
197
+ roleName: set.owner.roleName,
198
+ agentId: history.agentId,
199
+ adapterId: history.adapterId,
200
+ ...(history.launchId === undefined ? {} : { launchId: history.launchId }),
201
+ ...(history.nativeSessionId === undefined
202
+ ? {}
203
+ : { nativeSessionId: history.nativeSessionId }),
204
+ status: history.status,
205
+ inHistory: true
206
+ });
207
+ }
208
+ }
209
+ }
210
+ for (const set of store.listGlobalRoleSessionSets()) {
211
+ for (const [agentId, session] of Object.entries(set.sessions)) {
212
+ facts.push({
213
+ scope: "global",
214
+ roleName: set.owner.roleName,
215
+ agentId,
216
+ adapterId: session.adapterId,
217
+ ...(session.launchId === undefined ? {} : { launchId: session.launchId }),
218
+ ...(session.nativeSessionId === undefined
219
+ ? {}
220
+ : { nativeSessionId: session.nativeSessionId }),
221
+ status: session.status,
222
+ inHistory: false
223
+ });
224
+ }
225
+ const history = set.history;
226
+ for (const session of Object.values(history ?? {})) {
227
+ facts.push({
228
+ scope: "global",
229
+ roleName: set.owner.roleName,
230
+ agentId: session.agentId,
231
+ adapterId: session.adapterId,
232
+ ...(session.launchId === undefined ? {} : { launchId: session.launchId }),
233
+ ...(session.nativeSessionId === undefined
234
+ ? {}
235
+ : { nativeSessionId: session.nativeSessionId }),
236
+ status: session.status,
237
+ inHistory: true
238
+ });
239
+ }
240
+ }
241
+ return facts;
242
+ }
243
+ /** /proc observation for one owner record; undefined is a verification gap. */
244
+ export function observeSessionOwnerPhysical(record) {
245
+ const { pid, startIdentity } = record.providerRoot;
246
+ const current = readLinuxProcessIdentity(pid);
247
+ if (current === undefined) {
248
+ return {
249
+ alive: false,
250
+ identityConflict: false,
251
+ pid,
252
+ startIdentity,
253
+ rssBytes: 0,
254
+ ageMs: 0,
255
+ childCount: 0
256
+ };
257
+ }
258
+ if (current.startIdentity !== startIdentity) {
259
+ // PID reuse: the slot is live but it is a different process. Never kill.
260
+ return {
261
+ alive: false,
262
+ identityConflict: true,
263
+ pid,
264
+ startIdentity,
265
+ rssBytes: current.rssBytes,
266
+ ageMs: 0,
267
+ childCount: 0
268
+ };
269
+ }
270
+ if (current.state === "Z") {
271
+ // Zombie: the process has exited and only the unreaped task struct
272
+ // remains. It cannot execute or hold resources, so it is not alive.
273
+ return {
274
+ alive: false,
275
+ identityConflict: false,
276
+ pid,
277
+ startIdentity,
278
+ rssBytes: 0,
279
+ ageMs: 0,
280
+ childCount: 0
281
+ };
282
+ }
283
+ const tree = listOwnedProcessTree(pid, current.processGroupId);
284
+ return {
285
+ alive: true,
286
+ identityConflict: false,
287
+ pid,
288
+ startIdentity,
289
+ rssBytes: current.rssBytes,
290
+ ageMs: 0,
291
+ childCount: Math.max(0, tree.length - 1)
292
+ };
293
+ }
294
+ /** Reads the latest termination outcome recorded for one generation. */
295
+ export function lastSessionTerminationOutcome(store, taskId, roleName, launchId) {
296
+ if (taskId === undefined)
297
+ return undefined;
298
+ let latest;
299
+ for (const event of store.listEvents(taskId)) {
300
+ if (event.type !== "runtime.session-termination")
301
+ continue;
302
+ if (event.payload.roleName !== roleName)
303
+ continue;
304
+ if (launchId !== ""
305
+ && event.payload.launchId !== undefined
306
+ && event.payload.launchId !== ""
307
+ && event.payload.launchId !== launchId) {
308
+ continue;
309
+ }
310
+ latest = event.payload.outcome;
311
+ }
312
+ return latest;
313
+ }
314
+ /** True when one owner record's Provider root is still the exact live process. */
315
+ export function ownerRootIsLive(record) {
316
+ return isLinuxProcessLive(record.providerRoot.pid, record.providerRoot.startIdentity);
317
+ }
318
+ /** Exact tmux target for one owner, for reports and diagnostics. */
319
+ export function ownerTmuxTarget(home, owner) {
320
+ return yuiTmuxTarget(home, owner.scope === "task" ? owner.taskId : "operator", owner.roleName);
321
+ }