@akagilnc/pi-workflow-roles 0.1.3452 → 0.1.3469

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 (35) hide show
  1. package/dist/evidence-child-executor.js +2 -11
  2. package/dist/grok/production-host.js +196 -205
  3. package/dist/pi/in-process-session.js +4 -0
  4. package/dist/public-cli/main.js +998 -920
  5. package/package.json +1 -1
  6. package/scripts/build-package.mjs +1 -3
  7. package/src/analyst-gate-cycles-read.ts +3 -21
  8. package/src/analyst-ledger.ts +11 -42
  9. package/src/archivist-record-entry.ts +5 -6
  10. package/src/evidence-child-executor.ts +5 -15
  11. package/src/grok/production-host.ts +3 -32
  12. package/src/grok/role-envelope.ts +10 -4
  13. package/src/host-contracts.ts +4 -11
  14. package/src/host-transition-prior-native.ts +3 -7
  15. package/src/ledger-session-read.ts +1 -17
  16. package/src/pi/in-process-session.ts +6 -2
  17. package/src/pi/role-turn-host.ts +9 -0
  18. package/src/public-cli/collector-run.ts +4 -4
  19. package/src/public-cli/countersign-run.ts +90 -20
  20. package/src/public-cli/doctor-run.ts +4 -4
  21. package/src/public-cli/gleaner-left-run.ts +9 -6
  22. package/src/public-cli/inspector-run.ts +95 -32
  23. package/src/public-cli/instruction-seat-run.ts +7 -6
  24. package/src/public-cli/invocation.ts +14 -8
  25. package/src/public-cli/judge-run.ts +4 -4
  26. package/src/public-cli/notary-run.ts +91 -30
  27. package/src/public-cli/post-admission.ts +424 -360
  28. package/src/public-cli/run-lifecycle.ts +205 -0
  29. package/src/public-cli/seat-ticket-binding.ts +136 -16
  30. package/src/public-cli/settlement.ts +74 -52
  31. package/src/run-ticket-number.ts +53 -0
  32. package/src/submission-ledger.ts +86 -18
  33. package/dist/ledger-session-read.js +0 -241
  34. package/dist/ticket-seat-memory.js +0 -244
  35. package/src/ticket-seat-memory.ts +0 -454
@@ -1,241 +0,0 @@
1
- /**
2
- * Canonical ledger session JSONL read primitives (shared owner).
3
- * Consumers (ticket-trajectory, analyst, …) must import here — no second parse kernel.
4
- */
5
- import { readFile } from "node:fs/promises";
6
- /**
7
- * Loud JSONL failure that still retains rows parsed before the bad line.
8
- * Callers that only need the throw keep catching Error; owners that must
9
- * surface partial typed facts (e.g. first-frame timestamp) read prefixRows.
10
- */
11
- export class LedgerSessionJsonlError extends Error {
12
- path;
13
- line;
14
- prefixRows;
15
- constructor(message, init) {
16
- super(message);
17
- this.name = "LedgerSessionJsonlError";
18
- this.path = init.path;
19
- this.line = init.line;
20
- this.prefixRows = init.prefixRows;
21
- }
22
- }
23
- function isRecord(value) {
24
- return typeof value === "object" && value !== null && !Array.isArray(value);
25
- }
26
- /**
27
- * Read session JSONL with honest live-tail semantics:
28
- * a malformed line is tolerated only when it is an unfinished final
29
- * fragment at EOF (no record terminator after it). Any malformed line
30
- * completed by a line terminator must fail loudly with file and 1-based
31
- * line context — even when no non-empty record follows — never silently
32
- * under-count.
33
- *
34
- * Loud failures throw LedgerSessionJsonlError carrying prefixRows so the
35
- * single parse kernel can still expose facts obtained before the bad line.
36
- */
37
- export async function readLedgerSessionJsonl(path) {
38
- const text = await readFile(path, "utf8");
39
- // split keeps a trailing empty segment iff text ends with "\n", so
40
- // index < lines.length - 1 means this segment was terminated.
41
- const lines = text.split("\n");
42
- const rows = [];
43
- for (let index = 0; index < lines.length; index += 1) {
44
- const line = lines[index];
45
- if (!line.trim())
46
- continue;
47
- let row;
48
- try {
49
- row = JSON.parse(line);
50
- }
51
- catch (error) {
52
- if (!(error instanceof SyntaxError))
53
- throw error;
54
- const completedByTerminator = index < lines.length - 1;
55
- if (completedByTerminator) {
56
- throw new LedgerSessionJsonlError(`malformed JSONL record in ${path} at line ${index + 1}: ${error.message}`, { path, line: index + 1, prefixRows: rows });
57
- }
58
- // unfinished fragment at EOF — keep prior complete rows
59
- break;
60
- }
61
- // Syntactically complete line: must be a session object. Silent omission
62
- // would under-count ledger evidence (failure honesty).
63
- if (!isRecord(row)) {
64
- const kind = row === null ? "null" : Array.isArray(row) ? "array" : typeof row;
65
- throw new LedgerSessionJsonlError(`complete non-object JSONL record in ${path} at line ${index + 1}: expected object, got ${kind}`, { path, line: index + 1, prefixRows: rows });
66
- }
67
- rows.push(row);
68
- }
69
- return rows;
70
- }
71
- /** First and last record timestamps in encounter order. */
72
- export function extractSessionTimestampSpan(rows) {
73
- let startedAt;
74
- let endedAt;
75
- for (const row of rows) {
76
- if (typeof row.timestamp !== "string" || !row.timestamp)
77
- continue;
78
- if (startedAt === undefined)
79
- startedAt = row.timestamp;
80
- endedAt = row.timestamp;
81
- }
82
- return {
83
- ...(startedAt !== undefined ? { startedAt } : {}),
84
- ...(endedAt !== undefined ? { endedAt } : {}),
85
- };
86
- }
87
- /**
88
- * Ordered-unique model ids from session frames (first-seen order).
89
- * Sources (same faces ticket-trajectory already reads — single parse kernel):
90
- * - `model_change.modelId`
91
- * - assistant `message.model`
92
- * Blank / non-string values are skipped. Does not invent a default model.
93
- */
94
- export function extractSessionModelSequence(rows) {
95
- const seen = new Set();
96
- const ordered = [];
97
- const push = (raw) => {
98
- const model = raw.trim();
99
- if (model === "" || seen.has(model))
100
- return;
101
- seen.add(model);
102
- ordered.push(model);
103
- };
104
- for (const row of rows) {
105
- if (row.type === "model_change" && typeof row.modelId === "string") {
106
- push(row.modelId);
107
- }
108
- const message = isRecord(row.message) ? row.message : undefined;
109
- if (message?.role === "assistant" && typeof message.model === "string") {
110
- push(message.model);
111
- }
112
- }
113
- return ordered;
114
- }
115
- /** First line of a bash `command` argument (sole owner of this summary). */
116
- export function bashCommandFirstLine(command) {
117
- const match = /^[^\r\n]*/.exec(command);
118
- return match?.[0] ?? "";
119
- }
120
- /**
121
- * Pair toolCall frames → toolResult frames by toolCallId.
122
- * Throws when a tool-bearing frame is structurally unreadable for association
123
- * (toolCall missing string id, toolResult missing string toolCallId).
124
- * Unpaired open calls remain without endedAt — that is incomplete, not unreadable.
125
- */
126
- export function extractSessionToolIntervals(rows) {
127
- const order = [];
128
- const openById = new Map();
129
- for (const row of rows) {
130
- const rowTimestamp = typeof row.timestamp === "string" ? row.timestamp : undefined;
131
- const message = isRecord(row.message) ? row.message : undefined;
132
- if (message?.role === "assistant" && Array.isArray(message.content)) {
133
- const callTimestamp = typeof message.timestamp === "string" && message.timestamp
134
- ? message.timestamp
135
- : rowTimestamp;
136
- for (const part of message.content) {
137
- if (!isRecord(part) || part.type !== "toolCall")
138
- continue;
139
- if (typeof part.id !== "string" || part.id.length === 0) {
140
- throw new Error("toolCall frame missing string id");
141
- }
142
- if (typeof part.name !== "string" || part.name.length === 0) {
143
- throw new Error(`toolCall ${part.id} missing string name`);
144
- }
145
- if (callTimestamp === undefined || callTimestamp.length === 0) {
146
- throw new Error(`toolCall ${part.id} missing timestamp`);
147
- }
148
- if (openById.has(part.id)) {
149
- throw new Error(`duplicate toolCall id ${part.id}`);
150
- }
151
- const args = isRecord(part.arguments) ? part.arguments : undefined;
152
- // Ticket surface: only bash first-line summary is authorized here.
153
- const command = part.name === "bash" &&
154
- args !== undefined &&
155
- typeof args.command === "string"
156
- ? bashCommandFirstLine(args.command)
157
- : undefined;
158
- const interval = {
159
- toolCallId: part.id,
160
- toolName: part.name,
161
- startedAt: callTimestamp,
162
- ...(command !== undefined ? { command } : {}),
163
- };
164
- order.push(interval);
165
- openById.set(part.id, interval);
166
- }
167
- }
168
- if (message?.role === "toolResult") {
169
- if (typeof message.toolCallId !== "string" || message.toolCallId.length === 0) {
170
- throw new Error("toolResult frame missing string toolCallId");
171
- }
172
- const resultTimestamp = typeof message.timestamp === "string" && message.timestamp
173
- ? message.timestamp
174
- : rowTimestamp;
175
- if (resultTimestamp === undefined || resultTimestamp.length === 0) {
176
- throw new Error(`toolResult ${message.toolCallId} missing timestamp`);
177
- }
178
- const open = openById.get(message.toolCallId);
179
- if (open === undefined) {
180
- // Result without a prior call is still associable as a closed interval
181
- // once a name is known; keep structural readability without inventing a call.
182
- const toolName = typeof message.toolName === "string" && message.toolName.length > 0
183
- ? message.toolName
184
- : "unknown";
185
- order.push({
186
- toolCallId: message.toolCallId,
187
- toolName,
188
- startedAt: resultTimestamp,
189
- endedAt: resultTimestamp,
190
- });
191
- continue;
192
- }
193
- if (open.endedAt !== undefined) {
194
- throw new Error(`duplicate toolResult for toolCallId ${message.toolCallId}`);
195
- }
196
- open.endedAt = resultTimestamp;
197
- }
198
- }
199
- return order.map((interval) => {
200
- const base = {
201
- toolCallId: interval.toolCallId,
202
- toolName: interval.toolName,
203
- startedAt: interval.startedAt,
204
- ...(interval.command !== undefined ? { command: interval.command } : {}),
205
- };
206
- return interval.endedAt === undefined
207
- ? base
208
- : { ...base, endedAt: interval.endedAt };
209
- });
210
- }
211
- export function intervalRowsAroundAnchor(rows, anchorIndex, isBindingRow) {
212
- let start = 0;
213
- for (let i = anchorIndex; i >= 0; i -= 1) {
214
- if (isBindingRow(rows[i])) {
215
- start = i;
216
- break;
217
- }
218
- }
219
- let end = rows.length;
220
- for (let i = Math.max(anchorIndex, start) + 1; i < rows.length; i += 1) {
221
- if (isBindingRow(rows[i])) {
222
- end = i;
223
- break;
224
- }
225
- }
226
- return { rows: rows.slice(start, end), closed: end < rows.length };
227
- }
228
- /**
229
- * Interval owned by the first binding row that satisfies `matches`.
230
- * `undefined` when the volume carries no matching binding — callers must not
231
- * silently treat the whole continuous volume as that owner.
232
- */
233
- export function intervalRowsForMatchingBinding(rows, isBindingRow, matches) {
234
- for (let i = 0; i < rows.length; i += 1) {
235
- const row = rows[i];
236
- if (!isBindingRow(row) || !matches(row))
237
- continue;
238
- return intervalRowsAroundAnchor(rows, i, isBindingRow);
239
- }
240
- return undefined;
241
- }
@@ -1,244 +0,0 @@
1
- import { existsSync } from "node:fs";
2
- import { readFile, writeFile } from "node:fs/promises";
3
- import { join } from "node:path";
4
- import { pathToFileURL } from "node:url";
5
- import { homeFromRunDirectory } from "./activation-ledger-topology.js";
6
- import { subjectKeyedRecordDirectory } from "./archivist-record-topology.js";
7
- import { resolvePackageRootDir } from "./session-opening-materials.js";
8
- const TICKET_SEAT_RUN_BINDING_ENTRY_TYPE = "ak_ticket_seat_run_binding";
9
- function isTicketSeatRunBindingRow(row) {
10
- return row.type === "custom" && row.customType === TICKET_SEAT_RUN_BINDING_ENTRY_TYPE;
11
- }
12
- function ticketSeatRunBindingRunId(row) {
13
- if (!isTicketSeatRunBindingRow(row)) return void 0;
14
- if (row.data === null || typeof row.data !== "object" || Array.isArray(row.data)) {
15
- return void 0;
16
- }
17
- const runId = row.data.runId;
18
- return typeof runId === "string" && runId.length > 0 ? runId : void 0;
19
- }
20
- async function loadCreateRecordSessionOpen() {
21
- const packageRoot = resolvePackageRootDir(import.meta.url);
22
- const built = join(packageRoot, "dist/archivist-record-entry.js");
23
- const source = join(packageRoot, "src/archivist-record-entry.ts");
24
- const target = existsSync(built) ? built : source;
25
- const mod = await import(pathToFileURL(target).href);
26
- return mod.createRecordSessionOpen;
27
- }
28
- const TICKET_SEAT_MEMORY_KIND = "auditor-roles";
29
- const TICKET_SEAT_MEMORY_SEATS = ["inspector", "notary", "auditor"];
30
- function isTicketSeatMemorySeat(value) {
31
- return TICKET_SEAT_MEMORY_SEATS.includes(value);
32
- }
33
- function isTicketSeatMemoryBound(input) {
34
- return isTicketSeatMemorySeat(input.role) && typeof input.ticketNumber === "number" && Number.isSafeInteger(input.ticketNumber) && input.ticketNumber >= 1;
35
- }
36
- function isEnoent(error) {
37
- return error instanceof Error && "code" in error && error.code === "ENOENT";
38
- }
39
- function ticketSeatMemorySubject(ticketNumber, seat) {
40
- if (!Number.isSafeInteger(ticketNumber) || ticketNumber < 1) {
41
- throw new Error(
42
- `ticket-seat memory subject requires a positive ticket number, got ${String(ticketNumber)}`
43
- );
44
- }
45
- return `${ticketNumber}:${seat}`;
46
- }
47
- function ticketSeatMemorySessionDirectory(input) {
48
- return subjectKeyedRecordDirectory({
49
- cwd: input.cwd,
50
- kind: TICKET_SEAT_MEMORY_KIND,
51
- subject: ticketSeatMemorySubject(input.ticketNumber, input.seat),
52
- ...input.parentSessionFile === void 0 ? {} : { parentSessionFile: input.parentSessionFile },
53
- ...input.home === void 0 ? {} : { home: input.home }
54
- });
55
- }
56
- async function readRunAdmittedPageFacts(runDirectory) {
57
- try {
58
- const raw = JSON.parse(
59
- await readFile(join(runDirectory, "admitted-request.json"), "utf8")
60
- );
61
- if (raw === null || typeof raw !== "object" || Array.isArray(raw)) {
62
- return {};
63
- }
64
- const record = raw;
65
- const ticketNumber = record.ticketNumber;
66
- const projectRoot = record.projectRoot;
67
- return {
68
- ...typeof ticketNumber === "number" && Number.isSafeInteger(ticketNumber) && ticketNumber >= 1 ? { ticketNumber } : {},
69
- ...typeof projectRoot === "string" ? { projectRoot } : {}
70
- };
71
- } catch (error) {
72
- if (isEnoent(error)) return {};
73
- throw error;
74
- }
75
- }
76
- async function readInvocationTicketNumber(runDirectory) {
77
- try {
78
- const raw = JSON.parse(
79
- await readFile(join(runDirectory, "invocation.json"), "utf8")
80
- );
81
- if (raw === null || typeof raw !== "object" || Array.isArray(raw)) {
82
- return void 0;
83
- }
84
- const ticketNumber = raw.ticketNumber;
85
- if (typeof ticketNumber === "number" && Number.isSafeInteger(ticketNumber) && ticketNumber >= 1) {
86
- return ticketNumber;
87
- }
88
- } catch (error) {
89
- if (isEnoent(error)) return void 0;
90
- throw error;
91
- }
92
- return void 0;
93
- }
94
- async function readRunTicketNumber(runDirectory) {
95
- const admitted = await readRunAdmittedPageFacts(runDirectory);
96
- if (admitted.ticketNumber !== void 0) return admitted.ticketNumber;
97
- return readInvocationTicketNumber(runDirectory);
98
- }
99
- async function resolveTicketSeatMemoryNestDirectories(input) {
100
- if (input.seats.length === 0) return [];
101
- const admitted = await readRunAdmittedPageFacts(input.runDirectory);
102
- const ticketNumber = admitted.ticketNumber ?? await readInvocationTicketNumber(input.runDirectory);
103
- if (ticketNumber === void 0) return [];
104
- let home;
105
- try {
106
- home = homeFromRunDirectory(input.runDirectory);
107
- } catch {
108
- return [];
109
- }
110
- const projectRoot = admitted.projectRoot;
111
- if (projectRoot === void 0) return [];
112
- return input.seats.map(
113
- (seat) => ticketSeatMemorySessionDirectory({
114
- ticketNumber,
115
- seat,
116
- cwd: projectRoot,
117
- home
118
- })
119
- );
120
- }
121
- async function openTicketSeatMemoryCoordinates(input) {
122
- const createRecordSessionOpen = await loadCreateRecordSessionOpen();
123
- const opened = createRecordSessionOpen({
124
- cwd: input.cwd,
125
- kind: TICKET_SEAT_MEMORY_KIND,
126
- subject: ticketSeatMemorySubject(input.ticketNumber, input.seat),
127
- parent: {
128
- getSessionFile: () => input.ledgerAnchorSessionFile
129
- }
130
- });
131
- if (input.runId !== void 0) {
132
- const data = { version: 1, runId: input.runId };
133
- opened.session.appendCustomEntry(TICKET_SEAT_RUN_BINDING_ENTRY_TYPE, data);
134
- }
135
- const sessionFile = opened.session.getSessionFile();
136
- const sessionDirectory = opened.session.getSessionDir();
137
- if (typeof sessionFile !== "string" || sessionFile.length === 0) {
138
- throw new Error(
139
- `ticket-seat memory principal missing session file for ticket #${input.ticketNumber} seat ${input.seat}`
140
- );
141
- }
142
- if (typeof sessionDirectory !== "string" || sessionDirectory.length === 0) {
143
- throw new Error(
144
- `ticket-seat memory principal missing session directory for ticket #${input.ticketNumber} seat ${input.seat}`
145
- );
146
- }
147
- return { sessionDirectory, sessionFile, resumed: opened.resumed };
148
- }
149
- async function rebindAdmittedToTicketSeatMemory(input) {
150
- const ticketNumber = input.admitted.ticketNumber;
151
- if (ticketNumber === void 0) return void 0;
152
- const anchor = input.principalAuthority.decode(input.admitted.principal);
153
- const opened = await openTicketSeatMemoryCoordinates({
154
- ticketNumber,
155
- seat: input.seat,
156
- cwd: input.admitted.projectRoot,
157
- ledgerAnchorSessionFile: anchor.sessionFile,
158
- // Analyst attributes frame/model/tool to this binding interval only (#636 D).
159
- runId: input.admitted.runId
160
- });
161
- const coordinates = {
162
- sessionDirectory: opened.sessionDirectory,
163
- sessionFile: opened.sessionFile
164
- };
165
- const principal = input.principalAuthority.seal(coordinates);
166
- input.principalAuthority.decode(principal);
167
- input.admitted.principal = principal;
168
- for (const page of ["admitted-request.json", "invocation.json"]) {
169
- const path = join(input.admitted.runDirectory, page);
170
- try {
171
- const raw = JSON.parse(await readFile(path, "utf8"));
172
- if (raw === null || typeof raw !== "object" || Array.isArray(raw)) continue;
173
- const next = {
174
- ...raw,
175
- sessionDirectory: coordinates.sessionDirectory,
176
- sessionFile: coordinates.sessionFile
177
- };
178
- await writeFile(path, `${JSON.stringify(next, null, 2)}
179
- `, "utf8");
180
- } catch (error) {
181
- if (error instanceof Error && "code" in error && error.code === "ENOENT") {
182
- continue;
183
- }
184
- throw error;
185
- }
186
- }
187
- return { coordinates, resumed: opened.resumed };
188
- }
189
- const TICKET_SEAT_LAST_HOST_FILE = "last-host.json";
190
- async function readTicketSeatMemoryLastHost(sessionDirectory) {
191
- try {
192
- const raw = JSON.parse(
193
- await readFile(join(sessionDirectory, TICKET_SEAT_LAST_HOST_FILE), "utf8")
194
- );
195
- if (raw === null || typeof raw !== "object" || Array.isArray(raw) || typeof raw.host !== "string" || raw.host.length === 0) {
196
- throw new Error(
197
- `ticket-seat memory last-host page is damaged under ${sessionDirectory}`
198
- );
199
- }
200
- const host = raw.host;
201
- const runDirectory = raw.runDirectory;
202
- return {
203
- host,
204
- ...typeof runDirectory === "string" && runDirectory.length > 0 ? { runDirectory } : {}
205
- };
206
- } catch (error) {
207
- if (error instanceof Error && "code" in error && error.code === "ENOENT") {
208
- return void 0;
209
- }
210
- throw error;
211
- }
212
- }
213
- async function writeTicketSeatMemoryLastHost(sessionDirectory, host, runDirectory) {
214
- if (host.trim() === "") {
215
- throw new Error("ticket-seat memory last-host requires a non-empty host");
216
- }
217
- const body = {
218
- host,
219
- ...runDirectory !== void 0 && runDirectory.length > 0 ? { runDirectory } : {}
220
- };
221
- await writeFile(
222
- join(sessionDirectory, TICKET_SEAT_LAST_HOST_FILE),
223
- `${JSON.stringify(body, null, 2)}
224
- `,
225
- "utf8"
226
- );
227
- }
228
- export {
229
- TICKET_SEAT_MEMORY_KIND,
230
- TICKET_SEAT_MEMORY_SEATS,
231
- TICKET_SEAT_RUN_BINDING_ENTRY_TYPE,
232
- isTicketSeatMemoryBound,
233
- isTicketSeatMemorySeat,
234
- isTicketSeatRunBindingRow,
235
- openTicketSeatMemoryCoordinates,
236
- readRunTicketNumber,
237
- readTicketSeatMemoryLastHost,
238
- rebindAdmittedToTicketSeatMemory,
239
- resolveTicketSeatMemoryNestDirectories,
240
- ticketSeatMemorySessionDirectory,
241
- ticketSeatMemorySubject,
242
- ticketSeatRunBindingRunId,
243
- writeTicketSeatMemoryLastHost
244
- };