@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
|
@@ -58,7 +58,7 @@ async function requireRunDirectory(candidate, display) {
|
|
|
58
58
|
* - absolute/relative path that realpath's to that same book runs slot
|
|
59
59
|
*
|
|
60
60
|
* Rejects project-tree projections and any path outside the project's ledger book runs.
|
|
61
|
-
* Retained
|
|
61
|
+
* Retained state.jsonl run-state must match basename identity and book binding.
|
|
62
62
|
*/
|
|
63
63
|
export async function resolveNotarySourceRunLocator(options) {
|
|
64
64
|
const raw = options.sourceRun.trim();
|
|
@@ -88,14 +88,7 @@ const PUBLIC_ROLE_RECORDS = [
|
|
|
88
88
|
outputTool: FIXER_OUTPUT_TOOL_NAME,
|
|
89
89
|
settlement: "sealed",
|
|
90
90
|
artifactFace: {
|
|
91
|
-
reportPhase: true
|
|
92
|
-
evidenceRole: true,
|
|
93
|
-
leaves: [
|
|
94
|
-
{ key: "phase" },
|
|
95
|
-
{ key: "packetPath" },
|
|
96
|
-
{ key: "prerequisitesPath", omitUndefined: true },
|
|
97
|
-
{ key: "prerequisites" }
|
|
98
|
-
]
|
|
91
|
+
reportPhase: true
|
|
99
92
|
},
|
|
100
93
|
boardPlacement: "marshal",
|
|
101
94
|
activationFlags: [
|
|
@@ -126,12 +119,7 @@ const PUBLIC_ROLE_RECORDS = [
|
|
|
126
119
|
outputTool: CODER_OUTPUT_TOOL_NAME,
|
|
127
120
|
settlement: "sealed",
|
|
128
121
|
artifactFace: {
|
|
129
|
-
reportPhase: true
|
|
130
|
-
evidenceRole: true,
|
|
131
|
-
leaves: [
|
|
132
|
-
{ key: "phase" },
|
|
133
|
-
{ key: "taskPath" }
|
|
134
|
-
]
|
|
122
|
+
reportPhase: true
|
|
135
123
|
},
|
|
136
124
|
activationFlags: [
|
|
137
125
|
{ field: "taskPath", flag: "ak-coder-task", binds: "input" },
|
|
@@ -165,15 +153,7 @@ const PUBLIC_ROLE_RECORDS = [
|
|
|
165
153
|
settlement: "sealed",
|
|
166
154
|
/** Publish only an accepted ledger outcome; audit escalation stays unsettled here. */
|
|
167
155
|
sealedAcceptedOnly: true,
|
|
168
|
-
artifactFace: {
|
|
169
|
-
evidenceRole: true,
|
|
170
|
-
leaves: [
|
|
171
|
-
{ key: "baseRevision" },
|
|
172
|
-
{ key: "lens" },
|
|
173
|
-
{ key: "authorityRefs", copyArray: true },
|
|
174
|
-
{ key: "callerProvenance", callerProvenance: true }
|
|
175
|
-
]
|
|
176
|
-
},
|
|
156
|
+
artifactFace: {},
|
|
177
157
|
/** Frozen base/lens/authority become the initial prompt; instruction follows. */
|
|
178
158
|
transportPrompt: "skill-args",
|
|
179
159
|
boardPlacement: "marshal",
|
|
@@ -204,14 +184,7 @@ const PUBLIC_ROLE_RECORDS = [
|
|
|
204
184
|
bareCommand: false,
|
|
205
185
|
outputTool: COLLECTOR_OUTPUT_TOOL,
|
|
206
186
|
settlement: "sealed",
|
|
207
|
-
artifactFace: {
|
|
208
|
-
evidenceRole: true,
|
|
209
|
-
leaves: [
|
|
210
|
-
{ key: "prNumber", omitUndefined: true },
|
|
211
|
-
{ key: "repository", from: "repository.canonical" },
|
|
212
|
-
{ key: "manifestDigest" }
|
|
213
|
-
]
|
|
214
|
-
},
|
|
187
|
+
artifactFace: {},
|
|
215
188
|
boardPlacement: "collector",
|
|
216
189
|
/** Taishi acceptance is a typed groups array, not a status leaf. */
|
|
217
190
|
analystTerminal: "groups",
|
|
@@ -240,12 +213,6 @@ const PUBLIC_ROLE_RECORDS = [
|
|
|
240
213
|
auditTool: DOCTOR_AUDIT_TOOL_NAME,
|
|
241
214
|
settlement: "sealed",
|
|
242
215
|
artifactFace: {
|
|
243
|
-
evidenceRole: true,
|
|
244
|
-
leaves: [
|
|
245
|
-
{ key: "issueNumber" },
|
|
246
|
-
{ key: "caseRunsPath" },
|
|
247
|
-
{ key: "caseIdentity" }
|
|
248
|
-
],
|
|
249
216
|
doctorReportFacts: true
|
|
250
217
|
},
|
|
251
218
|
/** Navigator subject is the doctor case, not the case-path file bytes. */
|
|
@@ -271,13 +238,7 @@ const PUBLIC_ROLE_RECORDS = [
|
|
|
271
238
|
phases: [null],
|
|
272
239
|
outputTool: MERGER_OUTPUT_TOOL_NAME,
|
|
273
240
|
settlement: "sealed",
|
|
274
|
-
artifactFace: {
|
|
275
|
-
evidenceRole: true,
|
|
276
|
-
leaves: [
|
|
277
|
-
{ key: "mergerInputPath" },
|
|
278
|
-
{ key: "derived" }
|
|
279
|
-
]
|
|
280
|
-
},
|
|
241
|
+
artifactFace: {},
|
|
281
242
|
activationFlags: [
|
|
282
243
|
{ field: "inputPath", from: "mergerInputPath", flag: "ak-merger-input", binds: "input" }
|
|
283
244
|
],
|
|
@@ -14,10 +14,13 @@ import { ExplicitInternalActivationError } from "../host-contracts.js";
|
|
|
14
14
|
import { applyEngineChildEnv, ENGINE_MODEL_FLAG_NAME, normalizeEngineName } from "../engine-detour.js";
|
|
15
15
|
import { projectActivationFlags } from "../role-activation-flags.js";
|
|
16
16
|
import { encodeUserDialogueStdin } from "../user-dialogue-stdin.js";
|
|
17
|
-
import { readStrictPiSessionJsonl } from "../ledger-session-read.js";
|
|
17
|
+
import { readLedgerSessionJsonlLines, readStrictPiSessionJsonl } from "../ledger-session-read.js";
|
|
18
|
+
import { copyAndRecordHostDossier } from "../host-session-record.js";
|
|
18
19
|
import { projectThrownFailureLeaf, retainPackageFault } from "../public-cli/settlement.js";
|
|
19
20
|
import { describeErrorIdentity } from "../public-cli/run-lifecycle.js";
|
|
20
21
|
import { RECEIPT_DELIVERY_LIMIT_ENV } from "../receipt-delivery-policy.js";
|
|
22
|
+
import { createSessionIdentityAuthority } from "../session-identity.js";
|
|
23
|
+
import { sitianReport } from "../sitian-facade.js";
|
|
21
24
|
/** Package-relative Internal role entrypoint (ADR 0052; same path as public-cli registry). */
|
|
22
25
|
const INTERNAL_ROLE_ENTRYPOINT_RELATIVE = "extensions/role-runtime.ts";
|
|
23
26
|
export function resolveInternalRoleEntrypoint(packageRoot) {
|
|
@@ -351,17 +354,64 @@ export function createPiRoleTurnHost(config) {
|
|
|
351
354
|
await config.recordLaunchedRolePackageIdentity(request.runDirectory, await config.observeLaunchedRolePackageIdentity(config.packageRoot, roleEntry));
|
|
352
355
|
}
|
|
353
356
|
const timeoutMs = request.timeoutMs ?? config.timeoutMs;
|
|
354
|
-
|
|
357
|
+
const result = await spawnRunner(args, {
|
|
355
358
|
cwd: request.cwd,
|
|
356
359
|
env,
|
|
357
360
|
stdin,
|
|
358
361
|
...(timeoutMs === undefined ? {} : { timeoutMs }),
|
|
359
362
|
...(request.signal === undefined ? {} : { signal: request.signal }),
|
|
360
363
|
});
|
|
364
|
+
// Deliver already-written Pi host facts. Pi lands at session/session.jsonl;
|
|
365
|
+
// bind projects host.sessions; copy records the landing. Host adapters do
|
|
366
|
+
// not write current.json — public bind/settlement seams own that render.
|
|
367
|
+
try {
|
|
368
|
+
const { sessionFile, sessionDirectory } = config.principalAuthority.decode(request.principal);
|
|
369
|
+
const sessionId = await readPiSessionHeaderId(sessionFile);
|
|
370
|
+
if (sessionId !== undefined) {
|
|
371
|
+
await createSessionIdentityAuthority(config.principalAuthority, "pi").bind(request.principal, sessionId);
|
|
372
|
+
copyAndRecordHostDossier({
|
|
373
|
+
host: "pi",
|
|
374
|
+
sessionId,
|
|
375
|
+
cwd: request.cwd,
|
|
376
|
+
sessionDirectory,
|
|
377
|
+
sessionParent: sessionFile,
|
|
378
|
+
...(request.home !== undefined ? { home: request.home } : {}),
|
|
379
|
+
});
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
catch (error) {
|
|
383
|
+
await retainPackageFault({
|
|
384
|
+
runDirectory: request.runDirectory,
|
|
385
|
+
diagnostic: `pi host dossier record failed beside host terminal: ${describeErrorIdentity(error)}`,
|
|
386
|
+
error,
|
|
387
|
+
});
|
|
388
|
+
}
|
|
389
|
+
return result;
|
|
361
390
|
},
|
|
362
391
|
};
|
|
363
392
|
}
|
|
364
|
-
|
|
393
|
+
/**
|
|
394
|
+
* Session header id from the Pi volume the child (or fixture) already wrote.
|
|
395
|
+
* Reuses the ledger line kernel: a later malformed line must not erase a valid
|
|
396
|
+
* header already present (ADR 0086 originals are not validated whole-file).
|
|
397
|
+
*/
|
|
398
|
+
async function readPiSessionHeaderId(sessionFile) {
|
|
399
|
+
try {
|
|
400
|
+
for (const line of await readLedgerSessionJsonlLines(sessionFile)) {
|
|
401
|
+
const entry = line.row;
|
|
402
|
+
if (entry === undefined || entry.type !== "session")
|
|
403
|
+
continue;
|
|
404
|
+
if (typeof entry.id === "string" && entry.id.trim() !== "")
|
|
405
|
+
return entry.id;
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
catch (error) {
|
|
409
|
+
if (error.code === "ENOENT")
|
|
410
|
+
return undefined;
|
|
411
|
+
throw error;
|
|
412
|
+
}
|
|
413
|
+
return undefined;
|
|
414
|
+
}
|
|
365
415
|
/** Last non-session entry id. Session headers are not parents of custom lines. */
|
|
366
416
|
export function piSessionCustomParentId(entries) {
|
|
367
417
|
let parentId = null;
|
|
@@ -1,27 +1,10 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Single generic auto-resume loop for #416 (owner scope = single LLM call).
|
|
3
|
-
* Call-local retries, at most the effective autoResumeLimit times (injected once
|
|
4
|
-
* per call by the caller, #422 — never re-read from disk inside the loop),
|
|
5
|
-
* in-place (same runId/session).
|
|
6
|
-
* Unifies presentation: intermediate attempts use dummyIo, only final Terminal is presented.
|
|
7
|
-
*
|
|
8
|
-
* Owner 2026-08-23: a dispatch that exits by throwing used to bypass the entire
|
|
9
|
-
* retry mechanism (the throw escaped the while-loop before the count check ever
|
|
10
|
-
* ran). Every exception is now retained whole, in place, and the ordinary retry
|
|
11
|
-
* path continues: same budget, same call-local count semantics. No failure-type
|
|
12
|
-
* classification — every thrown value is treated identically.
|
|
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
1
|
import { AUTO_RESUME_LIMIT, describeErrorIdentity, acquireRunWriterLease, markRunTerminal, RunWriterLeaseHeldError, } from "./run-lifecycle.js";
|
|
19
2
|
import { parseAutoResumeLimit } from "./config.js";
|
|
20
3
|
import { processCancelSignalName } from "./process-cancel.js";
|
|
21
4
|
import { isLawfulTypedTerminalOutcome, formatTerminalResult } from "./terminal.js";
|
|
22
|
-
import { attachRecordedSubmissions,
|
|
23
|
-
export { ensureRealArtifactsDirectory };
|
|
5
|
+
import { attachRecordedSubmissions, presentFailureTerminal, retainPackageFault, } from "./settlement.js";
|
|
24
6
|
import { serializeThrownValue } from "../serialize-thrown-value.js";
|
|
7
|
+
import { reportRunRecord } from "../sitian-facade.js";
|
|
25
8
|
import { errorText } from "../unknown-value.js";
|
|
26
9
|
const dummyIo = { stdout: () => { }, stderr: () => { } };
|
|
27
10
|
/**
|
|
@@ -105,41 +88,19 @@ function jsonSafeReplacer() {
|
|
|
105
88
|
};
|
|
106
89
|
}
|
|
107
90
|
/**
|
|
108
|
-
* Retain one throwing dispatch attempt's complete exception as
|
|
109
|
-
*
|
|
110
|
-
*
|
|
111
|
-
*
|
|
112
|
-
* attempt can never overwrite an earlier attempt's file.
|
|
91
|
+
* Retain one throwing dispatch attempt's complete exception as a line in the
|
|
92
|
+
* run's log.jsonl, then leave an addressable pointer in the session principal
|
|
93
|
+
* (custom entry). The log only appends (史必追加, #419): a later attempt can
|
|
94
|
+
* never overwrite an earlier attempt's record.
|
|
113
95
|
*/
|
|
114
|
-
/**
|
|
115
|
-
* Hardened create-once JSON write shared by every durable artifact this loop
|
|
116
|
-
* retains directly (dispatch-error dumps, lawful-persist-failure error/
|
|
117
|
-
* evidence records): O_EXCL (fail loud on a colliding name, never overwrite)
|
|
118
|
-
* + O_NOFOLLOW where the platform provides it (a planted symlink is never
|
|
119
|
-
* followed) — mirrors settlement.ts's own hardened artifact writers.
|
|
120
|
-
*/
|
|
121
|
-
export async function writeHardenedArtifactFile(artifactsDir, namePrefix, payload) {
|
|
122
|
-
const filePath = join(artifactsDir, `${namePrefix}-${randomUUID()}.json`);
|
|
123
|
-
const body = `${JSON.stringify(payload, jsonSafeReplacer(), 2)}\n`;
|
|
124
|
-
const noFollowFlag = typeof fsConstants.O_NOFOLLOW === "number" ? fsConstants.O_NOFOLLOW : 0;
|
|
125
|
-
const handle = await open(filePath, fsConstants.O_WRONLY | fsConstants.O_CREAT | fsConstants.O_EXCL | noFollowFlag, 0o600);
|
|
126
|
-
try {
|
|
127
|
-
await handle.writeFile(body, "utf8");
|
|
128
|
-
}
|
|
129
|
-
finally {
|
|
130
|
-
await handle.close();
|
|
131
|
-
}
|
|
132
|
-
return filePath;
|
|
133
|
-
}
|
|
134
96
|
async function retainDispatchError(admitted, principalAuthority, sessionAppender, attempt, error) {
|
|
135
|
-
const artifactsDir = await ensureRealArtifactsDirectory(admitted.runDirectory);
|
|
136
97
|
// Whole-object dump: everything the thrown value carries, nothing picked.
|
|
137
|
-
const filePath =
|
|
98
|
+
const filePath = reportRunRecord(admitted.runDirectory, "dispatch-exception", JSON.parse(JSON.stringify({
|
|
138
99
|
version: 1,
|
|
139
100
|
attempt,
|
|
140
101
|
recordedAt: new Date().toISOString(),
|
|
141
102
|
error: serializeThrownValue(error),
|
|
142
|
-
});
|
|
103
|
+
}, jsonSafeReplacer())), "auto-resume").recordFile;
|
|
143
104
|
// Addressable pointer in the dossier (卷宗): Pi session custom-entry codec
|
|
144
105
|
// (appendPiSessionCustomEntry). Lease still owned here with run-writer.
|
|
145
106
|
let pointerLease;
|
|
@@ -29,7 +29,7 @@ import { packagedAdmitsCountersign, packagedBindsBoardTicket, packagedRebindSour
|
|
|
29
29
|
import { readAuditorResumeBinding } from "../auditor-soul.js";
|
|
30
30
|
import { CliUsageError } from "./cli-errors.js";
|
|
31
31
|
import { admitPublicRole, bindAdmittedTicketNumber, buildInstructionTransportPrompt, materializeCountersignInvocation, persistAdmittedSourceRunPath, recordAdmittedCorrelation, recordChildDiaristRun, relocateAdmittedRunToTicket, withPreparedAttachments, } from "./invocation.js";
|
|
32
|
-
import { presentControlledFailure, packageFaultNoteFor, prepareSummonsResumeMaterials, roleTurnOptions, runPostAdmissionOneShot, runPostAdmissionResumable, runPostAdmissionSeatResume, showResumeErrorPointer, resumeTurnRequestProjectionOptions, } from "./post-admission.js";
|
|
32
|
+
import { bindDiaristAssertedTicketNumber, presentControlledFailure, packageFaultNoteFor, prepareSummonsResumeMaterials, roleTurnOptions, runPostAdmissionOneShot, runPostAdmissionResumable, runPostAdmissionSeatResume, showResumeErrorPointer, resumeTurnRequestProjectionOptions, } from "./post-admission.js";
|
|
33
33
|
import { presentTerminal } from "./auto-resume.js";
|
|
34
34
|
import { buildAutoResumeContinuationPrompt, loadResumablePublicRole, markRunAdmitted, parentRunPathFromGatePointerInstruction, readCurrentCourt, } from "./run-lifecycle.js";
|
|
35
35
|
import { tryResumeSameTicketSeatRun } from "./seat-ticket-binding.js";
|
|
@@ -184,6 +184,8 @@ function isBoardTicketSeat(seat) {
|
|
|
184
184
|
return packagedBindsBoardTicket(seat.role);
|
|
185
185
|
}
|
|
186
186
|
async function bindAndRelocateDiarist(admitted, authority, lease) {
|
|
187
|
+
// The diarist's asserted ticketNumber binds here, in the public call's process.
|
|
188
|
+
await bindDiaristAssertedTicketNumber(admitted);
|
|
187
189
|
const boardTicket = await readBoardTicketNumber(admitted.runDirectory);
|
|
188
190
|
if (boardTicket === undefined)
|
|
189
191
|
return undefined;
|
|
@@ -856,7 +858,7 @@ export async function continueParentAfterChild(parentRunId, child, env, io) {
|
|
|
856
858
|
if (stopped.exitCode === 0
|
|
857
859
|
&& heldUnreadableTerminal(stopped.terminal)
|
|
858
860
|
&& stopped.terminal !== undefined) {
|
|
859
|
-
const parentTerminal = await trySettlePublicSeat(admitted, env.principalAuthority,
|
|
861
|
+
const parentTerminal = await trySettlePublicSeat(admitted, env.principalAuthority, { ...await readCurrentCourt(admitted.runDirectory), ...packageFaultScope(admitted, env, io) });
|
|
860
862
|
if (parentTerminal?.roleOutcome.kind === "accepted"
|
|
861
863
|
|| parentTerminal?.roleOutcome.kind === "audit_escalation") {
|
|
862
864
|
const terminal = await presentUnsettledAudit(parentTerminal, stopped.terminal, io, admitted.runDirectory);
|
|
@@ -870,7 +872,7 @@ export async function continueParentAfterChild(parentRunId, child, env, io) {
|
|
|
870
872
|
}
|
|
871
873
|
if (resolved !== undefined && (resolved.status === "continue" || resolved.status === "converged")) {
|
|
872
874
|
if (AUDITED_ROLES.has(admitted.role) && resolved.status === "converged") {
|
|
873
|
-
const terminal = await trySettlePublicSeat(admitted, env.principalAuthority, packageFaultScope(admitted, env, io));
|
|
875
|
+
const terminal = await trySettlePublicSeat(admitted, env.principalAuthority, { ...await readCurrentCourt(admitted.runDirectory), ...packageFaultScope(admitted, env, io) });
|
|
874
876
|
if (terminal?.roleOutcome.kind !== "accepted")
|
|
875
877
|
throw new Error("pending submission is not recorded");
|
|
876
878
|
return auditSubmittedRole({ exitCode: 0, admitted, terminal }, env, io, resolved.admitted, latestQueuePayload(resolved.terminal));
|
|
@@ -9,10 +9,11 @@ import { tmpdir } from "node:os";
|
|
|
9
9
|
import { basename, dirname, isAbsolute, join, resolve, sep } from "node:path";
|
|
10
10
|
import { activationBookDirectory, ensureRealDirectoryTree, homeFromRunDirectory, pathContainedIn, resolveActivationLedgerHome, } from "../activation-ledger-topology.js";
|
|
11
11
|
import { resolveBookKeyFromGit } from "../activation-ledger-git.js";
|
|
12
|
-
import { ensureRoleRunDirectory, ensureRoleRunPlacement, formatRunLeaf, isUnboundRunDirectory, listBookRunDirectories,
|
|
12
|
+
import { ensureRoleRunDirectory, ensureRoleRunPlacement, formatRunLeaf, isUnboundRunDirectory, listBookRunDirectories, roleRunPlacement, } from "../role-run-placement.js";
|
|
13
13
|
import { packagedAdmittedSubject, packagedArgvResult, packagedBareToken, packagedBaseIsRevision, packagedEmitsAuthorityRefs, packagedPublicInstructionSubject, packagedRoleMetadata, packagedStoresSourceRunRaw, packagedSubjectChoices, } from "../packaged-role-registry.js";
|
|
14
14
|
import { isSafePositiveTicketNumber, parseTicketNumber, readBoardTicketNumber, requireSafePositiveTicketNumber, } from "../run-ticket-number.js";
|
|
15
15
|
import { rewriteRunDirectoryPathFields, rewriteRunDirectoryPathValue, } from "../role-run-relocation.js";
|
|
16
|
+
import { readPageSync, renderCurrentSync, updateSectionSync, writeSectionSync } from "../run-dossier.js";
|
|
16
17
|
import { loadDoctorCase, } from "../doctor-evidence.js";
|
|
17
18
|
import { emptyCollectorManifest, loadCollectorManifest, parseCollectorPrNumber, parseCollectorRepository, } from "../collector-config.js";
|
|
18
19
|
import { ownerRepoFromGitHubRemoteUrl } from "./github-remote.js";
|
|
@@ -48,17 +49,16 @@ export function issueAdmissionPlacement(authority, request) {
|
|
|
48
49
|
};
|
|
49
50
|
}
|
|
50
51
|
/**
|
|
51
|
-
* Unique admitted
|
|
52
|
+
* Unique `admitted` section projection of current.json: top-level sessionDirectory/sessionFile
|
|
52
53
|
* (base wire shape). Memory Admitted keeps only the opaque principal — never dual-carry.
|
|
53
54
|
*/
|
|
54
|
-
|
|
55
|
+
function writeAdmittedRequestPersistence(runDirectory, body, coordinates) {
|
|
55
56
|
const { principal: _omitPrincipal, ...rest } = body;
|
|
56
|
-
|
|
57
|
+
writeSectionSync(runDirectory, "admitted", {
|
|
57
58
|
...rest,
|
|
58
59
|
sessionDirectory: coordinates.sessionDirectory,
|
|
59
60
|
sessionFile: coordinates.sessionFile,
|
|
60
|
-
};
|
|
61
|
-
await writeFile(admittedRequestPath, `${JSON.stringify(projection, null, 2)}\n`, "utf8");
|
|
61
|
+
});
|
|
62
62
|
}
|
|
63
63
|
/** Project effective model onto ledger fields; absent thinking stays absent. */
|
|
64
64
|
function effectiveModelLedgerFields(model) {
|
|
@@ -72,7 +72,7 @@ function effectiveModelLedgerFields(model) {
|
|
|
72
72
|
}
|
|
73
73
|
export { homeFromRunDirectory };
|
|
74
74
|
/**
|
|
75
|
-
* Persist
|
|
75
|
+
* Persist the `invocation` identity section of current.json for the public run.
|
|
76
76
|
* Admission is the sole source for every field; this is the only identity
|
|
77
77
|
* projection and callers never provide an independent ledger shape.
|
|
78
78
|
* When an effective model is known at admission, provider/model (and thinking
|
|
@@ -91,7 +91,7 @@ async function writeRoleInvocationLedger(source, role, effectiveModel) {
|
|
|
91
91
|
...(source.ticketNumber === undefined ? {} : { ticketNumber: source.ticketNumber }),
|
|
92
92
|
...effectiveModelLedgerFields(effectiveModel),
|
|
93
93
|
};
|
|
94
|
-
|
|
94
|
+
writeSectionSync(source.runDirectory, "invocation", identity);
|
|
95
95
|
}
|
|
96
96
|
/**
|
|
97
97
|
* Merge the effective launch model / engine / host onto the existing invocation
|
|
@@ -104,7 +104,7 @@ async function writeRoleInvocationLedger(source, role, effectiveModel) {
|
|
|
104
104
|
* Host stays write-if-present (`string` only).
|
|
105
105
|
*/
|
|
106
106
|
export async function recordEffectiveInvocationModel(runDirectory, model, engine, host, engineModel) {
|
|
107
|
-
|
|
107
|
+
updateSectionSync(runDirectory, "invocation", (current) => {
|
|
108
108
|
const next = { ...current };
|
|
109
109
|
if (model !== undefined) {
|
|
110
110
|
next.provider = model.provider;
|
|
@@ -134,33 +134,22 @@ export async function recordEffectiveInvocationModel(runDirectory, model, engine
|
|
|
134
134
|
return next;
|
|
135
135
|
});
|
|
136
136
|
}
|
|
137
|
-
/**
|
|
138
|
-
* Read one durable JSON object, let the caller keep its field rules, and write
|
|
139
|
-
* the page back. `undefined` means the caller decided not to change the page.
|
|
140
|
-
*/
|
|
141
|
-
async function updateDurableJsonPage(path, update) {
|
|
142
|
-
const current = JSON.parse(await readFile(path, "utf8"));
|
|
143
|
-
const next = update(current);
|
|
144
|
-
if (next === undefined)
|
|
145
|
-
return;
|
|
146
|
-
await writeFile(path, `${JSON.stringify(next, null, 2)}\n`, "utf8");
|
|
147
|
-
}
|
|
148
|
-
/** Merge observed launch-time fields into the single existing invocation.json identity page. */
|
|
137
|
+
/** Merge observed launch-time fields into the single existing invocation section of current.json. */
|
|
149
138
|
async function mergeInvocationIdentityPage(runDirectory, fields) {
|
|
150
|
-
|
|
139
|
+
updateSectionSync(runDirectory, "invocation", (current) => ({
|
|
151
140
|
...current,
|
|
152
141
|
...fields,
|
|
153
142
|
}));
|
|
154
143
|
}
|
|
155
144
|
/**
|
|
156
|
-
* Persist parent --source-run path onto admitted
|
|
145
|
+
* Persist parent --source-run path onto the admitted section for officer resume lookup (#747).
|
|
157
146
|
* Reuses the existing notary sourceRunPath key; does not invent a new field name.
|
|
158
147
|
*/
|
|
159
148
|
export async function persistAdmittedSourceRunPath(admitted, sourceRunPath, auditorSubject) {
|
|
160
149
|
if (sourceRunPath.trim() === "") {
|
|
161
150
|
throw new Error("persistAdmittedSourceRunPath requires a non-empty sourceRunPath");
|
|
162
151
|
}
|
|
163
|
-
|
|
152
|
+
updateSectionSync(admitted.runDirectory, "admitted", (current) => {
|
|
164
153
|
if (typeof current.sourceRunPath === "string" && current.sourceRunPath !== sourceRunPath) {
|
|
165
154
|
throw new Error(`persistAdmittedSourceRunPath refuses to replace ${current.sourceRunPath} with ${sourceRunPath}`);
|
|
166
155
|
}
|
|
@@ -176,7 +165,7 @@ export async function persistAdmittedSourceRunPath(admitted, sourceRunPath, audi
|
|
|
176
165
|
}
|
|
177
166
|
/**
|
|
178
167
|
* Bind a post-admission resolved ticketNumber onto the in-memory admitted
|
|
179
|
-
* object and both durable
|
|
168
|
+
* object and both durable sections (invocation + admitted).
|
|
180
169
|
* A later typed role receipt replaces an earlier assertion (#1025).
|
|
181
170
|
*/
|
|
182
171
|
export async function bindAdmittedTicketNumber(admitted, ticketNumber) {
|
|
@@ -188,7 +177,7 @@ export async function bindAdmittedTicketNumber(admitted, ticketNumber) {
|
|
|
188
177
|
/** Persist each direct caller observed while a retained run is resumed. */
|
|
189
178
|
export async function recordAdmittedCorrelation(admitted, correlationId) {
|
|
190
179
|
let correlationIds = [];
|
|
191
|
-
|
|
180
|
+
updateSectionSync(admitted.runDirectory, "admitted", (current) => {
|
|
192
181
|
const prior = [
|
|
193
182
|
...(Array.isArray(current.correlationIds)
|
|
194
183
|
? current.correlationIds.filter((value) => typeof value === "string" && value.trim() !== "")
|
|
@@ -225,8 +214,7 @@ export async function relocateAdmittedRunToTicket(admitted, authority, heldLease
|
|
|
225
214
|
// Keep that failure before the filesystem commit point.
|
|
226
215
|
const principal = authority.seal(target);
|
|
227
216
|
if (admitted.role !== "diarist") {
|
|
228
|
-
const
|
|
229
|
-
const childRunIds = parentPage.childDiaristRunIds;
|
|
217
|
+
const childRunIds = readPageSync(oldRunDirectory, "admitted")?.childDiaristRunIds;
|
|
230
218
|
for (const childRunId of Array.isArray(childRunIds) ? childRunIds : []) {
|
|
231
219
|
if (typeof childRunId !== "string")
|
|
232
220
|
continue;
|
|
@@ -245,6 +233,15 @@ export async function relocateAdmittedRunToTicket(admitted, authority, heldLease
|
|
|
245
233
|
await rehomeUnboundTicketProvenance(childDirectory, admitted.ticketNumber, admitted.projectRoot, homeFromRunDirectory(oldRunDirectory));
|
|
246
234
|
ensureRoleRunDirectory(ledgerHome, dirname(childTarget.runDirectory));
|
|
247
235
|
await rename(childDirectory, childTarget.runDirectory);
|
|
236
|
+
// Finished child legs will not settle again; refresh derived host.original
|
|
237
|
+
// after the rename commit. A derived render fault must not undo or block
|
|
238
|
+
// the committed placement (#1161 L1) — note and keep the true cause.
|
|
239
|
+
try {
|
|
240
|
+
renderCurrentSync(childTarget.runDirectory);
|
|
241
|
+
}
|
|
242
|
+
catch (error) {
|
|
243
|
+
process.stderr.write(`[invocation] current.json render refused after child relocate; placement kept: ${childTarget.runDirectory}: ${errorText(error)}\n`);
|
|
244
|
+
}
|
|
248
245
|
}
|
|
249
246
|
}
|
|
250
247
|
// The first ticket assignment moves only an unbound diarist's own diary.
|
|
@@ -255,13 +252,13 @@ export async function relocateAdmittedRunToTicket(admitted, authority, heldLease
|
|
|
255
252
|
// outside the atomic directory move.
|
|
256
253
|
// Persisted paths are resolved from typed run identity on read.
|
|
257
254
|
await rename(oldRunDirectory, target.runDirectory);
|
|
258
|
-
// rename moved the open lock inode with the directory. Transfer cleanup
|
|
259
|
-
// ownership immediately after the commit
|
|
255
|
+
// rename moved the open lock inode with the directory. Transfer cleanup and
|
|
256
|
+
// admitted path ownership immediately after the commit — before any derived
|
|
257
|
+
// render that may refuse (#1161 L1 / BASE order).
|
|
260
258
|
heldLease?.relocate(target.runDirectory);
|
|
261
259
|
const admittedRecord = admitted;
|
|
262
260
|
rewriteRunDirectoryPathFields(admittedRecord, [
|
|
263
261
|
"runDirectory",
|
|
264
|
-
"admittedRequestPath",
|
|
265
262
|
"taskPath",
|
|
266
263
|
"packetPath",
|
|
267
264
|
"prerequisitesPath",
|
|
@@ -272,11 +269,15 @@ export async function relocateAdmittedRunToTicket(admitted, authority, heldLease
|
|
|
272
269
|
attachment.frozenPath = rewriteRunDirectoryPathValue(attachment.frozenPath, oldRunDirectory, target.runDirectory);
|
|
273
270
|
}
|
|
274
271
|
admitted.principal = principal;
|
|
272
|
+
// current.json projects host.original from this run directory. Ownership is
|
|
273
|
+
// already transferred; a refuse still carries the true cause to the caller
|
|
274
|
+
// (#1161 L1) without leaving the leg under the unbound path.
|
|
275
|
+
renderCurrentSync(target.runDirectory);
|
|
275
276
|
return { oldRunDirectory, newRunDirectory: target.runDirectory };
|
|
276
277
|
}
|
|
277
278
|
/** Persist the exact child identity; later parent binding never inspects siblings. */
|
|
278
279
|
export async function recordChildDiaristRun(parent, childRunId) {
|
|
279
|
-
|
|
280
|
+
updateSectionSync(parent.runDirectory, "admitted", (page) => {
|
|
280
281
|
const existing = Array.isArray(page.childDiaristRunIds)
|
|
281
282
|
? page.childDiaristRunIds.filter((runId) => typeof runId === "string")
|
|
282
283
|
: [];
|
|
@@ -291,9 +292,8 @@ export async function recordChildDiaristRun(parent, childRunId) {
|
|
|
291
292
|
*/
|
|
292
293
|
export async function bindTicketNumberOnRunDirectory(runDirectory, ticketNumber) {
|
|
293
294
|
requireSafePositiveTicketNumber(ticketNumber, "bindTicketNumberOnRunDirectory");
|
|
294
|
-
const admittedPath = join(runDirectory, "admitted-request.json");
|
|
295
295
|
let unchanged = false;
|
|
296
|
-
|
|
296
|
+
updateSectionSync(runDirectory, "admitted", (admitted) => {
|
|
297
297
|
if (admitted.ticketNumber === ticketNumber) {
|
|
298
298
|
unchanged = true;
|
|
299
299
|
return undefined;
|
|
@@ -701,8 +701,13 @@ async function freezeRegularFileAttachment(sourcePath, destinationDir, index) {
|
|
|
701
701
|
};
|
|
702
702
|
}
|
|
703
703
|
/** Freeze attachments only — ticket binding is the shared LLM seat path (#635). */
|
|
704
|
-
async function freezeAttachments(attachmentPaths, attachmentsDirectory) {
|
|
704
|
+
async function freezeAttachments(ledgerHome, attachmentPaths, attachmentsDirectory) {
|
|
705
705
|
const attachments = [];
|
|
706
|
+
// attachments/ appears only when something is frozen; created through the
|
|
707
|
+
// ledger's symlink-rejecting tree walk, never a plain mkdir.
|
|
708
|
+
if (attachmentPaths.length > 0) {
|
|
709
|
+
ensureRealDirectoryTree(ledgerHome, attachmentsDirectory);
|
|
710
|
+
}
|
|
706
711
|
for (let i = 0; i < attachmentPaths.length; i += 1) {
|
|
707
712
|
const frozen = await freezeRegularFileAttachment(attachmentPaths[i], attachmentsDirectory, i);
|
|
708
713
|
attachments.push(frozen.attachment);
|
|
@@ -720,7 +725,7 @@ export async function freezeAttachmentsIntoRun(attachmentPaths, runDirectory, su
|
|
|
720
725
|
const ledgerHome = resolveActivationLedgerHome(homeFromRunDirectory(runDirectory));
|
|
721
726
|
const attachmentsDirectory = join(runDirectory, "attachments", summonsKey);
|
|
722
727
|
ensureRealDirectoryTree(ledgerHome, attachmentsDirectory);
|
|
723
|
-
return freezeAttachments(attachmentPaths, attachmentsDirectory);
|
|
728
|
+
return freezeAttachments(ledgerHome, attachmentPaths, attachmentsDirectory);
|
|
724
729
|
}
|
|
725
730
|
/** Freeze prepared bytes and metadata through one path for birth or retained runs. */
|
|
726
731
|
export async function freezePreparedAttachmentsIntoRun(prepared, runDirectory, summonsKey) {
|
|
@@ -969,7 +974,7 @@ export async function admitPublicRole(role, parsed, env, override) {
|
|
|
969
974
|
const detail = errorText(error);
|
|
970
975
|
throw new CliUsageError(`doctor case could not be constructed from retained evidence: ${detail}`, { cause: error });
|
|
971
976
|
}
|
|
972
|
-
frozenAttachments = await freezeAttachments(attachmentPaths, placed.attachmentsDirectory);
|
|
977
|
+
frozenAttachments = await freezeAttachments(placed.ledgerHome, attachmentPaths, placed.attachmentsDirectory);
|
|
973
978
|
return {
|
|
974
979
|
issueNumber,
|
|
975
980
|
caseRunsPath,
|
|
@@ -1126,7 +1131,7 @@ async function placeRoleAdmission(options) {
|
|
|
1126
1131
|
});
|
|
1127
1132
|
const attachments = options.freezeAttachments === false
|
|
1128
1133
|
? []
|
|
1129
|
-
: await freezeAttachments(options.attachmentPaths, attachmentsDirectory);
|
|
1134
|
+
: await freezeAttachments(ledgerHome, options.attachmentPaths, attachmentsDirectory);
|
|
1130
1135
|
return {
|
|
1131
1136
|
runId,
|
|
1132
1137
|
bookKey,
|
|
@@ -1151,8 +1156,7 @@ function persistedAttachmentRefs(attachments) {
|
|
|
1151
1156
|
}));
|
|
1152
1157
|
}
|
|
1153
1158
|
async function persistPlacedAdmission(admitted, placed, model) {
|
|
1154
|
-
|
|
1155
|
-
await writeAdmittedRequestPersistence(admittedRequestPath, admitted, {
|
|
1159
|
+
writeAdmittedRequestPersistence(placed.runDirectory, admitted, {
|
|
1156
1160
|
sessionDirectory: placed.sessionDirectory,
|
|
1157
1161
|
sessionFile: placed.sessionFile,
|
|
1158
1162
|
});
|
|
@@ -1161,11 +1165,10 @@ async function persistPlacedAdmission(admitted, placed, model) {
|
|
|
1161
1165
|
sessionDirectory: placed.sessionDirectory,
|
|
1162
1166
|
sessionFile: placed.sessionFile,
|
|
1163
1167
|
}, admitted.role, model);
|
|
1164
|
-
return admittedRequestPath;
|
|
1165
1168
|
}
|
|
1166
1169
|
/**
|
|
1167
1170
|
* Shared admission: project check, placement, attachment freeze,
|
|
1168
|
-
* admitted
|
|
1171
|
+
* admitted and invocation section write.
|
|
1169
1172
|
* Countersign passes deferPersistence so same-ticket lookup can reserve coordinates
|
|
1170
1173
|
* before materializeCountersignInvocation writes the page.
|
|
1171
1174
|
* Seats whose extra facts are known before placement pass them as admittedFields.
|
|
@@ -1214,9 +1217,8 @@ async function admitStandardMaterialInvocation(role, options) {
|
|
|
1214
1217
|
attachments: persistedAttachmentRefs(placed.attachments),
|
|
1215
1218
|
...placed.ticketFields,
|
|
1216
1219
|
};
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
: await persistPlacedAdmission(admitted, placed, options.model);
|
|
1220
|
+
if (!defer)
|
|
1221
|
+
await persistPlacedAdmission(admitted, placed, options.model);
|
|
1220
1222
|
return {
|
|
1221
1223
|
role,
|
|
1222
1224
|
runId: placed.runId,
|
|
@@ -1227,7 +1229,6 @@ async function admitStandardMaterialInvocation(role, options) {
|
|
|
1227
1229
|
attachments: placed.attachments,
|
|
1228
1230
|
runDirectory: placed.runDirectory,
|
|
1229
1231
|
principal: placed.principal,
|
|
1230
|
-
admittedRequestPath,
|
|
1231
1232
|
...correlationFields,
|
|
1232
1233
|
...placed.ticketFields,
|
|
1233
1234
|
...admittedFields,
|
|
@@ -1304,7 +1305,6 @@ export async function materializeCountersignInvocation(admitted, options) {
|
|
|
1304
1305
|
});
|
|
1305
1306
|
const placement = placed;
|
|
1306
1307
|
admitted.runDirectory = placement.runDirectory;
|
|
1307
|
-
admitted.admittedRequestPath = join(placement.runDirectory, "admitted-request.json");
|
|
1308
1308
|
admitted.principal = placement.principal;
|
|
1309
1309
|
const ticketFields = ticketAdmissionFields(options.ticketNumber);
|
|
1310
1310
|
if (ticketFields.ticketNumber !== undefined) {
|
|
@@ -1312,19 +1312,18 @@ export async function materializeCountersignInvocation(admitted, options) {
|
|
|
1312
1312
|
}
|
|
1313
1313
|
const attachments = await freezePreparedAttachmentsIntoRun(options.preparedAttachments, admitted.runDirectory);
|
|
1314
1314
|
admitted.attachments = attachments;
|
|
1315
|
-
|
|
1315
|
+
writeAdmittedRequestPersistence(admitted.runDirectory, admitted, {
|
|
1316
1316
|
sessionDirectory: placement.sessionDirectory,
|
|
1317
1317
|
sessionFile: placement.sessionFile,
|
|
1318
1318
|
});
|
|
1319
1319
|
await writeRoleInvocationLedger({ ...admitted, sessionDirectory: placement.sessionDirectory, sessionFile: placement.sessionFile }, admitted.role, options.model);
|
|
1320
1320
|
}
|
|
1321
|
-
/** Load admitted
|
|
1321
|
+
/** Load the admitted section written at admission (Navigator work-context seam). */
|
|
1322
1322
|
export async function loadAdmittedJudgeRequest(runDirectory) {
|
|
1323
1323
|
try {
|
|
1324
|
-
const
|
|
1325
|
-
if (
|
|
1324
|
+
const record = readPageSync(runDirectory, "admitted");
|
|
1325
|
+
if (record === undefined)
|
|
1326
1326
|
return undefined;
|
|
1327
|
-
const record = raw;
|
|
1328
1327
|
if (!packagedPublicInstructionSubject(record.role))
|
|
1329
1328
|
return undefined;
|
|
1330
1329
|
if (typeof record.instruction !== "string")
|
|
@@ -1343,10 +1342,6 @@ export async function loadAdmittedJudgeRequest(runDirectory) {
|
|
|
1343
1342
|
return undefined;
|
|
1344
1343
|
}
|
|
1345
1344
|
}
|
|
1346
|
-
export async function ensureRunArtifactsDir(runDirectory) {
|
|
1347
|
-
const directory = roleRunArtifactsDirectory(runDirectory);
|
|
1348
|
-
return ensureRoleRunDirectory(resolveActivationLedgerHome(homeFromRunDirectory(runDirectory)), directory);
|
|
1349
|
-
}
|
|
1350
1345
|
function parsePositivePrOption(raw) {
|
|
1351
1346
|
if (raw === undefined || raw.trim() === "")
|
|
1352
1347
|
throw new CliUsageError("--pr requires a positive pull request number");
|