@akagilnc/pi-workflow-roles 0.1.5504 → 0.1.5575
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/README.md +1 -1
- package/README.zh-CN.md +1 -1
- package/dist/acp-host/production-host.js +5965 -5994
- package/dist/analyst-gate-cycles-read.js +23 -55
- package/dist/analyst-ledger.js +21 -35
- package/dist/analyst-metric-families/acceptance-success-rework.js +25 -1
- package/dist/analyst-metric-families/round-timeline.js +2 -5
- package/dist/archivist-record-pointer.js +38 -30
- package/dist/auditor-dossier-tool.js +4 -3
- package/dist/auditor-soul.js +3 -11
- package/dist/doctor-evidence.js +3 -3
- package/dist/engine-detour-usage.js +6 -14
- package/dist/headless-host/production-host.js +5316 -5324
- package/dist/host-descriptions.js +0 -4
- package/dist/host-session-record.js +266 -267
- package/dist/migrate-book-topology.js +1314 -696
- package/dist/notary-source-run.js +1 -1
- package/dist/packaged-role-registry.js +5 -44
- package/dist/pi/role-turn-host.js +53 -3
- package/dist/public-cli/auto-resume.js +8 -47
- package/dist/public-cli/instruction-seat-run.js +5 -3
- package/dist/public-cli/invocation.js +51 -56
- package/dist/public-cli/main.js +1916 -1748
- package/dist/public-cli/post-admission.js +96 -55
- package/dist/public-cli/run-lifecycle.js +27 -53
- package/dist/public-cli/settlement.js +157 -302
- package/dist/role-run-placement.js +1 -11
- package/dist/role-run-relocation.js +72 -50
- package/dist/run-dossier-files.js +30 -0
- package/dist/run-dossier.js +322 -0
- package/dist/run-terminal-artifacts.js +68 -197
- package/dist/run-ticket-number.js +16 -3
- package/dist/session-identity.js +49 -18
- package/dist/sitian-appender.js +13 -16
- package/dist/sitian-facade.js +11 -0
- package/dist/sitian-reader.js +44 -29
- package/dist/submission-gate.js +1 -1
- package/dist/ticket-provenance.js +22 -7
- package/package.json +1 -1
- package/scripts/build-package.mjs +2 -0
- package/src/acp-host/production-host.ts +1 -1
- package/src/acp-host/role-turn-host.ts +9 -2
- package/src/analyst-gate-cycles-read.ts +26 -60
- package/src/analyst-ledger.ts +27 -42
- package/src/analyst-metric-families/acceptance-success-rework.ts +24 -1
- package/src/analyst-metric-families/gate-cycles.ts +1 -1
- package/src/analyst-metric-families/round-timeline.ts +2 -5
- package/src/analyst-page.ts +3 -3
- package/src/archivist-record-entry.ts +2 -2
- package/src/archivist-record-pointer.ts +50 -36
- package/src/auditor-dossier-tool.ts +7 -5
- package/src/auditor-soul.ts +3 -9
- package/src/book-topology-migration-placement.ts +16 -3
- package/src/book-topology-record-class-migrators.ts +4 -2
- package/src/book-topology-runs-migrator.ts +6 -3
- package/src/doctor-contracts.ts +1 -1
- package/src/doctor-evidence.ts +2 -2
- package/src/engine-detour-tool.ts +1 -1
- package/src/engine-detour-usage.ts +6 -16
- package/src/external-host-turn-loop.ts +25 -0
- package/src/headless-host/production-host.ts +1 -4
- package/src/headless-host/role-turn-host.ts +54 -20
- package/src/host-descriptions.ts +0 -6
- package/src/host-session-record.ts +224 -140
- package/src/notary-source-run.ts +1 -1
- package/src/packaged-role-registry.ts +1 -50
- package/src/pi/adapter.ts +50 -7
- package/src/pi/role-turn-host.ts +51 -3
- package/src/public-cli/auto-resume.ts +7 -43
- package/src/public-cli/instruction-seat-run.ts +9 -2
- package/src/public-cli/invocation.ts +61 -87
- package/src/public-cli/post-admission.ts +111 -72
- package/src/public-cli/run-lifecycle.ts +27 -72
- package/src/public-cli/settlement.ts +193 -418
- package/src/public-cli/terminal.ts +1 -1
- package/src/role-run-placement.ts +2 -13
- package/src/role-run-relocation.ts +71 -68
- package/src/role-runtime.ts +6 -10
- package/src/run-dossier-files.ts +33 -0
- package/src/run-dossier.ts +357 -0
- package/src/run-terminal-artifacts.ts +83 -237
- package/src/run-ticket-number.ts +41 -11
- package/src/session-identity.ts +52 -20
- package/src/sitian-appender.ts +17 -18
- package/src/sitian-facade.ts +22 -0
- package/src/sitian-reader.ts +62 -32
- package/src/submission-gate.ts +1 -1
- package/src/ticket-provenance.ts +20 -7
- package/src/ticket-trajectory.ts +10 -10
package/src/pi/role-turn-host.ts
CHANGED
|
@@ -25,10 +25,13 @@ import { ExplicitInternalActivationError } from "../host-contracts.ts";
|
|
|
25
25
|
import { applyEngineChildEnv, ENGINE_MODEL_FLAG_NAME, normalizeEngineName } from "../engine-detour.ts";
|
|
26
26
|
import { projectActivationFlags } from "../role-activation-flags.ts";
|
|
27
27
|
import { encodeUserDialogueStdin } from "../user-dialogue-stdin.ts";
|
|
28
|
-
import { readStrictPiSessionJsonl } from "../ledger-session-read.ts";
|
|
28
|
+
import { readLedgerSessionJsonlLines, readStrictPiSessionJsonl } from "../ledger-session-read.ts";
|
|
29
|
+
import { copyAndRecordHostDossier } from "../host-session-record.ts";
|
|
29
30
|
import { projectThrownFailureLeaf, retainPackageFault } from "../public-cli/settlement.ts";
|
|
30
31
|
import { describeErrorIdentity } from "../public-cli/run-lifecycle.ts";
|
|
31
32
|
import { RECEIPT_DELIVERY_LIMIT_ENV } from "../receipt-delivery-policy.ts";
|
|
33
|
+
import { createSessionIdentityAuthority } from "../session-identity.ts";
|
|
34
|
+
import { sitianReport } from "../sitian-facade.ts";
|
|
32
35
|
|
|
33
36
|
/** Package-relative Internal role entrypoint (ADR 0052; same path as public-cli registry). */
|
|
34
37
|
const INTERNAL_ROLE_ENTRYPOINT_RELATIVE = "extensions/role-runtime.ts";
|
|
@@ -450,18 +453,63 @@ export function createPiRoleTurnHost(config: PiRoleTurnHostConfig): RoleTurnHost
|
|
|
450
453
|
);
|
|
451
454
|
}
|
|
452
455
|
const timeoutMs = request.timeoutMs ?? config.timeoutMs;
|
|
453
|
-
|
|
456
|
+
const result = await spawnRunner(args, {
|
|
454
457
|
cwd: request.cwd,
|
|
455
458
|
env,
|
|
456
459
|
stdin,
|
|
457
460
|
...(timeoutMs === undefined ? {} : { timeoutMs }),
|
|
458
461
|
...(request.signal === undefined ? {} : { signal: request.signal }),
|
|
459
462
|
});
|
|
463
|
+
// Deliver already-written Pi host facts. Pi lands at session/session.jsonl;
|
|
464
|
+
// bind projects host.sessions; copy records the landing. Host adapters do
|
|
465
|
+
// not write current.json — public bind/settlement seams own that render.
|
|
466
|
+
try {
|
|
467
|
+
const { sessionFile, sessionDirectory } = config.principalAuthority.decode(request.principal);
|
|
468
|
+
const sessionId = await readPiSessionHeaderId(sessionFile);
|
|
469
|
+
if (sessionId !== undefined) {
|
|
470
|
+
await createSessionIdentityAuthority(config.principalAuthority, "pi").bind(
|
|
471
|
+
request.principal,
|
|
472
|
+
sessionId,
|
|
473
|
+
);
|
|
474
|
+
copyAndRecordHostDossier({
|
|
475
|
+
host: "pi",
|
|
476
|
+
sessionId,
|
|
477
|
+
cwd: request.cwd,
|
|
478
|
+
sessionDirectory,
|
|
479
|
+
sessionParent: sessionFile,
|
|
480
|
+
...(request.home !== undefined ? { home: request.home } : {}),
|
|
481
|
+
});
|
|
482
|
+
}
|
|
483
|
+
} catch (error) {
|
|
484
|
+
await retainPackageFault({
|
|
485
|
+
runDirectory: request.runDirectory,
|
|
486
|
+
diagnostic: `pi host dossier record failed beside host terminal: ${describeErrorIdentity(error)}`,
|
|
487
|
+
error,
|
|
488
|
+
});
|
|
489
|
+
}
|
|
490
|
+
return result;
|
|
460
491
|
},
|
|
461
492
|
};
|
|
462
493
|
}
|
|
463
494
|
|
|
464
|
-
|
|
495
|
+
/**
|
|
496
|
+
* Session header id from the Pi volume the child (or fixture) already wrote.
|
|
497
|
+
* Reuses the ledger line kernel: a later malformed line must not erase a valid
|
|
498
|
+
* header already present (ADR 0086 originals are not validated whole-file).
|
|
499
|
+
*/
|
|
500
|
+
async function readPiSessionHeaderId(sessionFile: string): Promise<string | undefined> {
|
|
501
|
+
try {
|
|
502
|
+
for (const line of await readLedgerSessionJsonlLines(sessionFile)) {
|
|
503
|
+
const entry = line.row;
|
|
504
|
+
if (entry === undefined || entry.type !== "session") continue;
|
|
505
|
+
if (typeof entry.id === "string" && entry.id.trim() !== "") return entry.id;
|
|
506
|
+
}
|
|
507
|
+
} catch (error) {
|
|
508
|
+
if ((error as NodeJS.ErrnoException).code === "ENOENT") return undefined;
|
|
509
|
+
throw error;
|
|
510
|
+
}
|
|
511
|
+
return undefined;
|
|
512
|
+
}
|
|
465
513
|
|
|
466
514
|
/** Last non-session entry id. Session headers are not parents of custom lines. */
|
|
467
515
|
export function piSessionCustomParentId(
|
|
@@ -11,10 +11,6 @@
|
|
|
11
11
|
* path continues: same budget, same call-local count semantics. No failure-type
|
|
12
12
|
* classification — every thrown value is treated identically.
|
|
13
13
|
*/
|
|
14
|
-
import { constants as fsConstants } from "node:fs";
|
|
15
|
-
import { randomUUID } from "node:crypto";
|
|
16
|
-
import { open } from "node:fs/promises";
|
|
17
|
-
import { join } from "node:path";
|
|
18
14
|
import type {
|
|
19
15
|
DurablePrincipal,
|
|
20
16
|
DurablePrincipalAuthority,
|
|
@@ -33,14 +29,13 @@ import { processCancelSignalName } from "./process-cancel.ts";
|
|
|
33
29
|
import { isLawfulTypedTerminalOutcome, formatTerminalResult, type TerminalArtifactRef, type TerminalResult, type TerminalRoleName } from "./terminal.ts";
|
|
34
30
|
import {
|
|
35
31
|
attachRecordedSubmissions,
|
|
36
|
-
ensureRealArtifactsDirectory,
|
|
37
32
|
presentFailureTerminal,
|
|
38
33
|
retainPackageFault,
|
|
39
34
|
} from "./settlement.ts";
|
|
40
35
|
|
|
41
|
-
export { ensureRealArtifactsDirectory };
|
|
42
36
|
import type { CliIo } from "./cli-io.ts";
|
|
43
37
|
import { serializeThrownValue } from "../serialize-thrown-value.ts";
|
|
38
|
+
import { reportRunRecord } from "../sitian-facade.ts";
|
|
44
39
|
|
|
45
40
|
import { errorText } from "../unknown-value.ts";
|
|
46
41
|
|
|
@@ -155,41 +150,11 @@ function jsonSafeReplacer(): (key: string, value: unknown) => unknown {
|
|
|
155
150
|
}
|
|
156
151
|
|
|
157
152
|
/**
|
|
158
|
-
* Retain one throwing dispatch attempt's complete exception as
|
|
159
|
-
*
|
|
160
|
-
*
|
|
161
|
-
*
|
|
162
|
-
* attempt can never overwrite an earlier attempt's file.
|
|
153
|
+
* Retain one throwing dispatch attempt's complete exception as a line in the
|
|
154
|
+
* run's log.jsonl, then leave an addressable pointer in the session principal
|
|
155
|
+
* (custom entry). The log only appends (史必追加, #419): a later attempt can
|
|
156
|
+
* never overwrite an earlier attempt's record.
|
|
163
157
|
*/
|
|
164
|
-
/**
|
|
165
|
-
* Hardened create-once JSON write shared by every durable artifact this loop
|
|
166
|
-
* retains directly (dispatch-error dumps, lawful-persist-failure error/
|
|
167
|
-
* evidence records): O_EXCL (fail loud on a colliding name, never overwrite)
|
|
168
|
-
* + O_NOFOLLOW where the platform provides it (a planted symlink is never
|
|
169
|
-
* followed) — mirrors settlement.ts's own hardened artifact writers.
|
|
170
|
-
*/
|
|
171
|
-
export async function writeHardenedArtifactFile(
|
|
172
|
-
artifactsDir: string,
|
|
173
|
-
namePrefix: string,
|
|
174
|
-
payload: Record<string, unknown>,
|
|
175
|
-
): Promise<string> {
|
|
176
|
-
const filePath = join(artifactsDir, `${namePrefix}-${randomUUID()}.json`);
|
|
177
|
-
const body = `${JSON.stringify(payload, jsonSafeReplacer(), 2)}\n`;
|
|
178
|
-
const noFollowFlag =
|
|
179
|
-
typeof fsConstants.O_NOFOLLOW === "number" ? fsConstants.O_NOFOLLOW : 0;
|
|
180
|
-
const handle = await open(
|
|
181
|
-
filePath,
|
|
182
|
-
fsConstants.O_WRONLY | fsConstants.O_CREAT | fsConstants.O_EXCL | noFollowFlag,
|
|
183
|
-
0o600,
|
|
184
|
-
);
|
|
185
|
-
try {
|
|
186
|
-
await handle.writeFile(body, "utf8");
|
|
187
|
-
} finally {
|
|
188
|
-
await handle.close();
|
|
189
|
-
}
|
|
190
|
-
return filePath;
|
|
191
|
-
}
|
|
192
|
-
|
|
193
158
|
async function retainDispatchError(
|
|
194
159
|
admitted: { runDirectory: string; principal: DurablePrincipal },
|
|
195
160
|
principalAuthority: DurablePrincipalAuthority,
|
|
@@ -197,14 +162,13 @@ async function retainDispatchError(
|
|
|
197
162
|
attempt: number,
|
|
198
163
|
error: unknown,
|
|
199
164
|
): Promise<{ file: string; pointerError?: unknown }> {
|
|
200
|
-
const artifactsDir = await ensureRealArtifactsDirectory(admitted.runDirectory);
|
|
201
165
|
// Whole-object dump: everything the thrown value carries, nothing picked.
|
|
202
|
-
const filePath =
|
|
166
|
+
const filePath = reportRunRecord(admitted.runDirectory, "dispatch-exception", JSON.parse(JSON.stringify({
|
|
203
167
|
version: 1,
|
|
204
168
|
attempt,
|
|
205
169
|
recordedAt: new Date().toISOString(),
|
|
206
170
|
error: serializeThrownValue(error),
|
|
207
|
-
});
|
|
171
|
+
}, jsonSafeReplacer())) as Record<string, unknown>, "auto-resume").recordFile;
|
|
208
172
|
// Addressable pointer in the dossier (卷宗): Pi session custom-entry codec
|
|
209
173
|
// (appendPiSessionCustomEntry). Lease still owned here with run-writer.
|
|
210
174
|
let pointerLease: RunWriterLease;
|
|
@@ -56,6 +56,7 @@ import {
|
|
|
56
56
|
type RunDirectoryRelocation,
|
|
57
57
|
} from "./invocation.ts";
|
|
58
58
|
import {
|
|
59
|
+
bindDiaristAssertedTicketNumber,
|
|
59
60
|
presentControlledFailure,
|
|
60
61
|
packageFaultNoteFor,
|
|
61
62
|
prepareSummonsResumeMaterials,
|
|
@@ -321,6 +322,8 @@ async function bindAndRelocateDiarist(
|
|
|
321
322
|
authority: DurablePrincipalAuthority,
|
|
322
323
|
lease?: RunWriterLease,
|
|
323
324
|
): Promise<RunDirectoryRelocation | undefined> {
|
|
325
|
+
// The diarist's asserted ticketNumber binds here, in the public call's process.
|
|
326
|
+
await bindDiaristAssertedTicketNumber(admitted);
|
|
324
327
|
const boardTicket = await readBoardTicketNumber(admitted.runDirectory);
|
|
325
328
|
if (boardTicket === undefined) return undefined;
|
|
326
329
|
if (admitted.ticketNumber === undefined) {
|
|
@@ -1096,7 +1099,11 @@ export async function continueParentAfterChild(
|
|
|
1096
1099
|
&& heldUnreadableTerminal(stopped.terminal)
|
|
1097
1100
|
&& stopped.terminal !== undefined
|
|
1098
1101
|
) {
|
|
1099
|
-
const parentTerminal = await trySettlePublicSeat(
|
|
1102
|
+
const parentTerminal = await trySettlePublicSeat(
|
|
1103
|
+
admitted,
|
|
1104
|
+
env.principalAuthority,
|
|
1105
|
+
{ ...await readCurrentCourt(admitted.runDirectory), ...packageFaultScope(admitted, env, io) },
|
|
1106
|
+
);
|
|
1100
1107
|
if (
|
|
1101
1108
|
parentTerminal?.roleOutcome.kind === "accepted"
|
|
1102
1109
|
|| parentTerminal?.roleOutcome.kind === "audit_escalation"
|
|
@@ -1115,7 +1122,7 @@ export async function continueParentAfterChild(
|
|
|
1115
1122
|
const terminal = await trySettlePublicSeat(
|
|
1116
1123
|
admitted,
|
|
1117
1124
|
env.principalAuthority,
|
|
1118
|
-
packageFaultScope(admitted, env, io),
|
|
1125
|
+
{ ...await readCurrentCourt(admitted.runDirectory), ...packageFaultScope(admitted, env, io) },
|
|
1119
1126
|
);
|
|
1120
1127
|
if (terminal?.roleOutcome.kind !== "accepted") throw new Error("pending submission is not recorded");
|
|
1121
1128
|
return auditSubmittedRole({ exitCode: 0, admitted, terminal }, env, io, resolved.admitted, latestQueuePayload(resolved.terminal));
|
|
@@ -30,7 +30,6 @@ import {
|
|
|
30
30
|
formatRunLeaf,
|
|
31
31
|
isUnboundRunDirectory,
|
|
32
32
|
listBookRunDirectories,
|
|
33
|
-
roleRunArtifactsDirectory,
|
|
34
33
|
roleRunPlacement,
|
|
35
34
|
type RoleRunSubject,
|
|
36
35
|
} from "../role-run-placement.ts";
|
|
@@ -60,6 +59,7 @@ import {
|
|
|
60
59
|
rewriteRunDirectoryPathFields,
|
|
61
60
|
rewriteRunDirectoryPathValue,
|
|
62
61
|
} from "../role-run-relocation.ts";
|
|
62
|
+
import { readPageSync, renderCurrentSync, updateSectionSync, writeSectionSync } from "../run-dossier.ts";
|
|
63
63
|
import {
|
|
64
64
|
loadDoctorCase,
|
|
65
65
|
} from "../doctor-evidence.ts";
|
|
@@ -137,7 +137,6 @@ export type AdmittedRoleInvocationBase = {
|
|
|
137
137
|
readonly runDirectory: string;
|
|
138
138
|
/** Host-issued opaque durable principal (coordinates only via authority.decode). */
|
|
139
139
|
readonly principal: DurablePrincipal;
|
|
140
|
-
readonly admittedRequestPath: string;
|
|
141
140
|
/**
|
|
142
141
|
* Optional opaque invocation correlation restored from a prior admitted page
|
|
143
142
|
* (ADR 0049 host channel). Admission does not mint ticket-binding ids.
|
|
@@ -318,7 +317,6 @@ export type AdmissionPlacement = {
|
|
|
318
317
|
readonly sessionDirectory: string;
|
|
319
318
|
readonly sessionFile: string;
|
|
320
319
|
readonly runDirectory: string;
|
|
321
|
-
readonly artifactsDirectory: string;
|
|
322
320
|
readonly attachmentsDirectory: string;
|
|
323
321
|
readonly ledgerHome: string;
|
|
324
322
|
readonly bookKey: string;
|
|
@@ -356,25 +354,20 @@ export function issueAdmissionPlacement(
|
|
|
356
354
|
}
|
|
357
355
|
|
|
358
356
|
/**
|
|
359
|
-
* Unique admitted
|
|
357
|
+
* Unique `admitted` section projection of current.json: top-level sessionDirectory/sessionFile
|
|
360
358
|
* (base wire shape). Memory Admitted keeps only the opaque principal — never dual-carry.
|
|
361
359
|
*/
|
|
362
|
-
|
|
363
|
-
|
|
360
|
+
function writeAdmittedRequestPersistence(
|
|
361
|
+
runDirectory: string,
|
|
364
362
|
body: Record<string, unknown>,
|
|
365
363
|
coordinates: { readonly sessionDirectory: string; readonly sessionFile: string },
|
|
366
|
-
):
|
|
364
|
+
): void {
|
|
367
365
|
const { principal: _omitPrincipal, ...rest } = body;
|
|
368
|
-
|
|
366
|
+
writeSectionSync(runDirectory, "admitted", {
|
|
369
367
|
...rest,
|
|
370
368
|
sessionDirectory: coordinates.sessionDirectory,
|
|
371
369
|
sessionFile: coordinates.sessionFile,
|
|
372
|
-
};
|
|
373
|
-
await writeFile(
|
|
374
|
-
admittedRequestPath,
|
|
375
|
-
`${JSON.stringify(projection, null, 2)}\n`,
|
|
376
|
-
"utf8",
|
|
377
|
-
);
|
|
370
|
+
});
|
|
378
371
|
}
|
|
379
372
|
|
|
380
373
|
/**
|
|
@@ -403,7 +396,7 @@ function effectiveModelLedgerFields(
|
|
|
403
396
|
export { homeFromRunDirectory };
|
|
404
397
|
|
|
405
398
|
/**
|
|
406
|
-
* Persist
|
|
399
|
+
* Persist the `invocation` identity section of current.json for the public run.
|
|
407
400
|
* Admission is the sole source for every field; this is the only identity
|
|
408
401
|
* projection and callers never provide an independent ledger shape.
|
|
409
402
|
* When an effective model is known at admission, provider/model (and thinking
|
|
@@ -426,11 +419,7 @@ async function writeRoleInvocationLedger(
|
|
|
426
419
|
...(source.ticketNumber === undefined ? {} : { ticketNumber: source.ticketNumber }),
|
|
427
420
|
...effectiveModelLedgerFields(effectiveModel),
|
|
428
421
|
};
|
|
429
|
-
|
|
430
|
-
join(source.runDirectory, "invocation.json"),
|
|
431
|
-
`${JSON.stringify(identity, null, 2)}\n`,
|
|
432
|
-
"utf8",
|
|
433
|
-
);
|
|
422
|
+
writeSectionSync(source.runDirectory, "invocation", identity);
|
|
434
423
|
}
|
|
435
424
|
|
|
436
425
|
/**
|
|
@@ -450,7 +439,7 @@ export async function recordEffectiveInvocationModel(
|
|
|
450
439
|
host?: string,
|
|
451
440
|
engineModel?: string | null,
|
|
452
441
|
): Promise<void> {
|
|
453
|
-
|
|
442
|
+
updateSectionSync(runDirectory, "invocation", (current) => {
|
|
454
443
|
const next: Record<string, unknown> = { ...current };
|
|
455
444
|
if (model !== undefined) {
|
|
456
445
|
next.provider = model.provider;
|
|
@@ -478,33 +467,19 @@ export async function recordEffectiveInvocationModel(
|
|
|
478
467
|
});
|
|
479
468
|
}
|
|
480
469
|
|
|
481
|
-
/**
|
|
482
|
-
* Read one durable JSON object, let the caller keep its field rules, and write
|
|
483
|
-
* the page back. `undefined` means the caller decided not to change the page.
|
|
484
|
-
*/
|
|
485
|
-
async function updateDurableJsonPage(
|
|
486
|
-
path: string,
|
|
487
|
-
update: (current: Record<string, unknown>) => Record<string, unknown> | undefined,
|
|
488
|
-
): Promise<void> {
|
|
489
|
-
const current = JSON.parse(await readFile(path, "utf8")) as Record<string, unknown>;
|
|
490
|
-
const next = update(current);
|
|
491
|
-
if (next === undefined) return;
|
|
492
|
-
await writeFile(path, `${JSON.stringify(next, null, 2)}\n`, "utf8");
|
|
493
|
-
}
|
|
494
|
-
|
|
495
|
-
/** Merge observed launch-time fields into the single existing invocation.json identity page. */
|
|
470
|
+
/** Merge observed launch-time fields into the single existing invocation section of current.json. */
|
|
496
471
|
async function mergeInvocationIdentityPage(
|
|
497
472
|
runDirectory: string,
|
|
498
473
|
fields: Record<string, unknown>,
|
|
499
474
|
): Promise<void> {
|
|
500
|
-
|
|
475
|
+
updateSectionSync(runDirectory, "invocation", (current) => ({
|
|
501
476
|
...current,
|
|
502
477
|
...fields,
|
|
503
478
|
}));
|
|
504
479
|
}
|
|
505
480
|
|
|
506
481
|
/**
|
|
507
|
-
* Persist parent --source-run path onto admitted
|
|
482
|
+
* Persist parent --source-run path onto the admitted section for officer resume lookup (#747).
|
|
508
483
|
* Reuses the existing notary sourceRunPath key; does not invent a new field name.
|
|
509
484
|
*/
|
|
510
485
|
export async function persistAdmittedSourceRunPath(
|
|
@@ -515,7 +490,7 @@ export async function persistAdmittedSourceRunPath(
|
|
|
515
490
|
if (sourceRunPath.trim() === "") {
|
|
516
491
|
throw new Error("persistAdmittedSourceRunPath requires a non-empty sourceRunPath");
|
|
517
492
|
}
|
|
518
|
-
|
|
493
|
+
updateSectionSync(admitted.runDirectory, "admitted", (current) => {
|
|
519
494
|
if (typeof current.sourceRunPath === "string" && current.sourceRunPath !== sourceRunPath) {
|
|
520
495
|
throw new Error(
|
|
521
496
|
`persistAdmittedSourceRunPath refuses to replace ${current.sourceRunPath} with ${sourceRunPath}`,
|
|
@@ -533,7 +508,7 @@ export async function persistAdmittedSourceRunPath(
|
|
|
533
508
|
|
|
534
509
|
/**
|
|
535
510
|
* Bind a post-admission resolved ticketNumber onto the in-memory admitted
|
|
536
|
-
* object and both durable
|
|
511
|
+
* object and both durable sections (invocation + admitted).
|
|
537
512
|
* A later typed role receipt replaces an earlier assertion (#1025).
|
|
538
513
|
*/
|
|
539
514
|
export async function bindAdmittedTicketNumber(
|
|
@@ -551,7 +526,7 @@ export async function recordAdmittedCorrelation(
|
|
|
551
526
|
correlationId: string,
|
|
552
527
|
): Promise<void> {
|
|
553
528
|
let correlationIds: string[] = [];
|
|
554
|
-
|
|
529
|
+
updateSectionSync(admitted.runDirectory, "admitted", (current) => {
|
|
555
530
|
const prior = [
|
|
556
531
|
...(Array.isArray(current.correlationIds)
|
|
557
532
|
? current.correlationIds.filter((value): value is string =>
|
|
@@ -598,8 +573,7 @@ export async function relocateAdmittedRunToTicket(
|
|
|
598
573
|
const principal = authority.seal(target);
|
|
599
574
|
|
|
600
575
|
if (admitted.role !== "diarist") {
|
|
601
|
-
const
|
|
602
|
-
const childRunIds = parentPage.childDiaristRunIds;
|
|
576
|
+
const childRunIds = readPageSync(oldRunDirectory, "admitted")?.childDiaristRunIds;
|
|
603
577
|
for (const childRunId of Array.isArray(childRunIds) ? childRunIds : []) {
|
|
604
578
|
if (typeof childRunId !== "string") continue;
|
|
605
579
|
const childDirectory = join(dirname(oldRunDirectory), formatRunLeaf(childRunId, "diarist"));
|
|
@@ -616,6 +590,16 @@ export async function relocateAdmittedRunToTicket(
|
|
|
616
590
|
await rehomeUnboundTicketProvenance(childDirectory, admitted.ticketNumber, admitted.projectRoot, homeFromRunDirectory(oldRunDirectory));
|
|
617
591
|
ensureRoleRunDirectory(ledgerHome, dirname(childTarget.runDirectory));
|
|
618
592
|
await rename(childDirectory, childTarget.runDirectory);
|
|
593
|
+
// Finished child legs will not settle again; refresh derived host.original
|
|
594
|
+
// after the rename commit. A derived render fault must not undo or block
|
|
595
|
+
// the committed placement (#1161 L1) — note and keep the true cause.
|
|
596
|
+
try {
|
|
597
|
+
renderCurrentSync(childTarget.runDirectory);
|
|
598
|
+
} catch (error) {
|
|
599
|
+
process.stderr.write(
|
|
600
|
+
`[invocation] current.json render refused after child relocate; placement kept: ${childTarget.runDirectory}: ${errorText(error)}\n`,
|
|
601
|
+
);
|
|
602
|
+
}
|
|
619
603
|
}
|
|
620
604
|
}
|
|
621
605
|
|
|
@@ -629,8 +613,9 @@ export async function relocateAdmittedRunToTicket(
|
|
|
629
613
|
// Persisted paths are resolved from typed run identity on read.
|
|
630
614
|
await rename(oldRunDirectory, target.runDirectory);
|
|
631
615
|
|
|
632
|
-
// rename moved the open lock inode with the directory. Transfer cleanup
|
|
633
|
-
// ownership immediately after the commit
|
|
616
|
+
// rename moved the open lock inode with the directory. Transfer cleanup and
|
|
617
|
+
// admitted path ownership immediately after the commit — before any derived
|
|
618
|
+
// render that may refuse (#1161 L1 / BASE order).
|
|
634
619
|
heldLease?.relocate(target.runDirectory);
|
|
635
620
|
|
|
636
621
|
const admittedRecord = admitted as unknown as Record<string, unknown>;
|
|
@@ -638,7 +623,6 @@ export async function relocateAdmittedRunToTicket(
|
|
|
638
623
|
admittedRecord,
|
|
639
624
|
[
|
|
640
625
|
"runDirectory",
|
|
641
|
-
"admittedRequestPath",
|
|
642
626
|
"taskPath",
|
|
643
627
|
"packetPath",
|
|
644
628
|
"prerequisitesPath",
|
|
@@ -657,6 +641,11 @@ export async function relocateAdmittedRunToTicket(
|
|
|
657
641
|
}
|
|
658
642
|
(admitted as { principal: DurablePrincipal }).principal = principal;
|
|
659
643
|
|
|
644
|
+
// current.json projects host.original from this run directory. Ownership is
|
|
645
|
+
// already transferred; a refuse still carries the true cause to the caller
|
|
646
|
+
// (#1161 L1) without leaving the leg under the unbound path.
|
|
647
|
+
renderCurrentSync(target.runDirectory);
|
|
648
|
+
|
|
660
649
|
return { oldRunDirectory, newRunDirectory: target.runDirectory };
|
|
661
650
|
}
|
|
662
651
|
|
|
@@ -665,7 +654,7 @@ export async function recordChildDiaristRun(
|
|
|
665
654
|
parent: AdmittedRoleInvocation,
|
|
666
655
|
childRunId: string,
|
|
667
656
|
): Promise<void> {
|
|
668
|
-
|
|
657
|
+
updateSectionSync(parent.runDirectory, "admitted", (page) => {
|
|
669
658
|
const existing = Array.isArray(page.childDiaristRunIds)
|
|
670
659
|
? page.childDiaristRunIds.filter((runId): runId is string => typeof runId === "string")
|
|
671
660
|
: [];
|
|
@@ -687,9 +676,8 @@ export async function bindTicketNumberOnRunDirectory(
|
|
|
687
676
|
ticketNumber,
|
|
688
677
|
"bindTicketNumberOnRunDirectory",
|
|
689
678
|
);
|
|
690
|
-
const admittedPath = join(runDirectory, "admitted-request.json");
|
|
691
679
|
let unchanged = false;
|
|
692
|
-
|
|
680
|
+
updateSectionSync(runDirectory, "admitted", (admitted) => {
|
|
693
681
|
if (admitted.ticketNumber === ticketNumber) {
|
|
694
682
|
unchanged = true;
|
|
695
683
|
return undefined;
|
|
@@ -712,7 +700,7 @@ export async function recordLaunchedPiIdentity(
|
|
|
712
700
|
}
|
|
713
701
|
|
|
714
702
|
/**
|
|
715
|
-
* Observed role-package launch provenance written onto the same
|
|
703
|
+
* Observed role-package launch provenance written onto the same state.jsonl invocation row.
|
|
716
704
|
* Values are field observations from the public CLI activation seam — never fixed schema markers.
|
|
717
705
|
*/
|
|
718
706
|
export type LaunchedRolePackageIdentity = {
|
|
@@ -1303,10 +1291,16 @@ async function freezeRegularFileAttachment(
|
|
|
1303
1291
|
|
|
1304
1292
|
/** Freeze attachments only — ticket binding is the shared LLM seat path (#635). */
|
|
1305
1293
|
async function freezeAttachments(
|
|
1294
|
+
ledgerHome: string,
|
|
1306
1295
|
attachmentPaths: readonly string[],
|
|
1307
1296
|
attachmentsDirectory: string,
|
|
1308
1297
|
): Promise<readonly FrozenAttachment[]> {
|
|
1309
1298
|
const attachments: FrozenAttachment[] = [];
|
|
1299
|
+
// attachments/ appears only when something is frozen; created through the
|
|
1300
|
+
// ledger's symlink-rejecting tree walk, never a plain mkdir.
|
|
1301
|
+
if (attachmentPaths.length > 0) {
|
|
1302
|
+
ensureRealDirectoryTree(ledgerHome, attachmentsDirectory);
|
|
1303
|
+
}
|
|
1310
1304
|
for (let i = 0; i < attachmentPaths.length; i += 1) {
|
|
1311
1305
|
const frozen = await freezeRegularFileAttachment(
|
|
1312
1306
|
attachmentPaths[i]!,
|
|
@@ -1332,7 +1326,7 @@ export async function freezeAttachmentsIntoRun(
|
|
|
1332
1326
|
const ledgerHome = resolveActivationLedgerHome(homeFromRunDirectory(runDirectory));
|
|
1333
1327
|
const attachmentsDirectory = join(runDirectory, "attachments", summonsKey);
|
|
1334
1328
|
ensureRealDirectoryTree(ledgerHome, attachmentsDirectory);
|
|
1335
|
-
return freezeAttachments(attachmentPaths, attachmentsDirectory);
|
|
1329
|
+
return freezeAttachments(ledgerHome, attachmentPaths, attachmentsDirectory);
|
|
1336
1330
|
}
|
|
1337
1331
|
|
|
1338
1332
|
/** Freeze prepared bytes and metadata through one path for birth or retained runs. */
|
|
@@ -1673,7 +1667,7 @@ export async function admitPublicRole(
|
|
|
1673
1667
|
{ cause: error },
|
|
1674
1668
|
);
|
|
1675
1669
|
}
|
|
1676
|
-
frozenAttachments = await freezeAttachments(attachmentPaths, placed.attachmentsDirectory);
|
|
1670
|
+
frozenAttachments = await freezeAttachments(placed.ledgerHome, attachmentPaths, placed.attachmentsDirectory);
|
|
1677
1671
|
return {
|
|
1678
1672
|
issueNumber,
|
|
1679
1673
|
caseRunsPath,
|
|
@@ -1832,7 +1826,7 @@ export type AdmitJudgeInvocationOptions = {
|
|
|
1832
1826
|
/** Injectable clock/id for tests. */
|
|
1833
1827
|
createRunId?: () => string;
|
|
1834
1828
|
principalAuthority: DurablePrincipalAuthority;
|
|
1835
|
-
/** Effective model for this invocation — written onto invocation.
|
|
1829
|
+
/** Effective model for this invocation — written onto the state.jsonl invocation row. */
|
|
1836
1830
|
model?: InvocationEffectiveModel;
|
|
1837
1831
|
/** Typed ticket already on this summons (起居录 / parent board). Never parsed from prose. */
|
|
1838
1832
|
assertedTicketNumber?: number;
|
|
@@ -1904,7 +1898,7 @@ async function placeRoleAdmission(options: {
|
|
|
1904
1898
|
});
|
|
1905
1899
|
const attachments = options.freezeAttachments === false
|
|
1906
1900
|
? []
|
|
1907
|
-
: await freezeAttachments(options.attachmentPaths, attachmentsDirectory);
|
|
1901
|
+
: await freezeAttachments(ledgerHome, options.attachmentPaths, attachmentsDirectory);
|
|
1908
1902
|
return {
|
|
1909
1903
|
runId,
|
|
1910
1904
|
bookKey,
|
|
@@ -1954,9 +1948,8 @@ async function persistPlacedAdmission(
|
|
|
1954
1948
|
},
|
|
1955
1949
|
placed: PlacedRoleAdmission,
|
|
1956
1950
|
model: InvocationEffectiveModel | undefined,
|
|
1957
|
-
): Promise<
|
|
1958
|
-
|
|
1959
|
-
await writeAdmittedRequestPersistence(admittedRequestPath, admitted, {
|
|
1951
|
+
): Promise<void> {
|
|
1952
|
+
writeAdmittedRequestPersistence(placed.runDirectory, admitted, {
|
|
1960
1953
|
sessionDirectory: placed.sessionDirectory,
|
|
1961
1954
|
sessionFile: placed.sessionFile,
|
|
1962
1955
|
});
|
|
@@ -1969,12 +1962,11 @@ async function persistPlacedAdmission(
|
|
|
1969
1962
|
admitted.role,
|
|
1970
1963
|
model,
|
|
1971
1964
|
);
|
|
1972
|
-
return admittedRequestPath;
|
|
1973
1965
|
}
|
|
1974
1966
|
|
|
1975
1967
|
/**
|
|
1976
1968
|
* Shared admission: project check, placement, attachment freeze,
|
|
1977
|
-
* admitted
|
|
1969
|
+
* admitted and invocation section write.
|
|
1978
1970
|
* Countersign passes deferPersistence so same-ticket lookup can reserve coordinates
|
|
1979
1971
|
* before materializeCountersignInvocation writes the page.
|
|
1980
1972
|
* Seats whose extra facts are known before placement pass them as admittedFields.
|
|
@@ -1988,7 +1980,7 @@ async function admitStandardMaterialInvocation<
|
|
|
1988
1980
|
>(
|
|
1989
1981
|
role: R,
|
|
1990
1982
|
options: AdmitInspectorInvocationOptions & {
|
|
1991
|
-
/** Reserve coordinates; skip freeze, placement disk, and admitted
|
|
1983
|
+
/** Reserve coordinates; skip freeze, placement disk, and admitted section write. */
|
|
1992
1984
|
readonly deferPersistence?: boolean;
|
|
1993
1985
|
/** Skip attachment freeze. Gleaner-left admits no caller attachments. */
|
|
1994
1986
|
readonly freezeAttachments?: boolean;
|
|
@@ -2039,9 +2031,7 @@ async function admitStandardMaterialInvocation<
|
|
|
2039
2031
|
attachments: persistedAttachmentRefs(placed.attachments),
|
|
2040
2032
|
...placed.ticketFields,
|
|
2041
2033
|
};
|
|
2042
|
-
|
|
2043
|
-
? join(placed.runDirectory, "admitted-request.json")
|
|
2044
|
-
: await persistPlacedAdmission(admitted, placed, options.model);
|
|
2034
|
+
if (!defer) await persistPlacedAdmission(admitted, placed, options.model);
|
|
2045
2035
|
return {
|
|
2046
2036
|
role,
|
|
2047
2037
|
runId: placed.runId,
|
|
@@ -2052,7 +2042,6 @@ async function admitStandardMaterialInvocation<
|
|
|
2052
2042
|
attachments: placed.attachments,
|
|
2053
2043
|
runDirectory: placed.runDirectory,
|
|
2054
2044
|
principal: placed.principal,
|
|
2055
|
-
admittedRequestPath,
|
|
2056
2045
|
...correlationFields,
|
|
2057
2046
|
...placed.ticketFields,
|
|
2058
2047
|
...admittedFields,
|
|
@@ -2145,7 +2134,7 @@ export type AdmitCountersignInvocationOptions = {
|
|
|
2145
2134
|
/** Injectable clock/id for tests. */
|
|
2146
2135
|
createRunId?: () => string;
|
|
2147
2136
|
principalAuthority: DurablePrincipalAuthority;
|
|
2148
|
-
/** Effective model for this invocation — written onto invocation.
|
|
2137
|
+
/** Effective model for this invocation — written onto the state.jsonl invocation row. */
|
|
2149
2138
|
model?: InvocationEffectiveModel;
|
|
2150
2139
|
correlationId?: string;
|
|
2151
2140
|
/** Same-ticket lookup may select an existing run before a new run is persisted. */
|
|
@@ -2175,10 +2164,6 @@ export async function materializeCountersignInvocation(
|
|
|
2175
2164
|
});
|
|
2176
2165
|
const placement = placed;
|
|
2177
2166
|
(admitted as { runDirectory: string }).runDirectory = placement.runDirectory;
|
|
2178
|
-
(admitted as { admittedRequestPath: string }).admittedRequestPath = join(
|
|
2179
|
-
placement.runDirectory,
|
|
2180
|
-
"admitted-request.json",
|
|
2181
|
-
);
|
|
2182
2167
|
(admitted as { principal: DurablePrincipal }).principal = placement.principal;
|
|
2183
2168
|
const ticketFields = ticketAdmissionFields(options.ticketNumber);
|
|
2184
2169
|
if (ticketFields.ticketNumber !== undefined) {
|
|
@@ -2189,7 +2174,7 @@ export async function materializeCountersignInvocation(
|
|
|
2189
2174
|
admitted.runDirectory,
|
|
2190
2175
|
);
|
|
2191
2176
|
(admitted as { attachments: readonly FrozenAttachment[] }).attachments = attachments;
|
|
2192
|
-
|
|
2177
|
+
writeAdmittedRequestPersistence(admitted.runDirectory, admitted, {
|
|
2193
2178
|
sessionDirectory: placement.sessionDirectory,
|
|
2194
2179
|
sessionFile: placement.sessionFile,
|
|
2195
2180
|
});
|
|
@@ -2200,7 +2185,7 @@ export async function materializeCountersignInvocation(
|
|
|
2200
2185
|
);
|
|
2201
2186
|
}
|
|
2202
2187
|
|
|
2203
|
-
/** Load admitted
|
|
2188
|
+
/** Load the admitted section written at admission (Navigator work-context seam). */
|
|
2204
2189
|
export async function loadAdmittedJudgeRequest(
|
|
2205
2190
|
runDirectory: string,
|
|
2206
2191
|
): Promise<{
|
|
@@ -2209,11 +2194,8 @@ export async function loadAdmittedJudgeRequest(
|
|
|
2209
2194
|
attachments: readonly FrozenAttachment[];
|
|
2210
2195
|
} | undefined> {
|
|
2211
2196
|
try {
|
|
2212
|
-
const
|
|
2213
|
-
|
|
2214
|
-
) as unknown;
|
|
2215
|
-
if (!isRecord(raw)) return undefined;
|
|
2216
|
-
const record = raw;
|
|
2197
|
+
const record = readPageSync(runDirectory, "admitted");
|
|
2198
|
+
if (record === undefined) return undefined;
|
|
2217
2199
|
if (!packagedPublicInstructionSubject(record.role)) return undefined;
|
|
2218
2200
|
if (typeof record.instruction !== "string") return undefined;
|
|
2219
2201
|
if (typeof record.instructionEmpty !== "boolean") return undefined;
|
|
@@ -2228,14 +2210,6 @@ export async function loadAdmittedJudgeRequest(
|
|
|
2228
2210
|
}
|
|
2229
2211
|
}
|
|
2230
2212
|
|
|
2231
|
-
export async function ensureRunArtifactsDir(runDirectory: string): Promise<string> {
|
|
2232
|
-
const directory = roleRunArtifactsDirectory(runDirectory);
|
|
2233
|
-
return ensureRoleRunDirectory(
|
|
2234
|
-
resolveActivationLedgerHome(homeFromRunDirectory(runDirectory)),
|
|
2235
|
-
directory,
|
|
2236
|
-
);
|
|
2237
|
-
}
|
|
2238
|
-
|
|
2239
2213
|
export type AdmitFixerInvocationOptions = {
|
|
2240
2214
|
home: string;
|
|
2241
2215
|
principalAuthority: DurablePrincipalAuthority;
|
|
@@ -2247,7 +2221,7 @@ export type AdmitFixerInvocationOptions = {
|
|
|
2247
2221
|
prerequisitesPath?: string;
|
|
2248
2222
|
project?: string;
|
|
2249
2223
|
createRunId?: () => string;
|
|
2250
|
-
/** Effective model for this invocation — written onto invocation.
|
|
2224
|
+
/** Effective model for this invocation — written onto the state.jsonl invocation row. */
|
|
2251
2225
|
model?: InvocationEffectiveModel;
|
|
2252
2226
|
/** Typed ticket already on this summons. Placement uses it; code does not infer one. */
|
|
2253
2227
|
assertedTicketNumber?: number;
|
|
@@ -2402,7 +2376,7 @@ export type AdmitReviewerInvocationOptions = {
|
|
|
2402
2376
|
project?: string;
|
|
2403
2377
|
createRunId?: () => string;
|
|
2404
2378
|
correlationId?: string;
|
|
2405
|
-
/** Effective model for this invocation — written onto invocation.
|
|
2379
|
+
/** Effective model for this invocation — written onto the state.jsonl invocation row. */
|
|
2406
2380
|
model?: InvocationEffectiveModel;
|
|
2407
2381
|
/** Typed ticket already on this summons. Placement uses it; code does not infer one. */
|
|
2408
2382
|
assertedTicketNumber?: number;
|
|
@@ -2455,7 +2429,7 @@ export type AdmitMergerInvocationOptions = {
|
|
|
2455
2429
|
attachmentPaths: readonly string[];
|
|
2456
2430
|
project?: string;
|
|
2457
2431
|
createRunId?: () => string;
|
|
2458
|
-
/** Effective model for this invocation — written onto invocation.
|
|
2432
|
+
/** Effective model for this invocation — written onto the state.jsonl invocation row. */
|
|
2459
2433
|
model?: InvocationEffectiveModel;
|
|
2460
2434
|
/** Typed ticket already on this summons. Placement uses it; code does not infer one. */
|
|
2461
2435
|
assertedTicketNumber?: number;
|