@astrosheep/keiyaku 2.9.8 → 2.9.10

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 (230) hide show
  1. package/README.md +50 -182
  2. package/build/.tsbuildinfo +1 -1
  3. package/build/agents/harness/control-types.js +18 -1
  4. package/build/agents/harness/index.js +2 -0
  5. package/build/agents/harness/outcome-error.js +81 -0
  6. package/build/agents/harness/projection.js +10 -7
  7. package/build/agents/harness/router.js +5 -1
  8. package/build/agents/providers/claude-agent-sdk/adapter.js +18 -4
  9. package/build/agents/providers/claude-agent-sdk/registration.js +11 -1
  10. package/build/agents/providers/claude-agent-sdk/session.js +19 -5
  11. package/build/agents/providers/codex-app-server/adapter.js +11 -6
  12. package/build/agents/providers/codex-app-server/events.js +67 -6
  13. package/build/agents/providers/codex-app-server/registration.js +11 -1
  14. package/build/agents/providers/opencode-sdk/adapter.js +8 -1
  15. package/build/agents/providers/opencode-sdk/events.js +24 -2
  16. package/build/agents/providers/opencode-sdk/registration.js +11 -1
  17. package/build/agents/providers/pi/checkpoint.js +9 -1
  18. package/build/agents/providers/pi/events.js +2 -0
  19. package/build/agents/providers/pi/registration.js +14 -1
  20. package/build/cli/commands/akuma/akuma/handler.js +1 -1
  21. package/build/cli/commands/akuma/akuma/meta.js +3 -3
  22. package/build/cli/commands/akuma/catalog.js +2 -2
  23. package/build/cli/commands/akuma/{view → show}/handler.js +5 -5
  24. package/build/cli/commands/akuma/{view → show}/meta.js +3 -3
  25. package/build/cli/commands/akuma.js +1 -1
  26. package/build/cli/commands/contract/amend/handler.js +5 -4
  27. package/build/cli/commands/contract/arc/handler.js +5 -4
  28. package/build/cli/commands/contract/audit/handler.js +2 -2
  29. package/build/cli/commands/contract/bind/handler.js +12 -3
  30. package/build/cli/commands/contract/bind/meta.js +45 -15
  31. package/build/cli/commands/contract/forfeit/handler.js +9 -5
  32. package/build/cli/commands/contract/log/handler.js +2 -2
  33. package/build/cli/commands/contract/petition/handler.js +34 -7
  34. package/build/cli/commands/contract/renew/handler.js +7 -7
  35. package/build/cli/commands/contract/renew/meta.js +3 -3
  36. package/build/cli/commands/metadata.js +10 -6
  37. package/build/cli/commands/projection/call/meta.js +1 -1
  38. package/build/cli/commands/projection/catalog.js +2 -0
  39. package/build/cli/commands/projection/history/handler.js +21 -0
  40. package/build/cli/commands/projection/history/meta.js +9 -0
  41. package/build/cli/commands/projection/shared.js +3 -4
  42. package/build/cli/commands/projection/status/handler.js +3 -3
  43. package/build/cli/commands/projection/wait/handler.js +20 -11
  44. package/build/cli/commands/task/add/handler.js +14 -5
  45. package/build/cli/commands/task/add/meta.js +2 -2
  46. package/build/cli/commands/task/catalog.js +4 -0
  47. package/build/cli/commands/task/done/handler.js +1 -4
  48. package/build/cli/commands/task/done/meta.js +1 -1
  49. package/build/cli/commands/task/drop/meta.js +1 -1
  50. package/build/cli/commands/task/hold/handler.js +3 -0
  51. package/build/cli/commands/task/hold/meta.js +8 -0
  52. package/build/cli/commands/task/log/handler.js +1 -1
  53. package/build/cli/commands/task/log/meta.js +2 -2
  54. package/build/cli/commands/task/resume/handler.js +3 -0
  55. package/build/cli/commands/task/resume/meta.js +8 -0
  56. package/build/cli/commands/task/shared.js +39 -14
  57. package/build/cli/commands/task/show/handler.js +1 -1
  58. package/build/cli/commands/task/show/meta.js +1 -1
  59. package/build/cli/commands/task/start/meta.js +1 -1
  60. package/build/cli/commands/task/stop/handler.js +1 -4
  61. package/build/cli/commands/task/stop/meta.js +1 -1
  62. package/build/cli/commands/task/update/handler.js +11 -3
  63. package/build/cli/commands/task/update/meta.js +3 -3
  64. package/build/cli/completion.js +3 -2
  65. package/build/cli/flags.js +51 -14
  66. package/build/cli/help.js +4 -1
  67. package/build/cli/index.js +40 -25
  68. package/build/cli/parse-flags.js +40 -7
  69. package/build/cli/parse-metadata.js +5 -2
  70. package/build/cli/parse-selectors.js +2 -3
  71. package/build/cli/parse.js +40 -6
  72. package/build/cli/projection-address.js +12 -10
  73. package/build/cli/render/amendment-warning.js +8 -0
  74. package/build/cli/render/arc.js +6 -2
  75. package/build/cli/render/audit.js +400 -81
  76. package/build/cli/render/kanshi.js +242 -79
  77. package/build/cli/render/lifecycle.js +56 -0
  78. package/build/cli/render/misc.js +24 -28
  79. package/build/cli/render/petition.js +5 -0
  80. package/build/cli/render/projection-activity.js +65 -29
  81. package/build/cli/render/projection-history.js +64 -0
  82. package/build/cli/render/shared.js +23 -9
  83. package/build/cli/render/status.js +13 -35
  84. package/build/cli/render/task.js +51 -26
  85. package/build/cli/render/terminal-failure.js +3 -1
  86. package/build/cli/render/wait.js +45 -69
  87. package/build/cli/subagent-guard.js +1 -1
  88. package/build/cli/types.js +1 -1
  89. package/build/config/env-keys.js +3 -1
  90. package/build/config/env.js +24 -1
  91. package/build/config/settings/schema.js +3 -1
  92. package/build/core/address-carrier.js +89 -0
  93. package/build/core/addressing.js +255 -196
  94. package/build/core/amend.js +115 -38
  95. package/build/core/amendment-criteria.js +17 -0
  96. package/build/core/amendment-warning.js +10 -0
  97. package/build/core/arc.js +184 -83
  98. package/build/core/audit/candidate.js +110 -9
  99. package/build/core/audit/coordinates.js +120 -65
  100. package/build/core/audit/evidence.js +19 -23
  101. package/build/core/audit/facade.js +34 -11
  102. package/build/core/audit/report.js +5 -5
  103. package/build/core/bind-reconciliation.js +390 -0
  104. package/build/core/bind-workspace.js +12 -20
  105. package/build/core/bind.js +242 -273
  106. package/build/core/call/call.js +1 -0
  107. package/build/core/call/context.js +46 -71
  108. package/build/core/call/prompt.js +6 -3
  109. package/build/core/context.js +49 -35
  110. package/build/core/contract-carrier-runtime.js +408 -0
  111. package/build/core/contract-carrier.js +270 -0
  112. package/build/core/contract-view.js +33 -27
  113. package/build/core/contract.js +5 -1
  114. package/build/core/derived-replay.js +207 -0
  115. package/build/core/draft.js +125 -57
  116. package/build/core/forfeit.js +113 -56
  117. package/build/core/identifier-slug.js +63 -13
  118. package/build/core/ids.js +4 -4
  119. package/build/core/lifecycle-history.js +35 -0
  120. package/build/core/lifecycle-recovery.js +117 -0
  121. package/build/core/lifecycle-runner.js +377 -0
  122. package/build/core/log.js +37 -11
  123. package/build/core/outcome-base.js +181 -0
  124. package/build/core/process-group.js +139 -0
  125. package/build/core/projection/generation/database.js +82 -7
  126. package/build/core/projection/generation/model.js +9 -36
  127. package/build/core/projection/generation/projection-generation-continuation.js +10 -22
  128. package/build/core/projection/generation/projection-generation-execution.js +13 -6
  129. package/build/core/projection/index.js +5 -4
  130. package/build/core/projection/projection-activity.js +29 -8
  131. package/build/core/projection/projection-alias.js +23 -10
  132. package/build/core/projection/projection-core.js +21 -7
  133. package/build/core/projection/projection-execution-observer.js +91 -39
  134. package/build/core/projection/projection-history.js +474 -0
  135. package/build/core/projection/projection-runner-lock.js +12 -0
  136. package/build/core/projection/projection-status-observation.js +365 -0
  137. package/build/core/projection/projection-status.js +35 -337
  138. package/build/core/projection/projection-terminal-failure.js +13 -8
  139. package/build/core/projection/projection-wait.js +7 -2
  140. package/build/core/projection/tell/launch-store.js +244 -0
  141. package/build/core/projection/tell/model.js +3 -3
  142. package/build/core/projection/tell/store.js +70 -118
  143. package/build/core/registry.js +22 -87
  144. package/build/core/renew-build.js +424 -0
  145. package/build/core/renew-plan.js +239 -0
  146. package/build/core/renew-rewrite.js +77 -0
  147. package/build/core/renew.js +295 -520
  148. package/build/core/repository-ledger/accepted-fold-read.js +19 -0
  149. package/build/core/repository-ledger/accepted-tail.js +116 -0
  150. package/build/core/repository-ledger/admission.js +38 -0
  151. package/build/core/repository-ledger/authoritative-transaction.js +1 -0
  152. package/build/core/repository-ledger/claim-evidence.js +147 -0
  153. package/build/core/repository-ledger/codec.js +297 -0
  154. package/build/core/repository-ledger/current-state-store.js +853 -0
  155. package/build/core/repository-ledger/fold-repository.js +375 -0
  156. package/build/core/repository-ledger/fold.js +248 -0
  157. package/build/core/repository-ledger/git-object-store.js +243 -0
  158. package/build/core/repository-ledger/history-read.js +15 -0
  159. package/build/core/repository-ledger/incremental-read.js +153 -0
  160. package/build/core/repository-ledger/index.js +15 -0
  161. package/build/core/repository-ledger/inventory-plan.js +75 -0
  162. package/build/core/repository-ledger/inventory.js +310 -0
  163. package/build/core/repository-ledger/publication-admission.js +125 -0
  164. package/build/core/repository-ledger/publication.js +353 -0
  165. package/build/core/repository-ledger/read-model.js +469 -0
  166. package/build/core/repository-ledger/transaction-membership.js +78 -0
  167. package/build/core/repository-ledger/traversal.js +404 -0
  168. package/build/core/repository-ledger/write-transaction.js +339 -0
  169. package/build/core/seal.js +168 -161
  170. package/build/core/settlement/claim-delivery.js +42 -215
  171. package/build/core/settlement/claim.js +235 -153
  172. package/build/core/settlement/index.js +4 -4
  173. package/build/core/settlement/petition-claim-gates.js +52 -33
  174. package/build/core/settlement/petition-forfeit.js +12 -5
  175. package/build/core/settlement/petition-head-guard.js +8 -5
  176. package/build/core/settlement/petition-preview.js +68 -56
  177. package/build/core/settlement/petition-window.js +50 -0
  178. package/build/core/settlement/petition.js +252 -131
  179. package/build/core/settlement/queue-read-model.js +72 -95
  180. package/build/core/settlement/queue-seat-allocation.js +35 -126
  181. package/build/core/settlement/queue.js +1 -1
  182. package/build/core/settlement/settlement.js +172 -80
  183. package/build/core/settlement/verdict.js +112 -66
  184. package/build/core/settlement/verification-coordination.js +305 -0
  185. package/build/core/settlement/verification-supervisor.js +275 -0
  186. package/build/core/settlement/verification.js +398 -31
  187. package/build/core/status/board.js +125 -127
  188. package/build/core/status/lifecycle.js +7 -80
  189. package/build/core/structured-query.js +23 -0
  190. package/build/core/target-ref.js +1 -1
  191. package/build/core/task/board.js +59 -24
  192. package/build/core/task/commands.js +181 -33
  193. package/build/core/task/coordinate.js +56 -0
  194. package/build/core/task/document.js +25 -9
  195. package/build/core/task/index.js +7 -4
  196. package/build/core/task/query.js +17 -27
  197. package/build/core/task/settlement-git.js +164 -179
  198. package/build/core/task/settlement-policy.js +15 -11
  199. package/build/core/task/source-board.js +31 -13
  200. package/build/core/task/task-bind-preparation.js +193 -0
  201. package/build/core/task/task-contract.js +102 -193
  202. package/build/core/task/task-file-transaction.js +171 -0
  203. package/build/core/task/task-git-runtime.js +76 -14
  204. package/build/core/task/task-git-store.js +68 -169
  205. package/build/core/task/task-store-repository.js +13 -6
  206. package/build/core/task/task-worktree.js +6 -33
  207. package/build/core/task/task.js +1 -1
  208. package/build/core/task/tree.js +3 -3
  209. package/build/core/task/validation.js +4 -3
  210. package/build/core/transcripts.js +93 -1
  211. package/build/core/worktree-bootstrap.js +44 -36
  212. package/build/core/worktree-path.js +210 -77
  213. package/build/flow-error.js +7 -7
  214. package/build/generated/version.js +2 -2
  215. package/build/git/core.js +76 -13
  216. package/build/git/refs.js +168 -46
  217. package/build/git/streaming-batch.js +73 -13
  218. package/package.json +1 -1
  219. package/skills/keiyaku/SKILL.md +1 -1
  220. package/skills/keiyaku-akuma/SKILL.md +3 -3
  221. package/skills/keiyaku-task/SKILL.md +4 -2
  222. package/skills/keiyaku-workflow/SKILL.md +88 -203
  223. package/build/core/entry.js +0 -305
  224. package/build/core/ledger-batch.js +0 -194
  225. package/build/core/ledger.js +0 -84
  226. package/build/core/projection/leash.js +0 -87
  227. package/build/core/ref-log.js +0 -119
  228. package/build/core/renew-session.js +0 -128
  229. package/build/core/status/ledger-batch.js +0 -1
  230. package/build/core/status/reconciliation.js +0 -137
@@ -1,20 +1,14 @@
1
1
  import fs from "node:fs";
2
2
  import * as path from "node:path";
3
3
  import { isErrnoException } from "../../errno.js";
4
- import { executionEventsPath, isProjectionId, projectionRoot, } from "./projection-core.js";
5
- import { readProjectionAlias } from "./projection-alias.js";
6
- import { ProjectionStateError } from "../atomic-publish.js";
7
- import { foldPublicProjectionActivity, } from "./projection-activity.js";
4
+ import { isProjectionId, projectionRoot, projectionRootAtCoordinate } from "./projection-core.js";
5
+ import { projectionAliasRootAtCoordinate, readProjectionAliasAtRoot, } from "./projection-alias.js";
6
+ import { resolveProjectionCoordinate } from "./projection-coordinate.js";
8
7
  import { readProjectionIdentity } from "./projection-identity.js";
9
- import { readProjectionTellCounts } from "./tell/store.js";
10
- import { ProjectionGenerationStoreError, } from "./generation/store.js";
11
8
  import { PROJECTION_ADOPTION_TIMEOUT_MS } from "./generation/projection-generation-launcher.js";
12
9
  import { observeProjectionLifeSnapshot, } from "./projection-life-observer.js";
13
- import { selectCurrentGeneration, } from "./projection-life-protocol.js";
14
- import { invalidActivityEventDiagnostic, selectValidStoredAgentEvents } from "../stored-agent-event.js";
15
10
  import { worktreeContainsCoordinate } from "../worktree-path.js";
16
- import { projectTerminalFailure, } from "./projection-terminal-failure.js";
17
- import { extractTerminalTimestamp } from "./generation/model.js";
11
+ import { readProjectionRow, readProjectionSummary } from "./projection-status-observation.js";
18
12
  const MAX_STATUS_FILE_BYTES = 256 * 1024;
19
13
  const STATUS_GROUP = {
20
14
  "startup-timeout": 0,
@@ -84,324 +78,13 @@ export function prioritizeProjectionStatusRows(rows) {
84
78
  export function requiresProjectionStatusDetails(state) {
85
79
  return !isOmissibleProjectionStatusState(state);
86
80
  }
87
- class ProjectionStatusFileError extends Error {
88
- diagnostic;
89
- constructor(kind, filePath) {
90
- const label = path.basename(filePath);
91
- const detail = `${label} is not a regular file`;
92
- super(detail);
93
- this.name = "ProjectionStatusFileError";
94
- this.diagnostic = { kind, detail };
95
- }
96
- }
97
- function oversizedProjectionFileDiagnostic(filePath) {
98
- return {
99
- kind: "oversized-projection-file",
100
- fileName: path.basename(filePath),
101
- };
102
- }
103
- function recordProjectionStatusFileError(error, diagnostics) {
104
- if (!(error instanceof ProjectionStatusFileError))
105
- return false;
106
- diagnostics.push(error.diagnostic);
107
- return true;
108
- }
109
- function readBoundedRegularFileTail(filePath) {
110
- const before = fs.lstatSync(filePath);
111
- if (!before.isFile())
112
- throw new ProjectionStatusFileError("non-regular-projection-file", filePath);
113
- let fd;
114
- try {
115
- fd = fs.openSync(filePath, fs.constants.O_RDONLY | fs.constants.O_NONBLOCK | fs.constants.O_NOFOLLOW);
116
- }
117
- catch (error) {
118
- if (isErrnoException(error) && error.code === "ELOOP") {
119
- throw new ProjectionStatusFileError("non-regular-projection-file", filePath);
120
- }
121
- throw error;
122
- }
123
- try {
124
- const opened = fs.fstatSync(fd);
125
- if (!opened.isFile())
126
- throw new ProjectionStatusFileError("non-regular-projection-file", filePath);
127
- const snapshotSize = opened.size;
128
- const truncated = snapshotSize > MAX_STATUS_FILE_BYTES;
129
- const readLength = Math.min(snapshotSize, MAX_STATUS_FILE_BYTES);
130
- const readOffset = Math.max(0, snapshotSize - MAX_STATUS_FILE_BYTES);
131
- const buffer = Buffer.allocUnsafe(readLength);
132
- let total = 0;
133
- while (total < readLength) {
134
- const bytesRead = fs.readSync(fd, buffer, total, readLength - total, readOffset + total);
135
- if (bytesRead === 0)
136
- break;
137
- total += bytesRead;
138
- }
139
- let tail = buffer.subarray(0, total);
140
- if (truncated) {
141
- const firstLf = tail.indexOf(0x0a);
142
- if (firstLf < 0) {
143
- return { content: "", truncated: true };
144
- }
145
- tail = tail.subarray(firstLf + 1);
146
- }
147
- return { content: tail.toString("utf8"), truncated };
148
- }
149
- finally {
150
- fs.closeSync(fd);
151
- }
152
- }
153
- function displayedTellCounts(counts) {
154
- return {
155
- inbox: counts.inbox,
156
- inflight: counts.inflight,
157
- submitted: counts.submitted,
158
- consumed: counts.consumed,
159
- };
160
- }
161
- function emptyTellCounts() {
162
- return { inbox: 0, inflight: 0, submitted: 0, consumed: 0 };
163
- }
164
- function readIdentityFacts(projectionDir, diagnostics) {
165
- try {
166
- const identity = readProjectionIdentity(projectionDir);
167
- return {
168
- identity,
169
- pid: identity.pid,
170
- akuma: identity.akuma,
171
- provider: identity.provider,
172
- mintedAt: identity.mintedAt,
173
- workspaceRoot: identity.workspaceRoot,
174
- };
175
- }
176
- catch (error) {
177
- if (isErrnoException(error) && error.code === "ENOENT") {
178
- diagnostics.push({ kind: "missing-identity", detail: "heart identity missing" });
179
- return { identity: null, pid: undefined, akuma: undefined, provider: undefined, mintedAt: undefined, workspaceRoot: undefined };
180
- }
181
- if (error instanceof ProjectionGenerationStoreError) {
182
- diagnostics.push({ kind: "invalid-identity", detail: error.message });
183
- return { identity: null, pid: undefined, akuma: undefined, provider: undefined, mintedAt: undefined, workspaceRoot: undefined };
184
- }
185
- if (error instanceof ProjectionStateError) {
186
- diagnostics.push({ kind: "invalid-identity", detail: error.message });
187
- return { identity: null, pid: undefined, akuma: undefined, provider: undefined, mintedAt: undefined, workspaceRoot: undefined };
188
- }
189
- diagnostics.push({ kind: "unreadable-identity", detail: `heart identity unreadable: ${String(error)}` });
190
- return { identity: null, pid: undefined, akuma: undefined, provider: undefined, mintedAt: undefined, workspaceRoot: undefined };
191
- }
192
- }
193
- function readHeartTellCounts(projectionDir, diagnostics) {
194
- try {
195
- return displayedTellCounts(readProjectionTellCounts(projectionDir));
196
- }
197
- catch (error) {
198
- if (error instanceof ProjectionGenerationStoreError || error instanceof ProjectionStateError) {
199
- diagnostics.push({ kind: "unreadable-tells", detail: error.message });
200
- return emptyTellCounts();
201
- }
202
- diagnostics.push({ kind: "unreadable-tells", detail: `heart tells unreadable: ${String(error)}` });
203
- return emptyTellCounts();
204
- }
205
- }
206
- function readStoredEvents(projectionDir, executionId, diagnostics) {
207
- let content;
208
- let truncated = false;
209
- const eventsPath = executionEventsPath(projectionDir, executionId);
210
- try {
211
- const read = readBoundedRegularFileTail(eventsPath);
212
- content = read.content;
213
- truncated = read.truncated;
214
- }
215
- catch (error) {
216
- if (isErrnoException(error) && error.code === "ENOENT")
217
- return [];
218
- if (recordProjectionStatusFileError(error, diagnostics))
219
- return [];
220
- diagnostics.push({ kind: "unreadable-activity", detail: `activity ledger unreadable: ${String(error)}` });
221
- return [];
222
- }
223
- if (truncated) {
224
- diagnostics.push(oversizedProjectionFileDiagnostic(eventsPath));
225
- }
226
- // Parse only LF-terminated records. An unterminated final line is ignored even
227
- // when it happens to parse: it may be a concurrent append fragment.
228
- const lines = content.split("\n");
229
- const values = [];
230
- let malformed = 0;
231
- for (const line of lines.slice(0, -1)) {
232
- try {
233
- values.push(JSON.parse(line));
234
- }
235
- catch {
236
- malformed += 1;
237
- }
238
- }
239
- if (malformed > 0) {
240
- diagnostics.push({
241
- kind: "invalid-activity-event",
242
- detail: `${malformed} persisted activity event${malformed === 1 ? "" : "s"} malformed`,
243
- });
244
- }
245
- return values;
246
- }
247
- function latestValidEvent(events) {
248
- let latest;
249
- for (const event of events) {
250
- const atMs = Date.parse(event.at);
251
- if (!Number.isFinite(atMs))
252
- continue;
253
- if (latest === undefined || atMs > latest.atMs)
254
- latest = { at: event.at, atMs };
255
- }
256
- return latest;
257
- }
258
- function readActivityFacts(projectionDir, executionId, nowMs, diagnostics, fallbackAt) {
259
- const fallbackAtMs = fallbackAt === undefined ? undefined : Date.parse(fallbackAt);
260
- const validFallback = fallbackAt !== undefined && Number.isFinite(fallbackAtMs)
261
- ? { at: fallbackAt, atMs: fallbackAtMs }
262
- : undefined;
263
- if (!executionId) {
264
- return validFallback
265
- ? { activityAt: validFallback.at, activityAtMs: validFallback.atMs, activityAgeMs: Math.max(0, nowMs - validFallback.atMs) }
266
- : {};
267
- }
268
- const stored = readStoredEvents(projectionDir, executionId, diagnostics);
269
- const { events, malformed } = selectValidStoredAgentEvents(stored);
270
- const invalidActivity = invalidActivityEventDiagnostic(malformed);
271
- if (invalidActivity)
272
- diagnostics.push(invalidActivity);
273
- const latest = latestValidEvent(events) ?? validFallback;
274
- return {
275
- ...(events.length > 0 ? { activity: foldPublicProjectionActivity(events) } : {}),
276
- ...(latest !== undefined
277
- ? {
278
- activityAt: latest.at,
279
- activityAtMs: latest.atMs,
280
- activityAgeMs: Math.max(0, nowMs - latest.atMs),
281
- }
282
- : {}),
283
- };
284
- }
285
- function terminalTimestamp(current) {
286
- return extractTerminalTimestamp(current?.verdict?.facts);
287
- }
288
- function readProjectionSummary(projectionDir, id, alias, nowMs) {
289
- const diagnostics = [];
290
- const snapshot = observeProjectionLifeSnapshot(projectionDir, {
291
- nowMs,
292
- startupTimeoutMs: PROJECTION_ADOPTION_TIMEOUT_MS,
293
- });
294
- if (snapshot.compatibility === "incompatible") {
295
- return {
296
- id,
297
- ...(alias ? { alias } : {}),
298
- projectionDir,
299
- snapshot,
300
- identityFacts: null,
301
- diagnostics,
302
- state: "incompatible",
303
- };
304
- }
305
- const identityFacts = readIdentityFacts(projectionDir, diagnostics);
306
- const life = snapshot.life;
307
- const phase = life.phase;
308
- const state = phase === "active" || phase === "quiescent" ? "out" : phase;
309
- return {
310
- id,
311
- ...(alias ? { alias } : {}),
312
- projectionDir,
313
- snapshot,
314
- identityFacts,
315
- diagnostics,
316
- state,
317
- };
318
- }
319
- function readProjectionRow(summary, nowMs, readDetails) {
320
- const { id, alias, projectionDir, snapshot } = summary;
321
- const diagnostics = [...summary.diagnostics];
322
- if (snapshot.compatibility === "incompatible") {
323
- return {
324
- id,
325
- ...(alias ? { alias } : {}),
326
- state: "incompatible",
327
- observation: snapshot,
328
- tellCounts: emptyTellCounts(),
329
- diagnostics,
330
- };
331
- }
332
- const identityFacts = summary.identityFacts;
333
- if (!identityFacts.identity) {
334
- const diagnostic = diagnostics.map((entry) => entry.detail).find((detail) => detail !== undefined)
335
- ?? "projection identity could not be observed";
336
- return {
337
- id,
338
- ...(alias ? { alias } : {}),
339
- state: "unknown",
340
- observation: {
341
- compatibility: "current",
342
- lifecycle: { phase: "unknown", diagnostic },
343
- },
344
- tellCounts: emptyTellCounts(),
345
- diagnostics,
346
- };
347
- }
348
- const life = snapshot.life;
349
- const current = snapshot.records ? selectCurrentGeneration(snapshot.records) : null;
350
- const adoptedAt = current?.adoption?.facts.adoptedAt;
351
- const adoptedAtMs = typeof adoptedAt === "string"
352
- ? Date.parse(adoptedAt)
353
- : Number.NaN;
354
- const tellCounts = readDetails
355
- ? readHeartTellCounts(projectionDir, diagnostics)
356
- : emptyTellCounts();
357
- const activityFacts = readDetails
358
- ? readActivityFacts(projectionDir, life.executionId, nowMs, diagnostics, typeof adoptedAt === "string" && Number.isFinite(adoptedAtMs) ? adoptedAt : identityFacts.mintedAt)
359
- : {};
360
- const mintedAtMs = Date.parse(identityFacts.mintedAt ?? "");
361
- const terminalAt = terminalTimestamp(current);
362
- const durationAnchorMs = current?.verdict
363
- ? (terminalAt === undefined ? Number.NaN : Date.parse(terminalAt))
364
- : nowMs;
365
- const durationMs = Number.isFinite(mintedAtMs) && Number.isFinite(durationAnchorMs)
366
- ? Math.max(0, durationAnchorMs - mintedAtMs)
367
- : undefined;
368
- const displayAgeMs = activityFacts.activityAgeMs
369
- ?? (Number.isFinite(mintedAtMs) ? Math.max(0, nowMs - mintedAtMs) : undefined);
370
- if (life.phase === "unknown" && life.diagnostic) {
371
- diagnostics.push({ kind: "unknown-lifecycle", detail: life.diagnostic });
372
- }
373
- const phase = life.phase;
374
- const state = phase === "active" || phase === "quiescent" ? "out" : phase;
375
- const terminalFailure = current?.verdict
376
- ? projectTerminalFailure(current.verdict.facts.state, current.verdict.facts)
377
- : undefined;
378
- return {
379
- id,
380
- ...(alias ? { alias } : {}),
381
- state,
382
- observation: { compatibility: "current", lifecycle: life },
383
- akuma: identityFacts.akuma,
384
- provider: identityFacts.provider,
385
- pid: identityFacts.pid,
386
- mintedAt: identityFacts.mintedAt,
387
- ...(terminalAt ? { terminalAt } : {}),
388
- ...(durationMs !== undefined ? { durationMs } : {}),
389
- ...(identityFacts.workspaceRoot ? { workspaceRoot: identityFacts.workspaceRoot } : {}),
390
- ...activityFacts,
391
- ...(displayAgeMs !== undefined ? { activityAgeMs: displayAgeMs } : {}),
392
- ...(terminalFailure ? { terminalFailure } : {}),
393
- tellCounts,
394
- diagnostics,
395
- };
396
- }
397
- async function readProjectionStatusAliases(cwd, root) {
81
+ async function readProjectionStatusAliases(root) {
398
82
  const aliases = new Map();
399
- const aliasRoot = path.join(path.dirname(path.dirname(root)), ".keiyaku", "projection-alias");
400
83
  try {
401
- for (const alias of fs.readdirSync(aliasRoot).sort()) {
84
+ for (const alias of fs.readdirSync(root).sort()) {
402
85
  if (alias.startsWith("."))
403
86
  continue;
404
- const id = await readProjectionAlias(cwd, alias);
87
+ const id = await readProjectionAliasAtRoot(root, alias);
405
88
  if (id)
406
89
  aliases.set(id, alias);
407
90
  }
@@ -424,8 +107,8 @@ function readProjectionTopologyEntries(directory) {
424
107
  throw error;
425
108
  }
426
109
  }
427
- async function readProjectionRootTopology(cwd) {
428
- const root = await projectionRoot(cwd);
110
+ function readProjectionRootTopology(coordinate) {
111
+ const root = projectionRootAtCoordinate(coordinate);
429
112
  const akumas = readProjectionTopologyEntries(root);
430
113
  return akumas ? { root, akumas } : null;
431
114
  }
@@ -433,7 +116,7 @@ function readAkumaProjectionStatusSummaries(root, akuma, aliases, nowMs) {
433
116
  const akumaDir = path.join(root, akuma);
434
117
  const hexes = readProjectionTopologyEntries(akumaDir);
435
118
  if (!hexes)
436
- return [];
119
+ return null;
437
120
  const summaries = [];
438
121
  for (const hex of hexes) {
439
122
  const id = `${akuma}/${hex}`;
@@ -447,7 +130,9 @@ function readAkumaProjectionStatusSummaries(root, akuma, aliases, nowMs) {
447
130
  catch {
448
131
  continue;
449
132
  }
450
- summaries.push(readProjectionSummary(dir, id, aliases.get(id), nowMs));
133
+ summaries.push(readProjectionSummary(dir, id, aliases.get(id), nowMs, {
134
+ requiresIdentity: requiresProjectionStatusDetails,
135
+ }));
451
136
  }
452
137
  return summaries;
453
138
  }
@@ -456,24 +141,33 @@ function isNonterminalProjectionStatusSummary(summary) {
456
141
  && requiresProjectionStatusDetails(summary.state);
457
142
  }
458
143
  export async function readOperationalProjectionStatusBoard(cwd, nowMs) {
459
- const topology = await readProjectionRootTopology(cwd);
144
+ const coordinate = await resolveProjectionCoordinate(cwd);
145
+ const topology = readProjectionRootTopology(coordinate);
460
146
  if (!topology)
461
147
  return null;
462
148
  const { root, akumas } = topology;
463
- const aliases = await readProjectionStatusAliases(cwd, root);
149
+ const aliases = await readProjectionStatusAliases(projectionAliasRootAtCoordinate(coordinate));
464
150
  const summaries = [];
465
151
  for (const akuma of akumas) {
466
152
  if (akuma.startsWith("."))
467
153
  continue;
468
- summaries.push(...readAkumaProjectionStatusSummaries(root, akuma, aliases, nowMs));
154
+ const scoped = readAkumaProjectionStatusSummaries(root, akuma, aliases, nowMs);
155
+ // Seeing an entry and then being unable to observe its namespace is not an
156
+ // observable zero. Keep the whole aggregate surface unavailable.
157
+ if (scoped === null)
158
+ return null;
159
+ summaries.push(...scoped);
469
160
  }
470
161
  const rows = summaries.map((summary) => readProjectionRow(summary, nowMs, requiresProjectionStatusDetails(summary.state)));
471
- return rows.length > 0 ? prioritizeProjectionStatusRows(rows) : null;
162
+ // A published root is an observable surface even when it currently has no
163
+ // rows. The caller must distinguish that fact from an unavailable root.
164
+ return prioritizeProjectionStatusRows(rows);
472
165
  }
473
166
  export async function readExactProjectionStatus(cwd, nowMs, projectionId) {
474
167
  if (!isProjectionId(projectionId))
475
168
  return null;
476
- const root = await projectionRoot(cwd);
169
+ const coordinate = await resolveProjectionCoordinate(cwd);
170
+ const root = projectionRootAtCoordinate(coordinate);
477
171
  try {
478
172
  if (!fs.lstatSync(root).isDirectory())
479
173
  return null;
@@ -494,13 +188,14 @@ export async function readExactProjectionStatus(cwd, nowMs, projectionId) {
494
188
  return null;
495
189
  throw error;
496
190
  }
497
- const aliases = await readProjectionStatusAliases(cwd, root);
191
+ const aliases = await readProjectionStatusAliases(projectionAliasRootAtCoordinate(coordinate));
498
192
  return readProjectionRow(readProjectionSummary(dir, projectionId, aliases.get(projectionId), nowMs), nowMs, true);
499
193
  }
500
194
  export async function readAkumaNonterminalProjectionStatus(cwd, nowMs, akuma) {
501
195
  if (!isProjectionId(`${akuma}/00000000`))
502
196
  return null;
503
- const root = await projectionRoot(cwd);
197
+ const coordinate = await resolveProjectionCoordinate(cwd);
198
+ const root = projectionRootAtCoordinate(coordinate);
504
199
  try {
505
200
  if (!fs.lstatSync(root).isDirectory())
506
201
  return null;
@@ -510,8 +205,11 @@ export async function readAkumaNonterminalProjectionStatus(cwd, nowMs, akuma) {
510
205
  return null;
511
206
  throw error;
512
207
  }
513
- const aliases = await readProjectionStatusAliases(cwd, root);
514
- const rows = readAkumaProjectionStatusSummaries(root, akuma, aliases, nowMs)
208
+ const aliases = await readProjectionStatusAliases(projectionAliasRootAtCoordinate(coordinate));
209
+ const summaries = readAkumaProjectionStatusSummaries(root, akuma, aliases, nowMs);
210
+ if (summaries === null)
211
+ return null;
212
+ const rows = summaries
515
213
  .filter(isNonterminalProjectionStatusSummary)
516
214
  .map((summary) => readProjectionRow(summary, nowMs, true))
517
215
  .sort(compareProjectionStatusRows);
@@ -43,15 +43,19 @@ function artifactId(source) {
43
43
  }
44
44
  function providerFailureCause(value) {
45
45
  const source = objectValue(value);
46
- return source?.kind === "rate_limit_exhausted"
47
- ? { kind: "rate_limit_exhausted" }
48
- : undefined;
46
+ return source && isHarnessFailureValue(source) ? source : undefined;
47
+ }
48
+ function isHarnessFailureValue(value) {
49
+ if (value === null || typeof value === "boolean" || typeof value === "string")
50
+ return true;
51
+ if (typeof value === "number")
52
+ return Number.isFinite(value);
53
+ if (Array.isArray(value))
54
+ return value.every(isHarnessFailureValue);
55
+ const object = objectValue(value);
56
+ return object !== undefined && Object.values(object).every(isHarnessFailureValue);
49
57
  }
50
- /**
51
- * Convert artifact-rich verdict facts into the strict public allowlist. Raw
52
- * messages, output, diagnostics, sessions, provider payloads, and unknown
53
- * strings have no path through this function.
54
- */
58
+ /** Convert the public harness terminal failure into the shared projection face. */
55
59
  export function projectTerminalFailure(state, facts) {
56
60
  if (state !== "failed" && state !== "lost-runner" && state !== "launch-failed")
57
61
  return undefined;
@@ -97,6 +101,7 @@ export function projectTerminalFailure(state, facts) {
97
101
  class: "harness",
98
102
  code,
99
103
  phase: source.phase === "adoption" ? "adoption" : "runtime",
104
+ ...(typeof error.message === "string" ? { message: error.message } : {}),
100
105
  ...(timeoutMs !== undefined ? { timeoutMs } : {}),
101
106
  ...(publicExitCode !== undefined ? { exitCode: publicExitCode } : {}),
102
107
  ...(publicSignal ? { signal: publicSignal } : {}),
@@ -1,7 +1,8 @@
1
1
  import * as fs from "node:fs";
2
2
  import * as path from "node:path";
3
3
  import { foldPublicProjectionActivity } from "./projection-activity.js";
4
- import { readGenerationEvents, isProjectionId, projectionRoot, } from "./projection-core.js";
4
+ import { readGenerationEvents, isProjectionId, projectionRootAtCoordinate, } from "./projection-core.js";
5
+ import { resolveProjectionCoordinate } from "./projection-coordinate.js";
5
6
  import { readProjectionIdentity } from "./projection-identity.js";
6
7
  import { RESPONSE_ARTIFACT_ID_RE } from "../response-artifact-id.js";
7
8
  import { extractTerminalTimestamp } from "./generation/model.js";
@@ -15,7 +16,11 @@ import { tellProjection } from "./projection-wake.js";
15
16
  import { FlowError } from "../../flow-error.js";
16
17
  /** Return the commission-bound projection IDs visible at one admission instant. */
17
18
  export async function snapshotCommissionProjectionIds(cwd, commissionId) {
18
- const root = await projectionRoot(cwd);
19
+ return snapshotCommissionProjectionIdsAtCoordinate(await resolveProjectionCoordinate(cwd), commissionId);
20
+ }
21
+ /** Scan one already-resolved projection coordinate for commission-bound IDs. */
22
+ export async function snapshotCommissionProjectionIdsAtCoordinate(coordinate, commissionId) {
23
+ const root = projectionRootAtCoordinate(coordinate);
19
24
  let akumas;
20
25
  try {
21
26
  akumas = fs.readdirSync(root).sort();