@astrosheep/keiyaku 2.9.9 → 2.9.11

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 +93 -174
  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 +2 -1
  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 +6 -1
  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 +3 -2
  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 +9 -16
  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 +3 -5
  87. package/build/cli/subagent-guard.js +1 -1
  88. package/build/cli/types.js +1 -1
  89. package/build/config/env-keys.js +2 -0
  90. package/build/config/env.js +23 -0
  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 +6 -5
  129. package/build/core/projection/index.js +5 -4
  130. package/build/core/projection/projection-activity.js +27 -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 +21 -358
  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 +114 -119
  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
@@ -0,0 +1,365 @@
1
+ import fs from "node:fs";
2
+ import * as path from "node:path";
3
+ import { isErrnoException } from "../../errno.js";
4
+ import { executionEventsPath } from "./projection-core.js";
5
+ import { ProjectionStateError } from "../atomic-publish.js";
6
+ import { foldPublicProjectionActivity } from "./projection-activity.js";
7
+ import { readProjectionIdentity } from "./projection-identity.js";
8
+ import { readPendingTellWindow, readProjectionTellCounts } from "./tell/store.js";
9
+ import { ProjectionGenerationStoreError } from "./generation/store.js";
10
+ import { PROJECTION_ADOPTION_TIMEOUT_MS } from "./generation/projection-generation-launcher.js";
11
+ import { observeProjectionLifeSnapshot } from "./projection-life-observer.js";
12
+ import { selectCurrentGeneration } from "./projection-life-protocol.js";
13
+ import { invalidActivityEventDiagnostic, selectValidStoredAgentEvents } from "../stored-agent-event.js";
14
+ import { projectTerminalFailure } from "./projection-terminal-failure.js";
15
+ import { extractTerminalTimestamp } from "./generation/model.js";
16
+ const MAX_STATUS_FILE_BYTES = 256 * 1024;
17
+ class ProjectionStatusFileError extends Error {
18
+ diagnostic;
19
+ constructor(kind, filePath) {
20
+ const label = path.basename(filePath);
21
+ const detail = `${label} is not a regular file`;
22
+ super(detail);
23
+ this.name = "ProjectionStatusFileError";
24
+ this.diagnostic = { kind, detail };
25
+ }
26
+ }
27
+ function oversizedProjectionFileDiagnostic(filePath) {
28
+ return {
29
+ kind: "oversized-projection-file",
30
+ fileName: path.basename(filePath),
31
+ };
32
+ }
33
+ function recordProjectionStatusFileError(error, diagnostics) {
34
+ if (!(error instanceof ProjectionStatusFileError))
35
+ return false;
36
+ diagnostics.push(error.diagnostic);
37
+ return true;
38
+ }
39
+ function readBoundedRegularFileTail(filePath) {
40
+ const before = fs.lstatSync(filePath);
41
+ if (!before.isFile())
42
+ throw new ProjectionStatusFileError("non-regular-projection-file", filePath);
43
+ let fd;
44
+ try {
45
+ fd = fs.openSync(filePath, fs.constants.O_RDONLY | fs.constants.O_NONBLOCK | fs.constants.O_NOFOLLOW);
46
+ }
47
+ catch (error) {
48
+ if (isErrnoException(error) && error.code === "ELOOP") {
49
+ throw new ProjectionStatusFileError("non-regular-projection-file", filePath);
50
+ }
51
+ throw error;
52
+ }
53
+ try {
54
+ const opened = fs.fstatSync(fd);
55
+ if (!opened.isFile())
56
+ throw new ProjectionStatusFileError("non-regular-projection-file", filePath);
57
+ const snapshotSize = opened.size;
58
+ const truncated = snapshotSize > MAX_STATUS_FILE_BYTES;
59
+ const readLength = Math.min(snapshotSize, MAX_STATUS_FILE_BYTES);
60
+ const readOffset = Math.max(0, snapshotSize - MAX_STATUS_FILE_BYTES);
61
+ const buffer = Buffer.allocUnsafe(readLength);
62
+ let total = 0;
63
+ while (total < readLength) {
64
+ const bytesRead = fs.readSync(fd, buffer, total, readLength - total, readOffset + total);
65
+ if (bytesRead === 0)
66
+ break;
67
+ total += bytesRead;
68
+ }
69
+ let tail = buffer.subarray(0, total);
70
+ if (truncated) {
71
+ const firstLf = tail.indexOf(0x0a);
72
+ if (firstLf < 0) {
73
+ return { content: "", truncated: true };
74
+ }
75
+ tail = tail.subarray(firstLf + 1);
76
+ }
77
+ return { content: tail.toString("utf8"), truncated };
78
+ }
79
+ finally {
80
+ fs.closeSync(fd);
81
+ }
82
+ }
83
+ function displayedTellCounts(counts) {
84
+ return {
85
+ inbox: counts.inbox,
86
+ inflight: counts.inflight,
87
+ submitted: counts.submitted,
88
+ seen: counts.seen,
89
+ consumed: counts.consumed,
90
+ };
91
+ }
92
+ function emptyTellCounts() {
93
+ return { inbox: 0, inflight: 0, submitted: 0, seen: 0, consumed: 0 };
94
+ }
95
+ function readIdentityFacts(projectionDir, diagnostics) {
96
+ try {
97
+ const identity = readProjectionIdentity(projectionDir);
98
+ return {
99
+ identity,
100
+ pid: identity.pid,
101
+ akuma: identity.akuma,
102
+ provider: identity.provider,
103
+ binding: identity.binding,
104
+ mintedAt: identity.mintedAt,
105
+ workspaceRoot: identity.workspaceRoot,
106
+ };
107
+ }
108
+ catch (error) {
109
+ if (isErrnoException(error) && error.code === "ENOENT") {
110
+ diagnostics.push({ kind: "missing-identity", detail: "heart identity missing" });
111
+ return { identity: null, pid: undefined, akuma: undefined, provider: undefined, binding: undefined, mintedAt: undefined, workspaceRoot: undefined };
112
+ }
113
+ if (error instanceof ProjectionGenerationStoreError) {
114
+ diagnostics.push({ kind: "invalid-identity", detail: error.message });
115
+ return { identity: null, pid: undefined, akuma: undefined, provider: undefined, binding: undefined, mintedAt: undefined, workspaceRoot: undefined };
116
+ }
117
+ if (error instanceof ProjectionStateError) {
118
+ diagnostics.push({ kind: "invalid-identity", detail: error.message });
119
+ return { identity: null, pid: undefined, akuma: undefined, provider: undefined, binding: undefined, mintedAt: undefined, workspaceRoot: undefined };
120
+ }
121
+ diagnostics.push({ kind: "unreadable-identity", detail: `heart identity unreadable: ${String(error)}` });
122
+ return { identity: null, pid: undefined, akuma: undefined, provider: undefined, binding: undefined, mintedAt: undefined, workspaceRoot: undefined };
123
+ }
124
+ }
125
+ function readHeartTellCounts(projectionDir, diagnostics) {
126
+ try {
127
+ return displayedTellCounts(readProjectionTellCounts(projectionDir));
128
+ }
129
+ catch (error) {
130
+ if (error instanceof ProjectionGenerationStoreError || error instanceof ProjectionStateError) {
131
+ diagnostics.push({ kind: "unreadable-tells", detail: error.message });
132
+ return emptyTellCounts();
133
+ }
134
+ diagnostics.push({ kind: "unreadable-tells", detail: `heart tells unreadable: ${String(error)}` });
135
+ return emptyTellCounts();
136
+ }
137
+ }
138
+ function readPendingTells(projectionDir, diagnostics) {
139
+ try {
140
+ // Kanshi is an operator surface: unconsumed tells are actionable facts, not
141
+ // a count to hide behind. This is one heart read and does not enumerate a
142
+ // second projection topology.
143
+ return readPendingTellWindow(projectionDir, Number.MAX_SAFE_INTEGER).window;
144
+ }
145
+ catch (error) {
146
+ if (error instanceof ProjectionGenerationStoreError || error instanceof ProjectionStateError) {
147
+ diagnostics.push({ kind: "unreadable-tells", detail: error.message });
148
+ return [];
149
+ }
150
+ diagnostics.push({ kind: "unreadable-tells", detail: `heart tells unreadable: ${String(error)}` });
151
+ return [];
152
+ }
153
+ }
154
+ function readStoredEvents(projectionDir, executionId, diagnostics) {
155
+ let content;
156
+ let truncated = false;
157
+ const eventsPath = executionEventsPath(projectionDir, executionId);
158
+ try {
159
+ const read = readBoundedRegularFileTail(eventsPath);
160
+ content = read.content;
161
+ truncated = read.truncated;
162
+ }
163
+ catch (error) {
164
+ if (isErrnoException(error) && error.code === "ENOENT")
165
+ return [];
166
+ if (recordProjectionStatusFileError(error, diagnostics))
167
+ return [];
168
+ diagnostics.push({ kind: "unreadable-activity", detail: `activity ledger unreadable: ${String(error)}` });
169
+ return [];
170
+ }
171
+ if (truncated) {
172
+ diagnostics.push(oversizedProjectionFileDiagnostic(eventsPath));
173
+ }
174
+ // Parse only LF-terminated records. An unterminated final line is ignored even
175
+ // when it happens to parse: it may be a concurrent append fragment.
176
+ const lines = content.split("\n");
177
+ const values = [];
178
+ let malformed = 0;
179
+ for (const line of lines.slice(0, -1)) {
180
+ try {
181
+ values.push(JSON.parse(line));
182
+ }
183
+ catch {
184
+ malformed += 1;
185
+ }
186
+ }
187
+ if (malformed > 0) {
188
+ diagnostics.push({
189
+ kind: "invalid-activity-event",
190
+ detail: `${malformed} persisted activity event${malformed === 1 ? "" : "s"} malformed`,
191
+ });
192
+ }
193
+ return values;
194
+ }
195
+ function latestValidEvent(events) {
196
+ let latest;
197
+ for (const event of events) {
198
+ const atMs = Date.parse(event.at);
199
+ if (!Number.isFinite(atMs))
200
+ continue;
201
+ if (latest === undefined || atMs > latest.atMs)
202
+ latest = { at: event.at, atMs };
203
+ }
204
+ return latest;
205
+ }
206
+ function readActivityFacts(projectionDir, executionId, nowMs, diagnostics, fallbackAt) {
207
+ const fallbackAtMs = fallbackAt === undefined ? undefined : Date.parse(fallbackAt);
208
+ const validFallback = fallbackAt !== undefined && Number.isFinite(fallbackAtMs)
209
+ ? { at: fallbackAt, atMs: fallbackAtMs }
210
+ : undefined;
211
+ if (!executionId) {
212
+ return validFallback
213
+ ? { activityAt: validFallback.at, activityAtMs: validFallback.atMs, activityAgeMs: Math.max(0, nowMs - validFallback.atMs) }
214
+ : {};
215
+ }
216
+ const stored = readStoredEvents(projectionDir, executionId, diagnostics);
217
+ const { events, malformed } = selectValidStoredAgentEvents(stored);
218
+ const invalidActivity = invalidActivityEventDiagnostic(malformed);
219
+ if (invalidActivity)
220
+ diagnostics.push(invalidActivity);
221
+ const latest = latestValidEvent(events) ?? validFallback;
222
+ return {
223
+ ...(events.length > 0 ? { activity: foldPublicProjectionActivity(events) } : {}),
224
+ ...(latest !== undefined
225
+ ? {
226
+ activityAt: latest.at,
227
+ activityAtMs: latest.atMs,
228
+ activityAgeMs: Math.max(0, nowMs - latest.atMs),
229
+ }
230
+ : {}),
231
+ };
232
+ }
233
+ function terminalTimestamp(current) {
234
+ return extractTerminalTimestamp(current?.verdict?.facts);
235
+ }
236
+ export function readProjectionSummary(projectionDir, id, alias, nowMs, options) {
237
+ const diagnostics = [];
238
+ const snapshot = observeProjectionLifeSnapshot(projectionDir, {
239
+ nowMs,
240
+ startupTimeoutMs: PROJECTION_ADOPTION_TIMEOUT_MS,
241
+ });
242
+ if (snapshot.compatibility === "incompatible") {
243
+ return {
244
+ id,
245
+ ...(alias ? { alias } : {}),
246
+ projectionDir,
247
+ snapshot,
248
+ identityFacts: null,
249
+ diagnostics,
250
+ state: "incompatible",
251
+ };
252
+ }
253
+ const life = snapshot.life;
254
+ const phase = life.phase;
255
+ const state = phase === "active" || phase === "quiescent" ? "out" : phase;
256
+ const identityFacts = options?.requiresIdentity?.(state) === false
257
+ ? null
258
+ : readIdentityFacts(projectionDir, diagnostics);
259
+ return {
260
+ id,
261
+ ...(alias ? { alias } : {}),
262
+ projectionDir,
263
+ snapshot,
264
+ identityFacts,
265
+ diagnostics,
266
+ state,
267
+ };
268
+ }
269
+ export function readProjectionRow(summary, nowMs, readDetails) {
270
+ const { id, alias, projectionDir, snapshot } = summary;
271
+ const diagnostics = [...summary.diagnostics];
272
+ if (snapshot.compatibility === "incompatible") {
273
+ return {
274
+ id,
275
+ observedAtMs: nowMs,
276
+ ...(alias ? { alias } : {}),
277
+ state: "incompatible",
278
+ observation: snapshot,
279
+ tellCounts: emptyTellCounts(),
280
+ diagnostics,
281
+ };
282
+ }
283
+ const identityFacts = summary.identityFacts;
284
+ if (identityFacts === null && !readDetails) {
285
+ return {
286
+ id,
287
+ observedAtMs: nowMs,
288
+ ...(alias ? { alias } : {}),
289
+ state: summary.state,
290
+ observation: { compatibility: "current", lifecycle: snapshot.life },
291
+ tellCounts: emptyTellCounts(),
292
+ diagnostics,
293
+ };
294
+ }
295
+ if (identityFacts === null || !identityFacts.identity) {
296
+ const diagnostic = diagnostics.map((entry) => entry.detail).find((detail) => detail !== undefined)
297
+ ?? "projection identity could not be observed";
298
+ return {
299
+ id,
300
+ observedAtMs: nowMs,
301
+ ...(alias ? { alias } : {}),
302
+ state: "unknown",
303
+ observation: {
304
+ compatibility: "current",
305
+ lifecycle: { phase: "unknown", diagnostic },
306
+ },
307
+ tellCounts: emptyTellCounts(),
308
+ diagnostics,
309
+ };
310
+ }
311
+ const life = snapshot.life;
312
+ const current = snapshot.records ? selectCurrentGeneration(snapshot.records) : null;
313
+ const adoptedAt = current?.adoption?.facts.adoptedAt;
314
+ const adoptedAtMs = typeof adoptedAt === "string"
315
+ ? Date.parse(adoptedAt)
316
+ : Number.NaN;
317
+ const tellCounts = readDetails
318
+ ? readHeartTellCounts(projectionDir, diagnostics)
319
+ : emptyTellCounts();
320
+ const pendingTells = readDetails
321
+ ? readPendingTells(projectionDir, diagnostics)
322
+ : [];
323
+ const activityFacts = readDetails
324
+ ? readActivityFacts(projectionDir, life.executionId, nowMs, diagnostics, typeof adoptedAt === "string" && Number.isFinite(adoptedAtMs) ? adoptedAt : identityFacts.mintedAt)
325
+ : {};
326
+ const mintedAtMs = Date.parse(identityFacts.mintedAt ?? "");
327
+ const terminalAt = terminalTimestamp(current);
328
+ const durationAnchorMs = current?.verdict
329
+ ? (terminalAt === undefined ? Number.NaN : Date.parse(terminalAt))
330
+ : nowMs;
331
+ const durationMs = Number.isFinite(mintedAtMs) && Number.isFinite(durationAnchorMs)
332
+ ? Math.max(0, durationAnchorMs - mintedAtMs)
333
+ : undefined;
334
+ const displayAgeMs = activityFacts.activityAgeMs
335
+ ?? (Number.isFinite(mintedAtMs) ? Math.max(0, nowMs - mintedAtMs) : undefined);
336
+ if (life.phase === "unknown" && life.diagnostic) {
337
+ diagnostics.push({ kind: "unknown-lifecycle", detail: life.diagnostic });
338
+ }
339
+ const phase = life.phase;
340
+ const state = phase === "active" || phase === "quiescent" ? "out" : phase;
341
+ const terminalFailure = current?.verdict
342
+ ? projectTerminalFailure(current.verdict.facts.state, current.verdict.facts)
343
+ : undefined;
344
+ return {
345
+ id,
346
+ observedAtMs: nowMs,
347
+ ...(alias ? { alias } : {}),
348
+ state,
349
+ observation: { compatibility: "current", lifecycle: life },
350
+ akuma: identityFacts.akuma,
351
+ provider: identityFacts.provider,
352
+ binding: identityFacts.binding,
353
+ pid: identityFacts.pid,
354
+ mintedAt: identityFacts.mintedAt,
355
+ ...(terminalAt ? { terminalAt } : {}),
356
+ ...(durationMs !== undefined ? { durationMs } : {}),
357
+ ...(identityFacts.workspaceRoot ? { workspaceRoot: identityFacts.workspaceRoot } : {}),
358
+ ...activityFacts,
359
+ ...(displayAgeMs !== undefined ? { activityAgeMs: displayAgeMs } : {}),
360
+ ...(terminalFailure ? { terminalFailure } : {}),
361
+ tellCounts,
362
+ ...(pendingTells.length > 0 ? { pendingTells } : {}),
363
+ diagnostics,
364
+ };
365
+ }