@akagilnc/pi-workflow-roles 0.1.4035 → 0.1.4193
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.
- package/dist/acp-host/production-host.js +2368 -1155
- package/dist/archivist-record-entry.js +204 -20
- package/dist/archivist-record-topology.js +36 -16
- package/dist/auditor-dossier-tool.js +2 -1
- package/dist/diarist-contracts.js +96 -0
- package/dist/doctor-auditor.js +1 -1
- package/dist/doctor-evidence.js +3 -2
- package/dist/dossier-resolution.js +21 -8
- package/dist/gatekeeper-role.js +34 -27
- package/dist/headless-host/description.js +7 -8
- package/dist/headless-host/production-host.js +2375 -1149
- package/dist/host-contracts.js +16 -0
- package/dist/inspector-contracts.js +8 -0
- package/dist/migrate-book-topology.js +278917 -0
- package/dist/navigator-public-session.js +22 -25
- package/dist/notary-source-run.js +7 -4
- package/dist/packaged-role-registry.js +1 -1
- package/dist/pi/durable-principal.js +9 -6
- package/dist/pi/role-turn-host.js +43 -13
- package/dist/public-cli/auto-resume.js +2 -5
- package/dist/public-cli/case-dossier-delivery.js +60 -1
- package/dist/public-cli/diarist-run.js +27 -25
- package/dist/public-cli/inspector-run.js +17 -3
- package/dist/public-cli/instruction-seat-run.js +17 -17
- package/dist/public-cli/invocation.js +127 -63
- package/dist/public-cli/main.js +1685 -696
- package/dist/public-cli/notary-run.js +7 -4
- package/dist/public-cli/option-definitions.js +2 -2
- package/dist/public-cli/post-admission.js +113 -71
- package/dist/public-cli/run-lifecycle.js +209 -58
- package/dist/public-cli/seat-ticket-binding.js +3 -6
- package/dist/public-cli/settlement.js +29 -10
- package/dist/public-role-summons.js +8 -0
- package/dist/role-run-placement.js +84 -0
- package/dist/role-run-relocation.js +358 -0
- package/dist/run-terminal-artifacts.js +2 -1
- package/dist/run-ticket-number.js +51 -32
- package/dist/sitian-appender.js +11 -26
- package/dist/submission-ledger.js +106 -25
- package/dist/ticket-provenance.js +3 -1
- package/dist/user-dialogue-stdin.js +33 -0
- package/extensions/role-runtime.ts +1 -0
- package/package.json +3 -2
- package/resources/836-deleted-machine-instruction-inventory.md +1 -1
- package/scripts/build-package.mjs +34 -2
- package/souls/fixer.md +3 -2
- package/src/analyst-ledger.ts +14 -15
- package/src/archivist-record-entry.ts +285 -38
- package/src/archivist-record-topology.ts +43 -18
- package/src/auditor-dossier-tool.ts +2 -1
- package/src/book-topology-migration-placement.ts +493 -0
- package/src/book-topology-migration.ts +220 -0
- package/src/book-topology-migrators-t11.ts +856 -0
- package/src/book-topology-mixed-volume-migrators.ts +470 -0
- package/src/book-topology-partition-migrators.ts +25 -0
- package/src/book-topology-record-class-migrators.ts +779 -0
- package/src/book-topology-runs-migrator.ts +244 -0
- package/src/diarist-contracts.ts +116 -0
- package/src/diarist-role.ts +6 -0
- package/src/diarist.ts +2 -0
- package/src/doctor-auditor.ts +1 -1
- package/src/doctor-evidence.ts +4 -1
- package/src/doctor-role.ts +1 -1
- package/src/dossier-resolution.ts +26 -8
- package/src/external-host-turn-loop.ts +9 -0
- package/src/gatekeeper-pass-envelope.ts +6 -0
- package/src/gatekeeper-role.ts +47 -28
- package/src/headless-host/description.ts +8 -11
- package/src/headless-host/role-turn-host.ts +20 -5
- package/src/host-contracts.ts +22 -3
- package/src/inspector-contracts.ts +7 -0
- package/src/judge-role.ts +4 -0
- package/src/navigator-public-session.ts +33 -30
- package/src/navigator-work-context.ts +3 -4
- package/src/notary-role.ts +1 -0
- package/src/notary-source-run.ts +9 -4
- package/src/packaged-role-registry.ts +1 -1
- package/src/pi/adapter.ts +19 -4
- package/src/pi/durable-principal.ts +9 -13
- package/src/pi/role-turn-host.ts +48 -14
- package/src/public-cli/auto-resume.ts +2 -6
- package/src/public-cli/case-dossier-delivery.ts +86 -1
- package/src/public-cli/countersign-run.ts +182 -32
- package/src/public-cli/diarist-run.ts +34 -26
- package/src/public-cli/inspector-run.ts +26 -2
- package/src/public-cli/instruction-seat-run.ts +24 -20
- package/src/public-cli/invocation.ts +194 -58
- package/src/public-cli/notary-run.ts +13 -4
- package/src/public-cli/option-definitions.ts +2 -2
- package/src/public-cli/post-admission.ts +147 -78
- package/src/public-cli/run-lifecycle.ts +258 -65
- package/src/public-cli/seat-ticket-binding.ts +3 -6
- package/src/public-cli/settlement.ts +38 -10
- package/src/public-role-summons.ts +31 -7
- package/src/role-envelope.ts +9 -30
- package/src/role-run-placement.ts +128 -0
- package/src/role-run-relocation.ts +491 -0
- package/src/role-runtime-dependencies.ts +1 -0
- package/src/role-runtime.ts +106 -24
- package/src/run-terminal-artifacts.ts +3 -1
- package/src/run-ticket-number.ts +80 -20
- package/src/sitian-appender.ts +25 -28
- package/src/submission-ledger.ts +148 -30
- package/src/ticket-provenance.ts +4 -1
- package/src/ticket-trajectory.ts +22 -35
- package/src/user-dialogue-stdin.ts +37 -0
- package/src/worker-role.ts +8 -4
- package/src/worker-submission-gates.ts +6 -6
package/src/submission-ledger.ts
CHANGED
|
@@ -1,13 +1,23 @@
|
|
|
1
|
+
import { join } from "node:path";
|
|
2
|
+
|
|
1
3
|
import {
|
|
2
4
|
resolveActivationLedgerHome,
|
|
3
5
|
tryHomeFromAkRolesPath,
|
|
4
6
|
} from "./activation-ledger-topology.ts";
|
|
5
|
-
import
|
|
7
|
+
import {
|
|
8
|
+
courtAttemptIdFromHostContext,
|
|
9
|
+
runDirectoryFromHostContext,
|
|
10
|
+
type HostContext,
|
|
11
|
+
type HostToolResult,
|
|
12
|
+
type RoleHost,
|
|
13
|
+
} from "./host-contracts.ts";
|
|
6
14
|
import { isAuditEscalationProjection } from "./audit-escalation.ts";
|
|
7
15
|
|
|
8
16
|
|
|
9
17
|
import { runIdFromRunDirectory } from "./run-terminal-artifacts.ts";
|
|
10
18
|
import { readSitianRecords, resolveSitianRecordPathInLedger, sitianReport, type RecordPointer } from "./sitian-facade.ts";
|
|
19
|
+
import type { SitianRecord } from "./sitian-contracts.ts";
|
|
20
|
+
import { findRunDirectoryById } from "./public-cli/run-lifecycle.ts";
|
|
11
21
|
import type { TerminalRoleName } from "./public-cli/terminal.ts";
|
|
12
22
|
import { isCorrectableExecuteError } from "./submission-correctable-error.ts";
|
|
13
23
|
import { failOnInfrastructureFailureDeclaration } from "./package-contracts/terminating-infrastructure.ts";
|
|
@@ -57,8 +67,8 @@ export type SubmissionLedgerEvent =
|
|
|
57
67
|
* otherwise session header id. Never a shared "unbound" bucket.
|
|
58
68
|
*/
|
|
59
69
|
function runIdentity(context: HostContext): string {
|
|
60
|
-
const directory =
|
|
61
|
-
if (
|
|
70
|
+
const directory = runDirectoryFromHostContext(context);
|
|
71
|
+
if (directory !== undefined) {
|
|
62
72
|
const fromDir = runIdFromRunDirectory(directory);
|
|
63
73
|
if (fromDir !== undefined) return fromDir;
|
|
64
74
|
}
|
|
@@ -74,8 +84,8 @@ function runIdentity(context: HostContext): string {
|
|
|
74
84
|
export const COURT_ATTEMPT_ENV = "AK_ROLE_COURT_ATTEMPT" as const;
|
|
75
85
|
|
|
76
86
|
function attemptIdentity(context: HostContext, runId: string): string {
|
|
77
|
-
const courtAttempt =
|
|
78
|
-
if (
|
|
87
|
+
const courtAttempt = courtAttemptIdFromHostContext(context);
|
|
88
|
+
if (courtAttempt !== undefined) return courtAttempt;
|
|
79
89
|
return context.sessionManager.getHeader?.()?.id ?? context.sessionManager.getLeafId?.() ?? `${runId}:initial`;
|
|
80
90
|
}
|
|
81
91
|
|
|
@@ -105,28 +115,64 @@ export type ClosedSubmission = {
|
|
|
105
115
|
|
|
106
116
|
export type ClosedSubmissionProjection = ClosedSubmission;
|
|
107
117
|
|
|
108
|
-
|
|
109
|
-
|
|
118
|
+
/**
|
|
119
|
+
* Resolve the submission-ledger record file for a run.
|
|
120
|
+
* Unknown run (no sessionParent and no discoverable directory) → undefined so
|
|
121
|
+
* read APIs return their empty set at the read boundary. Write paths always
|
|
122
|
+
* supply sessionParent and keep the ownership gate.
|
|
123
|
+
*/
|
|
124
|
+
async function submissionRecordFile(cwd: string, runId: string, scope: SubmissionLedgerReadScope): Promise<string | undefined> {
|
|
125
|
+
const ledgerHome = resolveActivationLedgerHome(scope.home);
|
|
126
|
+
let sessionParent = scope.sessionParent;
|
|
127
|
+
if (sessionParent === undefined) {
|
|
128
|
+
const discoveredRun = await findRunDirectoryById(scope.home, runId);
|
|
129
|
+
if (discoveredRun === undefined) return undefined;
|
|
130
|
+
sessionParent = join(discoveredRun, "session", "session.jsonl");
|
|
131
|
+
}
|
|
110
132
|
return resolveSitianRecordPathInLedger({
|
|
111
133
|
level: "event",
|
|
112
134
|
kind: "candidate",
|
|
113
135
|
subject: { runId },
|
|
114
136
|
cwd,
|
|
137
|
+
sessionParent,
|
|
115
138
|
}, ledgerHome).recordFile;
|
|
116
139
|
}
|
|
117
140
|
|
|
118
|
-
|
|
119
|
-
|
|
141
|
+
/**
|
|
142
|
+
* Discriminated owned-row view:
|
|
143
|
+
* - unknown run → file absent + empty owned (read APIs only)
|
|
144
|
+
* - located ledger → file is string; prior/write consumers may build RecordPointer
|
|
145
|
+
*/
|
|
146
|
+
type OwnedSubmissionRecords =
|
|
147
|
+
| { readonly file: undefined; readonly owned: readonly [] }
|
|
148
|
+
| { readonly file: string; readonly owned: readonly SitianRecord[] };
|
|
149
|
+
|
|
150
|
+
async function readOwnedSubmissionRecords(
|
|
151
|
+
cwd: string,
|
|
152
|
+
runId: string,
|
|
153
|
+
scope: SubmissionLedgerReadScope = {},
|
|
154
|
+
): Promise<OwnedSubmissionRecords> {
|
|
155
|
+
const file = await submissionRecordFile(cwd, runId, scope);
|
|
156
|
+
if (file === undefined) {
|
|
157
|
+
return { file: undefined, owned: [] };
|
|
158
|
+
}
|
|
120
159
|
const { records } = await readSitianRecords(file);
|
|
121
160
|
return {
|
|
122
161
|
file,
|
|
123
|
-
owned: records.filter(
|
|
162
|
+
owned: records.filter(
|
|
163
|
+
(record) =>
|
|
164
|
+
typeof record.subject === "object"
|
|
165
|
+
&& record.subject !== null
|
|
166
|
+
&& (record.subject as { runId?: string }).runId === runId,
|
|
167
|
+
),
|
|
124
168
|
};
|
|
125
169
|
}
|
|
126
170
|
|
|
127
171
|
/** Optional court-turn scope for settlement reads (#637). */
|
|
128
172
|
export type SubmissionLedgerReadScope = {
|
|
129
173
|
readonly home?: string;
|
|
174
|
+
/** Durable run principal; the direct ownership coordinate when already known. */
|
|
175
|
+
readonly sessionParent?: string;
|
|
130
176
|
/**
|
|
131
177
|
* Recording tag for this court. Presentation of original payloads is run-scoped
|
|
132
178
|
* (#836: attempt/latest must not hide already-recorded rows).
|
|
@@ -178,9 +224,9 @@ export async function hasFreshAttemptSubmission(
|
|
|
178
224
|
cwd: string,
|
|
179
225
|
runId: string,
|
|
180
226
|
attemptId: string,
|
|
181
|
-
|
|
227
|
+
homeOrScope?: string | SubmissionLedgerReadScope,
|
|
182
228
|
): Promise<boolean> {
|
|
183
|
-
const { owned } = await readOwnedSubmissionRecords(cwd, runId,
|
|
229
|
+
const { owned } = await readOwnedSubmissionRecords(cwd, runId, resolveReadScope(homeOrScope));
|
|
184
230
|
return owned.some((record) => {
|
|
185
231
|
if (recordAttemptId(record) !== attemptId) return false;
|
|
186
232
|
if (record.kind === "sealed") return true;
|
|
@@ -258,14 +304,10 @@ function rowFromPayload(
|
|
|
258
304
|
* recorded attemptId + toolCallId so distinct court attempts stay distinct (#881).
|
|
259
305
|
* `accepted` is the original payload; never rebuilt from a status/facts envelope.
|
|
260
306
|
*/
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
): Promise<readonly RecordedSubmissionRow[]> {
|
|
266
|
-
const scope = resolveReadScope(homeOrScope);
|
|
267
|
-
const { owned } = await readOwnedSubmissionRecords(cwd, runId, scope.home);
|
|
268
|
-
const scoped = recordsForAttempt(owned, scope.attemptId);
|
|
307
|
+
function mapOwnedToSubmissionRows(
|
|
308
|
+
owned: readonly { kind?: unknown; subject?: unknown; payload?: unknown }[],
|
|
309
|
+
): readonly RecordedSubmissionRow[] {
|
|
310
|
+
const scoped = owned;
|
|
269
311
|
const out: RecordedSubmissionRow[] = [];
|
|
270
312
|
const indexByCall = new Map<string, number>();
|
|
271
313
|
// Recover seat identity for historical non-sealed rows that omitted role (#881).
|
|
@@ -360,6 +402,43 @@ export async function readRecordedSubmissionRows(
|
|
|
360
402
|
return out;
|
|
361
403
|
}
|
|
362
404
|
|
|
405
|
+
/**
|
|
406
|
+
* All recorded role submissions in ledger order (#836 multi-submit).
|
|
407
|
+
* `accepted` is the original payload; never rebuilt from a status/facts envelope.
|
|
408
|
+
* Presentation stays run-scoped: attemptId on the read scope is a recording tag,
|
|
409
|
+
* not a visibility gate (#836).
|
|
410
|
+
*/
|
|
411
|
+
export async function readRecordedSubmissionRows(
|
|
412
|
+
cwd: string,
|
|
413
|
+
runId: string,
|
|
414
|
+
homeOrScope?: string | SubmissionLedgerReadScope,
|
|
415
|
+
): Promise<readonly RecordedSubmissionRow[]> {
|
|
416
|
+
const scope = resolveReadScope(homeOrScope);
|
|
417
|
+
const { owned } = await readOwnedSubmissionRecords(cwd, runId, scope);
|
|
418
|
+
const scoped = recordsForAttempt(owned, scope.attemptId);
|
|
419
|
+
return mapOwnedToSubmissionRows(scoped);
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
/**
|
|
423
|
+
* Settlement-only this-court rows (#879 return path).
|
|
424
|
+
* Filters by attemptId for court-scoped roleOutcome; does not replace the
|
|
425
|
+
* run-scoped presentation API above (#836 visibility gate stays pass-through).
|
|
426
|
+
*/
|
|
427
|
+
export async function readAttemptScopedSubmissionRows(
|
|
428
|
+
cwd: string,
|
|
429
|
+
runId: string,
|
|
430
|
+
attemptId: string,
|
|
431
|
+
home?: string,
|
|
432
|
+
): Promise<readonly RecordedSubmissionRow[]> {
|
|
433
|
+
if (attemptId.length === 0) return [];
|
|
434
|
+
const { owned } = await readOwnedSubmissionRecords(
|
|
435
|
+
cwd,
|
|
436
|
+
runId,
|
|
437
|
+
home === undefined ? {} : { home },
|
|
438
|
+
);
|
|
439
|
+
return mapOwnedToSubmissionRows(owned.filter((record) => recordAttemptId(record) === attemptId));
|
|
440
|
+
}
|
|
441
|
+
|
|
363
442
|
/**
|
|
364
443
|
* All raw role payloads in ledger order (#836 multi-submit).
|
|
365
444
|
* Returns `accepted` bytes as stored — unknown, never re-wrapped.
|
|
@@ -390,7 +469,7 @@ export async function readLatestSubmissionOutcome(
|
|
|
390
469
|
homeOrScope?: string | SubmissionLedgerReadScope,
|
|
391
470
|
): Promise<LatestSubmissionOutcome | undefined> {
|
|
392
471
|
const scope = resolveReadScope(homeOrScope);
|
|
393
|
-
const { owned } = await readOwnedSubmissionRecords(cwd, runId, scope
|
|
472
|
+
const { owned } = await readOwnedSubmissionRecords(cwd, runId, scope);
|
|
394
473
|
const scoped = recordsForAttempt(owned, scope.attemptId);
|
|
395
474
|
for (let index = scoped.length - 1; index >= 0; index -= 1) {
|
|
396
475
|
const record = scoped[index];
|
|
@@ -405,14 +484,35 @@ export async function readLatestSubmissionOutcome(
|
|
|
405
484
|
|
|
406
485
|
type LedgerState = { prior?: RecordPointer; sequence: number };
|
|
407
486
|
|
|
408
|
-
async function restoreState(cwd: string, runId: string,
|
|
409
|
-
const
|
|
487
|
+
async function restoreState(cwd: string, runId: string, scope: SubmissionLedgerReadScope): Promise<LedgerState> {
|
|
488
|
+
const located = await readOwnedSubmissionRecords(cwd, runId, scope);
|
|
489
|
+
// Unknown-run empty set: no prior chain. Owned rows without a file are an internal bug.
|
|
490
|
+
if (located.file === undefined) {
|
|
491
|
+
if (located.owned.length !== 0) {
|
|
492
|
+
throw new Error(
|
|
493
|
+
`submission ledger invariant: owned rows present without recordFile for run ${runId}`,
|
|
494
|
+
);
|
|
495
|
+
}
|
|
496
|
+
return { sequence: 0 };
|
|
497
|
+
}
|
|
498
|
+
const { file, owned } = located;
|
|
410
499
|
const last = owned.at(-1);
|
|
411
500
|
return {
|
|
412
|
-
...(last === undefined
|
|
501
|
+
...(last === undefined
|
|
502
|
+
? {}
|
|
503
|
+
: {
|
|
504
|
+
prior: {
|
|
505
|
+
identity: last.identity,
|
|
506
|
+
recordFile: file,
|
|
507
|
+
kind: last.kind,
|
|
508
|
+
level: last.level,
|
|
509
|
+
},
|
|
510
|
+
}),
|
|
413
511
|
sequence: owned.reduce((maximum, record) => {
|
|
414
512
|
const payload = record.payload as Partial<SubmissionLedgerEvent> | undefined;
|
|
415
|
-
return payload?.type === "candidate" && typeof payload.sequence === "number"
|
|
513
|
+
return payload?.type === "candidate" && typeof payload.sequence === "number"
|
|
514
|
+
? Math.max(maximum, payload.sequence)
|
|
515
|
+
: maximum;
|
|
416
516
|
}, 0),
|
|
417
517
|
};
|
|
418
518
|
}
|
|
@@ -431,21 +531,38 @@ export function createSubmissionLedgerHost(
|
|
|
431
531
|
options?: { home?: string },
|
|
432
532
|
): RoleHost {
|
|
433
533
|
const states = new Map<string, Promise<LedgerState>>();
|
|
534
|
+
/** Sole HostContext-derived run coordinate for restore and append (never process.env). */
|
|
535
|
+
const sessionParentFromContext = (context: HostContext): string | undefined => {
|
|
536
|
+
const runDirectory = runDirectoryFromHostContext(context);
|
|
537
|
+
if (runDirectory !== undefined) {
|
|
538
|
+
return join(runDirectory, "session", "session.jsonl");
|
|
539
|
+
}
|
|
540
|
+
const sessionFile = context.sessionManager.getSessionFile?.();
|
|
541
|
+
if (typeof sessionFile === "string" && sessionFile.length > 0) return sessionFile;
|
|
542
|
+
const sessionDir = context.sessionManager.getSessionDir?.();
|
|
543
|
+
if (typeof sessionDir === "string" && sessionDir.length > 0) {
|
|
544
|
+
return join(sessionDir, "session.jsonl");
|
|
545
|
+
}
|
|
546
|
+
return undefined;
|
|
547
|
+
};
|
|
434
548
|
const resolveHomeFromContext = (context: HostContext): string | undefined => {
|
|
435
549
|
if (options?.home !== undefined) return options.home;
|
|
436
|
-
const
|
|
437
|
-
return
|
|
438
|
-
? tryHomeFromAkRolesPath(sessionFile)
|
|
439
|
-
: undefined;
|
|
550
|
+
const sessionParent = sessionParentFromContext(context);
|
|
551
|
+
return sessionParent !== undefined ? tryHomeFromAkRolesPath(sessionParent) : undefined;
|
|
440
552
|
};
|
|
441
553
|
const stateFor = (context: HostContext, runId: string) => states.get(runId) ?? (() => {
|
|
442
554
|
const home = resolveHomeFromContext(context);
|
|
443
|
-
const
|
|
555
|
+
const sessionParent = sessionParentFromContext(context);
|
|
556
|
+
const pending = restoreState(context.cwd, runId, {
|
|
557
|
+
...(home === undefined ? {} : { home }),
|
|
558
|
+
...(sessionParent === undefined ? {} : { sessionParent }),
|
|
559
|
+
});
|
|
444
560
|
states.set(runId, pending);
|
|
445
561
|
return pending;
|
|
446
562
|
})();
|
|
447
563
|
const appendFor = (state: LedgerState, context: HostContext, runId: string, attemptId: string, event: SubmissionLedgerEvent): RecordPointer => {
|
|
448
564
|
const home = resolveHomeFromContext(context);
|
|
565
|
+
const sessionParent = sessionParentFromContext(context);
|
|
449
566
|
const pointer = sitianReport({
|
|
450
567
|
level: "event",
|
|
451
568
|
kind: event.type,
|
|
@@ -455,6 +572,7 @@ export function createSubmissionLedgerHost(
|
|
|
455
572
|
source: "role-runtime",
|
|
456
573
|
cwd: context.cwd,
|
|
457
574
|
...(home !== undefined ? { home } : {}),
|
|
575
|
+
...(sessionParent === undefined ? {} : { sessionParent }),
|
|
458
576
|
});
|
|
459
577
|
state.prior = pointer;
|
|
460
578
|
return pointer;
|
package/src/ticket-provenance.ts
CHANGED
|
@@ -13,6 +13,7 @@ import {
|
|
|
13
13
|
type RecordPointer,
|
|
14
14
|
type SitianRecord,
|
|
15
15
|
} from "./sitian-facade.ts";
|
|
16
|
+
import { isSafePositiveTicketNumber } from "./run-ticket-number.ts";
|
|
16
17
|
import {
|
|
17
18
|
TICKET_PROVENANCE_HUMAN_VIEW,
|
|
18
19
|
TICKET_PROVENANCE_KIND,
|
|
@@ -25,7 +26,7 @@ import {
|
|
|
25
26
|
|
|
26
27
|
/** Subject string for ticket-keyed volumes — history follows the ticket. */
|
|
27
28
|
export function ticketProvenanceSubject(ticketNumber: number): string {
|
|
28
|
-
if (!
|
|
29
|
+
if (!isSafePositiveTicketNumber(ticketNumber)) {
|
|
29
30
|
throw new Error(`ticket-provenance subject requires a positive ticket number, got ${String(ticketNumber)}`);
|
|
30
31
|
}
|
|
31
32
|
return String(ticketNumber);
|
|
@@ -57,6 +58,7 @@ export function ticketProvenanceEntryIdentity(
|
|
|
57
58
|
export type AppendTicketProvenanceInput = {
|
|
58
59
|
readonly ticketNumber: number;
|
|
59
60
|
readonly cwd: string;
|
|
61
|
+
readonly sessionParent: string;
|
|
60
62
|
/** Original diarist row (or `{ original, unprojected: true }` envelope). */
|
|
61
63
|
readonly payload: unknown;
|
|
62
64
|
/** Explicit package home (tests / admitted run); never process.env.HOME (#604). */
|
|
@@ -107,6 +109,7 @@ export function appendTicketProvenanceEntry(
|
|
|
107
109
|
identity,
|
|
108
110
|
subject,
|
|
109
111
|
cwd: input.cwd,
|
|
112
|
+
sessionParent: input.sessionParent,
|
|
110
113
|
...(input.home === undefined ? {} : { home: input.home }),
|
|
111
114
|
host: input.host ?? "diarist",
|
|
112
115
|
source: input.source ?? "diarist",
|
package/src/ticket-trajectory.ts
CHANGED
|
@@ -29,6 +29,8 @@ import {
|
|
|
29
29
|
formatTokensCompact,
|
|
30
30
|
formatUsdPrecise,
|
|
31
31
|
} from "./human-format.ts";
|
|
32
|
+
import { listBookRunDirectories } from "./role-run-placement.ts";
|
|
33
|
+
import { readRunTicketNumber } from "./run-ticket-number.ts";
|
|
32
34
|
import {
|
|
33
35
|
extractSessionTimestampSpan,
|
|
34
36
|
readLedgerSessionJsonl,
|
|
@@ -302,16 +304,12 @@ async function readInvocation(runDir: string): Promise<InvocationInfo | undefine
|
|
|
302
304
|
info.model = rawModel;
|
|
303
305
|
}
|
|
304
306
|
}
|
|
305
|
-
if (
|
|
306
|
-
typeof parsed.ticketNumber === "number" &&
|
|
307
|
-
Number.isInteger(parsed.ticketNumber) &&
|
|
308
|
-
parsed.ticketNumber >= 1
|
|
309
|
-
) {
|
|
310
|
-
info.ticketNumber = parsed.ticketNumber;
|
|
311
|
-
}
|
|
312
307
|
if (typeof parsed.correlationId === "string" && parsed.correlationId.trim() !== "") {
|
|
313
308
|
info.correlationId = parsed.correlationId;
|
|
314
309
|
}
|
|
310
|
+
// Display placement: board pages first, then migration-derived (never forges board).
|
|
311
|
+
const ticketNumber = await readRunTicketNumber(runDir);
|
|
312
|
+
if (ticketNumber !== undefined) info.ticketNumber = ticketNumber;
|
|
315
313
|
return info;
|
|
316
314
|
} catch (error) {
|
|
317
315
|
// Only genuine absence activates the invocation fallback. Malformed JSON and
|
|
@@ -656,56 +654,47 @@ ${stationBlocks || "<p data-empty=\"true\">no runs</p>"}
|
|
|
656
654
|
* Load one ticket's runs via the S1 tracer path (read-only ledger scan).
|
|
657
655
|
* Factory board reuses this — no parallel receipt parser.
|
|
658
656
|
*
|
|
659
|
-
*
|
|
660
|
-
*
|
|
661
|
-
*
|
|
657
|
+
* Book-run attribution (#176 / #859): read typed `ticketNumber` from each run's
|
|
658
|
+
* invocation.json under listBookRunDirectories (flat legacy `runs/` + subject-tree
|
|
659
|
+
* `<ticket|unbound>/runs/`). Legacy `issues/<N>/runs` remains a read-only
|
|
660
|
+
* compatibility entrance. Unbound runs never join a ticket.
|
|
662
661
|
*/
|
|
663
662
|
|
|
664
|
-
/** One
|
|
663
|
+
/** One book run's typed binding projection from invocation.json. */
|
|
665
664
|
export type FlatRunTicketBinding = {
|
|
666
665
|
readonly runDir: string;
|
|
667
666
|
readonly runFolder: string;
|
|
667
|
+
/** Book-relative coord (e.g. `runs/id@role` or `582/runs/id@role`). */
|
|
668
|
+
readonly ledgerCoord: string;
|
|
668
669
|
readonly ticketNumber?: number;
|
|
669
670
|
readonly role?: string;
|
|
670
671
|
readonly correlationId?: string;
|
|
671
672
|
};
|
|
672
673
|
|
|
673
674
|
/**
|
|
674
|
-
* Book-level
|
|
675
|
-
* Pure derived view of
|
|
675
|
+
* Book-level run index built once per lane/render and reused per ticket.
|
|
676
|
+
* Pure derived view of book runs' invocation.json files — not a parallel ledger.
|
|
676
677
|
*/
|
|
677
678
|
export type TicketTrajectoryBookIndex = {
|
|
678
|
-
/** ticket number →
|
|
679
|
+
/** ticket number → runs bound via invocation.json ticketNumber. */
|
|
679
680
|
readonly runsByTicket: ReadonlyMap<number, readonly FlatRunTicketBinding[]>;
|
|
680
|
-
/**
|
|
681
|
+
/** Runs with no typed ticketNumber (unknown/unbound seam). */
|
|
681
682
|
readonly unboundRuns: readonly FlatRunTicketBinding[];
|
|
682
683
|
};
|
|
683
684
|
|
|
684
|
-
async function listFlatRunDirectories(ledgerDir: string): Promise<string[]> {
|
|
685
|
-
const runsDir = join(resolve(ledgerDir), "runs");
|
|
686
|
-
try {
|
|
687
|
-
const entries = await readdir(runsDir, { withFileTypes: true });
|
|
688
|
-
return entries
|
|
689
|
-
.filter((entry) => entry.isDirectory())
|
|
690
|
-
.map((entry) => join(runsDir, entry.name))
|
|
691
|
-
.sort();
|
|
692
|
-
} catch (error) {
|
|
693
|
-
if (isMissingPathError(error)) return [];
|
|
694
|
-
throw error;
|
|
695
|
-
}
|
|
696
|
-
}
|
|
697
|
-
|
|
698
685
|
export async function buildTicketTrajectoryBookIndex(
|
|
699
686
|
ledgerDir: string,
|
|
700
687
|
): Promise<TicketTrajectoryBookIndex> {
|
|
701
688
|
const root = resolve(ledgerDir);
|
|
702
689
|
const runsByTicket = new Map<number, FlatRunTicketBinding[]>();
|
|
703
690
|
const unboundRuns: FlatRunTicketBinding[] = [];
|
|
704
|
-
for (const runDir of await
|
|
691
|
+
for (const runDir of await listBookRunDirectories(root)) {
|
|
705
692
|
const invocation = await readInvocation(runDir);
|
|
693
|
+
const rel = relative(root, runDir).split(sep).join("/");
|
|
706
694
|
const binding: FlatRunTicketBinding = {
|
|
707
695
|
runDir,
|
|
708
696
|
runFolder: basename(runDir),
|
|
697
|
+
ledgerCoord: rel,
|
|
709
698
|
...(invocation?.ticketNumber !== undefined
|
|
710
699
|
? { ticketNumber: invocation.ticketNumber }
|
|
711
700
|
: {}),
|
|
@@ -744,8 +733,7 @@ export async function loadUnboundTrajectoryRuns(
|
|
|
744
733
|
const index = bookIndex ?? (await buildTicketTrajectoryBookIndex(root));
|
|
745
734
|
const out: UnboundTrajectoryRun[] = [];
|
|
746
735
|
for (const binding of index.unboundRuns) {
|
|
747
|
-
const
|
|
748
|
-
const run = await parseRunDirectory(binding.runDir, ledgerCoord);
|
|
736
|
+
const run = await parseRunDirectory(binding.runDir, binding.ledgerCoord);
|
|
749
737
|
out.push({
|
|
750
738
|
run,
|
|
751
739
|
role: binding.role ?? run.station,
|
|
@@ -781,14 +769,13 @@ export async function loadTicketTrajectoryRuns(
|
|
|
781
769
|
runs.push(parsed);
|
|
782
770
|
}
|
|
783
771
|
|
|
784
|
-
//
|
|
772
|
+
// Book runs (flat + subject-tree) bound by typed invocation.json ticketNumber.
|
|
785
773
|
const flatBindings = index.runsByTicket.get(issueNumber) ?? [];
|
|
786
774
|
for (const binding of flatBindings) {
|
|
787
775
|
const resolvedRunDir = resolve(binding.runDir);
|
|
788
776
|
if (seenRunDirs.has(resolvedRunDir)) continue;
|
|
789
777
|
seenRunDirs.add(resolvedRunDir);
|
|
790
|
-
|
|
791
|
-
runs.push(await parseRunDirectory(binding.runDir, ledgerCoord));
|
|
778
|
+
runs.push(await parseRunDirectory(binding.runDir, binding.ledgerCoord));
|
|
792
779
|
}
|
|
793
780
|
|
|
794
781
|
return runs;
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Typed user-dialogue stdin transport (#879 / #471).
|
|
3
|
+
* Pi's piped-stdin reader trims the wrapper; the body field keeps original bytes.
|
|
4
|
+
* No size cap and no path/pointer substitute — the whole dialogue rides the envelope.
|
|
5
|
+
*/
|
|
6
|
+
export const USER_DIALOGUE_STDIN_KIND = "ak-user-dialogue" as const;
|
|
7
|
+
|
|
8
|
+
export type UserDialogueStdinEnvelope = {
|
|
9
|
+
readonly kind: typeof USER_DIALOGUE_STDIN_KIND;
|
|
10
|
+
readonly body: string;
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
export function encodeUserDialogueStdin(body: string): string {
|
|
14
|
+
return JSON.stringify({ kind: USER_DIALOGUE_STDIN_KIND, body } satisfies UserDialogueStdinEnvelope);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function isUserDialogueEnvelope(value: unknown): value is UserDialogueStdinEnvelope {
|
|
18
|
+
if (value === null || typeof value !== "object" || Array.isArray(value)) return false;
|
|
19
|
+
const record = value as { kind?: unknown; body?: unknown };
|
|
20
|
+
return record.kind === USER_DIALOGUE_STDIN_KIND && typeof record.body === "string";
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Host read: recover original dialogue after a trim-capable pipe.
|
|
25
|
+
* Non-envelope text (interactive Pi, officer JSON, headless raw body) is unchanged.
|
|
26
|
+
*/
|
|
27
|
+
export function readUserDialogueStdin(raw: string): string {
|
|
28
|
+
const trimmed = raw.trim();
|
|
29
|
+
if (!trimmed.startsWith("{")) return raw;
|
|
30
|
+
try {
|
|
31
|
+
const parsed: unknown = JSON.parse(trimmed);
|
|
32
|
+
if (isUserDialogueEnvelope(parsed)) return parsed.body;
|
|
33
|
+
} catch {
|
|
34
|
+
// Not our envelope — leave caller bytes intact.
|
|
35
|
+
}
|
|
36
|
+
return raw;
|
|
37
|
+
}
|
package/src/worker-role.ts
CHANGED
|
@@ -145,8 +145,8 @@ export type CoderRoleDependencies = {
|
|
|
145
145
|
|
|
146
146
|
export type WorkerRoleRuntime = {
|
|
147
147
|
activate(ctx?: HostContext): Promise<void>;
|
|
148
|
-
/** Arm gate ① baseline after envelope places the worktree (coder/fixer).
|
|
149
|
-
armSubmissionGate(cwd: string, parent
|
|
148
|
+
/** Arm gate ① baseline after envelope places the worktree (coder/fixer). Durable parent required (#857). */
|
|
149
|
+
armSubmissionGate(cwd: string, parent: { getSessionFile(): string | undefined }): void;
|
|
150
150
|
};
|
|
151
151
|
|
|
152
152
|
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
@@ -290,6 +290,8 @@ export function createFixerRoleRuntime(
|
|
|
290
290
|
...(_signal === undefined ? {} : { signal: _signal }),
|
|
291
291
|
hostActions,
|
|
292
292
|
toolCallId,
|
|
293
|
+
// #879: this-turn typed payload — identity-bound at submit site.
|
|
294
|
+
submission: output,
|
|
293
295
|
});
|
|
294
296
|
}
|
|
295
297
|
const acceptedDetails = output;
|
|
@@ -326,7 +328,7 @@ export function createFixerRoleRuntime(
|
|
|
326
328
|
});
|
|
327
329
|
}
|
|
328
330
|
},
|
|
329
|
-
armSubmissionGate(cwd: string, parent
|
|
331
|
+
armSubmissionGate(cwd: string, parent: { getSessionFile(): string | undefined }) {
|
|
330
332
|
submissionGate.arm(cwd, parent);
|
|
331
333
|
},
|
|
332
334
|
};
|
|
@@ -428,6 +430,8 @@ export function createCoderRoleRuntime(
|
|
|
428
430
|
...(_signal === undefined ? {} : { signal: _signal }),
|
|
429
431
|
hostActions,
|
|
430
432
|
toolCallId,
|
|
433
|
+
// #879: this-turn typed payload — identity-bound at submit site.
|
|
434
|
+
submission: output,
|
|
431
435
|
});
|
|
432
436
|
}
|
|
433
437
|
const acceptedDetails = output;
|
|
@@ -473,7 +477,7 @@ export function createCoderRoleRuntime(
|
|
|
473
477
|
});
|
|
474
478
|
}
|
|
475
479
|
},
|
|
476
|
-
armSubmissionGate(cwd: string, parent
|
|
480
|
+
armSubmissionGate(cwd: string, parent: { getSessionFile(): string | undefined }) {
|
|
477
481
|
submissionGate.arm(cwd, parent);
|
|
478
482
|
},
|
|
479
483
|
};
|
|
@@ -221,9 +221,8 @@ function reliableWindow(
|
|
|
221
221
|
|
|
222
222
|
export type CreateWorkerSubmissionGateOptions = {
|
|
223
223
|
/**
|
|
224
|
-
* Explicit package home for sitian writes
|
|
225
|
-
*
|
|
226
|
-
* (path-derive from parent, else package machine home). #604 Scope 2.
|
|
224
|
+
* Explicit package home for sitian writes (tests / admitted run).
|
|
225
|
+
* Path still derives from required parent session; home only pins ledger root. #604 Scope 2.
|
|
227
226
|
*/
|
|
228
227
|
readonly home?: string;
|
|
229
228
|
};
|
|
@@ -231,7 +230,8 @@ export type CreateWorkerSubmissionGateOptions = {
|
|
|
231
230
|
export function createWorkerSubmissionGate(
|
|
232
231
|
options: CreateWorkerSubmissionGateOptions = {},
|
|
233
232
|
): {
|
|
234
|
-
arm(
|
|
233
|
+
/** Durable parent is required — ownership must be known at arm (#857 loud failure). */
|
|
234
|
+
arm(cwd: string, parent: WorkerSubmissionGateParent): void;
|
|
235
235
|
assertAcceptable(status: string, details?: unknown): void;
|
|
236
236
|
} {
|
|
237
237
|
let baseline: string | null | undefined;
|
|
@@ -267,11 +267,11 @@ export function createWorkerSubmissionGate(
|
|
|
267
267
|
arm(cwd, parent) {
|
|
268
268
|
uninstallPackageWorkerHooks(cwd);
|
|
269
269
|
root = cwd;
|
|
270
|
-
sessionParent = parent
|
|
270
|
+
sessionParent = parent.getSessionFile();
|
|
271
271
|
record = createRecordSession({
|
|
272
272
|
cwd,
|
|
273
273
|
kind: WORKER_SUBMISSION_GATE_RECORD_KIND,
|
|
274
|
-
|
|
274
|
+
parent,
|
|
275
275
|
});
|
|
276
276
|
const prior = readGateState(record);
|
|
277
277
|
if (prior.baseline !== undefined) {
|