@akagilnc/pi-workflow-roles 0.1.5483 → 0.1.5571
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 +6312 -6376
- package/dist/analyst-gate-cycles-read.js +28 -78
- package/dist/analyst-ledger.js +26 -42
- 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-entry.js +4 -3
- package/dist/archivist-record-pointer.js +71 -36
- package/dist/atomic-write.js +2 -2
- package/dist/auditor-dossier-tool.js +11 -6
- package/dist/auditor-soul.js +5 -12
- package/dist/collector-config.js +6 -5
- package/dist/doctor-evidence.js +8 -4
- package/dist/engine-detour-usage.js +8 -22
- package/dist/headless-host/production-host.js +5969 -6011
- package/dist/host-descriptions.js +0 -4
- package/dist/host-session-record.js +266 -267
- package/dist/ledger-session-read.js +5 -0
- package/dist/migrate-book-topology.js +1369 -722
- package/dist/navigator-public-session.js +13 -17
- package/dist/notary-source-run.js +8 -16
- package/dist/packaged-role-registry.js +5 -56
- package/dist/pi/durable-principal.js +2 -3
- package/dist/pi/role-turn-host.js +79 -15
- package/dist/public-cli/auto-resume.js +8 -47
- package/dist/public-cli/instruction-seat-run.js +9 -7
- package/dist/public-cli/invocation.js +67 -100
- package/dist/public-cli/main.js +2689 -2532
- package/dist/public-cli/post-admission.js +96 -55
- package/dist/public-cli/run-lifecycle.js +44 -100
- package/dist/public-cli/settlement.js +184 -429
- package/dist/public-cli/terminal.js +0 -12
- package/dist/role-run-placement.js +53 -16
- package/dist/role-run-relocation.js +111 -52
- package/dist/run-dossier-files.js +30 -0
- package/dist/run-dossier.js +322 -0
- package/dist/run-terminal-artifacts.js +69 -202
- package/dist/run-ticket-number.js +49 -7
- package/dist/session-assistant-usage.js +9 -18
- package/dist/session-identity.js +49 -19
- package/dist/sitian-appender.js +29 -18
- package/dist/sitian-facade.js +11 -0
- package/dist/sitian-reader.js +44 -29
- package/dist/submission-gate.js +1 -1
- package/dist/submission-ledger.js +4 -4
- package/dist/ticket-provenance-contracts.js +2 -8
- package/dist/ticket-provenance.js +25 -9
- 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 +11 -4
- package/src/analyst-gate-cycles-read.ts +31 -88
- package/src/analyst-ledger.ts +36 -51
- 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 +6 -5
- package/src/archivist-record-pointer.ts +92 -42
- package/src/atomic-write.ts +3 -1
- package/src/auditor-dossier-tool.ts +14 -8
- package/src/auditor-soul.ts +5 -10
- 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/collector-config.ts +5 -3
- package/src/doctor-contracts.ts +1 -1
- package/src/doctor-evidence.ts +7 -3
- package/src/engine-detour-tool.ts +1 -1
- package/src/engine-detour-usage.ts +12 -25
- package/src/external-host-turn-loop.ts +25 -0
- package/src/factory-board.ts +16 -81
- package/src/headless-host/production-host.ts +1 -4
- package/src/headless-host/role-turn-host.ts +56 -21
- package/src/host-descriptions.ts +0 -6
- package/src/host-session-record.ts +224 -140
- package/src/ledger-session-read.ts +6 -0
- package/src/navigator-public-session.ts +13 -18
- package/src/navigator-work-context.ts +3 -1
- package/src/notary-role.ts +2 -1
- package/src/notary-source-run.ts +8 -18
- package/src/packaged-role-registry.ts +1 -65
- package/src/pi/adapter.ts +50 -7
- package/src/pi/durable-principal.ts +2 -4
- package/src/pi/role-turn-host.ts +85 -14
- package/src/public-cli/auto-resume.ts +7 -43
- package/src/public-cli/instruction-seat-run.ts +13 -6
- package/src/public-cli/invocation.ts +90 -144
- package/src/public-cli/post-admission.ts +111 -72
- package/src/public-cli/run-lifecycle.ts +47 -115
- package/src/public-cli/settlement.ts +221 -589
- package/src/public-cli/terminal.ts +1 -61
- package/src/role-envelope.ts +4 -2
- package/src/role-run-placement.ts +65 -17
- package/src/role-run-relocation.ts +110 -70
- package/src/role-runtime.ts +46 -69
- package/src/run-dossier-files.ts +33 -0
- package/src/run-dossier.ts +357 -0
- package/src/run-terminal-artifacts.ts +84 -240
- package/src/run-ticket-number.ts +82 -14
- package/src/session-assistant-usage.ts +8 -20
- package/src/session-identity.ts +52 -21
- package/src/sitian-appender.ts +33 -26
- package/src/sitian-facade.ts +22 -0
- package/src/sitian-reader.ts +62 -32
- package/src/submission-gate.ts +1 -1
- package/src/submission-ledger.ts +4 -4
- package/src/ticket-provenance-contracts.ts +3 -6
- package/src/ticket-provenance.ts +23 -11
- package/src/ticket-snapshot.ts +3 -2
- package/src/ticket-trajectory.ts +95 -109
|
@@ -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, 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
|
-
import { isSafePositiveTicketNumber, readBoardTicketNumber, requireSafePositiveTicketNumber, } from "../run-ticket-number.js";
|
|
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,12 +214,11 @@ 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;
|
|
233
|
-
const childDirectory = join(dirname(oldRunDirectory),
|
|
221
|
+
const childDirectory = join(dirname(oldRunDirectory), formatRunLeaf(childRunId, "diarist"));
|
|
234
222
|
// A child that already filed under a ticket keeps its own assertion.
|
|
235
223
|
if (!existsSync(childDirectory))
|
|
236
224
|
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;
|
|
@@ -334,17 +334,16 @@ export async function recordLaunchedRolePackageIdentity(runDirectory, identity)
|
|
|
334
334
|
entryMode: identity.entryMode,
|
|
335
335
|
});
|
|
336
336
|
}
|
|
337
|
+
/** Trim, then the shared ticket spelling. Callers keep their own diagnostic text. */
|
|
338
|
+
function rejectUnlessTicketNumber(raw, message) {
|
|
339
|
+
const parsed = parseTicketNumber(raw.trim());
|
|
340
|
+
if (parsed === undefined)
|
|
341
|
+
throw new CliUsageError(message);
|
|
342
|
+
return parsed;
|
|
343
|
+
}
|
|
337
344
|
/** Positive ticket number for analyst query-scope face (and shared integer parse). */
|
|
338
345
|
export function parsePositiveTicketNumber(raw, flag) {
|
|
339
|
-
|
|
340
|
-
if (!ANALYST_TICKET_NUMBER_PATTERN.test(trimmed)) {
|
|
341
|
-
throw new CliUsageError(`${flag} must be a positive integer, got ${raw}`);
|
|
342
|
-
}
|
|
343
|
-
const value = Number(trimmed);
|
|
344
|
-
if (!Number.isSafeInteger(value) || value < 1) {
|
|
345
|
-
throw new CliUsageError(`${flag} must be a positive integer, got ${raw}`);
|
|
346
|
-
}
|
|
347
|
-
return value;
|
|
346
|
+
return rejectUnlessTicketNumber(raw, `${flag} must be a positive integer, got ${raw}`);
|
|
348
347
|
}
|
|
349
348
|
/**
|
|
350
349
|
* One token scan for public argv.
|
|
@@ -702,8 +701,13 @@ async function freezeRegularFileAttachment(sourcePath, destinationDir, index) {
|
|
|
702
701
|
};
|
|
703
702
|
}
|
|
704
703
|
/** Freeze attachments only — ticket binding is the shared LLM seat path (#635). */
|
|
705
|
-
async function freezeAttachments(attachmentPaths, attachmentsDirectory) {
|
|
704
|
+
async function freezeAttachments(ledgerHome, attachmentPaths, attachmentsDirectory) {
|
|
706
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
|
+
}
|
|
707
711
|
for (let i = 0; i < attachmentPaths.length; i += 1) {
|
|
708
712
|
const frozen = await freezeRegularFileAttachment(attachmentPaths[i], attachmentsDirectory, i);
|
|
709
713
|
attachments.push(frozen.attachment);
|
|
@@ -721,7 +725,7 @@ export async function freezeAttachmentsIntoRun(attachmentPaths, runDirectory, su
|
|
|
721
725
|
const ledgerHome = resolveActivationLedgerHome(homeFromRunDirectory(runDirectory));
|
|
722
726
|
const attachmentsDirectory = join(runDirectory, "attachments", summonsKey);
|
|
723
727
|
ensureRealDirectoryTree(ledgerHome, attachmentsDirectory);
|
|
724
|
-
return freezeAttachments(attachmentPaths, attachmentsDirectory);
|
|
728
|
+
return freezeAttachments(ledgerHome, attachmentPaths, attachmentsDirectory);
|
|
725
729
|
}
|
|
726
730
|
/** Freeze prepared bytes and metadata through one path for birth or retained runs. */
|
|
727
731
|
export async function freezePreparedAttachmentsIntoRun(prepared, runDirectory, summonsKey) {
|
|
@@ -957,7 +961,7 @@ export async function admitPublicRole(role, parsed, env, override) {
|
|
|
957
961
|
}
|
|
958
962
|
let caseIdentity;
|
|
959
963
|
try {
|
|
960
|
-
const patient = await loadDoctorCase(caseRunsPath);
|
|
964
|
+
const patient = await loadDoctorCase(caseRunsPath, parsed.runs === undefined ? undefined : placed.projectRoot);
|
|
961
965
|
if (patient.identity.issueNumber !== issueNumber) {
|
|
962
966
|
throw new CliUsageError(`doctor case issue ${patient.identity.issueNumber} does not match --issue ${issueNumber}`);
|
|
963
967
|
}
|
|
@@ -970,7 +974,7 @@ export async function admitPublicRole(role, parsed, env, override) {
|
|
|
970
974
|
const detail = errorText(error);
|
|
971
975
|
throw new CliUsageError(`doctor case could not be constructed from retained evidence: ${detail}`, { cause: error });
|
|
972
976
|
}
|
|
973
|
-
frozenAttachments = await freezeAttachments(attachmentPaths, placed.attachmentsDirectory);
|
|
977
|
+
frozenAttachments = await freezeAttachments(placed.ledgerHome, attachmentPaths, placed.attachmentsDirectory);
|
|
974
978
|
return {
|
|
975
979
|
issueNumber,
|
|
976
980
|
caseRunsPath,
|
|
@@ -1127,7 +1131,7 @@ async function placeRoleAdmission(options) {
|
|
|
1127
1131
|
});
|
|
1128
1132
|
const attachments = options.freezeAttachments === false
|
|
1129
1133
|
? []
|
|
1130
|
-
: await freezeAttachments(options.attachmentPaths, attachmentsDirectory);
|
|
1134
|
+
: await freezeAttachments(ledgerHome, options.attachmentPaths, attachmentsDirectory);
|
|
1131
1135
|
return {
|
|
1132
1136
|
runId,
|
|
1133
1137
|
bookKey,
|
|
@@ -1152,8 +1156,7 @@ function persistedAttachmentRefs(attachments) {
|
|
|
1152
1156
|
}));
|
|
1153
1157
|
}
|
|
1154
1158
|
async function persistPlacedAdmission(admitted, placed, model) {
|
|
1155
|
-
|
|
1156
|
-
await writeAdmittedRequestPersistence(admittedRequestPath, admitted, {
|
|
1159
|
+
writeAdmittedRequestPersistence(placed.runDirectory, admitted, {
|
|
1157
1160
|
sessionDirectory: placed.sessionDirectory,
|
|
1158
1161
|
sessionFile: placed.sessionFile,
|
|
1159
1162
|
});
|
|
@@ -1162,11 +1165,10 @@ async function persistPlacedAdmission(admitted, placed, model) {
|
|
|
1162
1165
|
sessionDirectory: placed.sessionDirectory,
|
|
1163
1166
|
sessionFile: placed.sessionFile,
|
|
1164
1167
|
}, admitted.role, model);
|
|
1165
|
-
return admittedRequestPath;
|
|
1166
1168
|
}
|
|
1167
1169
|
/**
|
|
1168
1170
|
* Shared admission: project check, placement, attachment freeze,
|
|
1169
|
-
* admitted
|
|
1171
|
+
* admitted and invocation section write.
|
|
1170
1172
|
* Countersign passes deferPersistence so same-ticket lookup can reserve coordinates
|
|
1171
1173
|
* before materializeCountersignInvocation writes the page.
|
|
1172
1174
|
* Seats whose extra facts are known before placement pass them as admittedFields.
|
|
@@ -1215,9 +1217,8 @@ async function admitStandardMaterialInvocation(role, options) {
|
|
|
1215
1217
|
attachments: persistedAttachmentRefs(placed.attachments),
|
|
1216
1218
|
...placed.ticketFields,
|
|
1217
1219
|
};
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
: await persistPlacedAdmission(admitted, placed, options.model);
|
|
1220
|
+
if (!defer)
|
|
1221
|
+
await persistPlacedAdmission(admitted, placed, options.model);
|
|
1221
1222
|
return {
|
|
1222
1223
|
role,
|
|
1223
1224
|
runId: placed.runId,
|
|
@@ -1228,7 +1229,6 @@ async function admitStandardMaterialInvocation(role, options) {
|
|
|
1228
1229
|
attachments: placed.attachments,
|
|
1229
1230
|
runDirectory: placed.runDirectory,
|
|
1230
1231
|
principal: placed.principal,
|
|
1231
|
-
admittedRequestPath,
|
|
1232
1232
|
...correlationFields,
|
|
1233
1233
|
...placed.ticketFields,
|
|
1234
1234
|
...admittedFields,
|
|
@@ -1305,7 +1305,6 @@ export async function materializeCountersignInvocation(admitted, options) {
|
|
|
1305
1305
|
});
|
|
1306
1306
|
const placement = placed;
|
|
1307
1307
|
admitted.runDirectory = placement.runDirectory;
|
|
1308
|
-
admitted.admittedRequestPath = join(placement.runDirectory, "admitted-request.json");
|
|
1309
1308
|
admitted.principal = placement.principal;
|
|
1310
1309
|
const ticketFields = ticketAdmissionFields(options.ticketNumber);
|
|
1311
1310
|
if (ticketFields.ticketNumber !== undefined) {
|
|
@@ -1313,19 +1312,18 @@ export async function materializeCountersignInvocation(admitted, options) {
|
|
|
1313
1312
|
}
|
|
1314
1313
|
const attachments = await freezePreparedAttachmentsIntoRun(options.preparedAttachments, admitted.runDirectory);
|
|
1315
1314
|
admitted.attachments = attachments;
|
|
1316
|
-
|
|
1315
|
+
writeAdmittedRequestPersistence(admitted.runDirectory, admitted, {
|
|
1317
1316
|
sessionDirectory: placement.sessionDirectory,
|
|
1318
1317
|
sessionFile: placement.sessionFile,
|
|
1319
1318
|
});
|
|
1320
1319
|
await writeRoleInvocationLedger({ ...admitted, sessionDirectory: placement.sessionDirectory, sessionFile: placement.sessionFile }, admitted.role, options.model);
|
|
1321
1320
|
}
|
|
1322
|
-
/** Load admitted
|
|
1321
|
+
/** Load the admitted section written at admission (Navigator work-context seam). */
|
|
1323
1322
|
export async function loadAdmittedJudgeRequest(runDirectory) {
|
|
1324
1323
|
try {
|
|
1325
|
-
const
|
|
1326
|
-
if (
|
|
1324
|
+
const record = readPageSync(runDirectory, "admitted");
|
|
1325
|
+
if (record === undefined)
|
|
1327
1326
|
return undefined;
|
|
1328
|
-
const record = raw;
|
|
1329
1327
|
if (!packagedPublicInstructionSubject(record.role))
|
|
1330
1328
|
return undefined;
|
|
1331
1329
|
if (typeof record.instruction !== "string")
|
|
@@ -1344,10 +1342,6 @@ export async function loadAdmittedJudgeRequest(runDirectory) {
|
|
|
1344
1342
|
return undefined;
|
|
1345
1343
|
}
|
|
1346
1344
|
}
|
|
1347
|
-
export async function ensureRunArtifactsDir(runDirectory) {
|
|
1348
|
-
const directory = roleRunArtifactsDirectory(runDirectory);
|
|
1349
|
-
return ensureRoleRunDirectory(resolveActivationLedgerHome(homeFromRunDirectory(runDirectory)), directory);
|
|
1350
|
-
}
|
|
1351
1345
|
function parsePositivePrOption(raw) {
|
|
1352
1346
|
if (raw === undefined || raw.trim() === "")
|
|
1353
1347
|
throw new CliUsageError("--pr requires a positive pull request number");
|
|
@@ -1411,26 +1405,17 @@ function isGitRemoteMissing(error) {
|
|
|
1411
1405
|
const status = error.status;
|
|
1412
1406
|
return status === 2;
|
|
1413
1407
|
}
|
|
1414
|
-
/** Positive Issue number grammar shared with Doctor case path identity. */
|
|
1415
|
-
const DOCTOR_ISSUE_NUMBER_PATTERN = /^[1-9]\d*$/;
|
|
1416
|
-
/** Match retained Doctor case runs roots (ADR 0017 / loadDoctorCase). */
|
|
1417
|
-
/** Canonical Doctor case: `<book>/<ticket>/runs`. Legacy `issues/<n>/runs` is read-only compat. */
|
|
1418
|
-
const DOCTOR_CASE_RUNS_PATH_PATTERN = /\/\.ak-roles\/books\/[^/]+\/(?:issues\/)?([1-9]\d*)\/runs$/;
|
|
1419
1408
|
/**
|
|
1420
1409
|
* Parse a positive Issue number for public Doctor admission.
|
|
1421
1410
|
* Leading zeros and non-integers are structural rejects.
|
|
1422
1411
|
*/
|
|
1423
1412
|
export function parseDoctorIssueNumber(raw) {
|
|
1424
|
-
|
|
1425
|
-
if (!DOCTOR_ISSUE_NUMBER_PATTERN.test(trimmed)) {
|
|
1426
|
-
throw new CliUsageError(`doctor --issue must be a positive integer, got ${raw}`);
|
|
1427
|
-
}
|
|
1428
|
-
return Number(trimmed);
|
|
1413
|
+
return rejectUnlessTicketNumber(raw, `doctor --issue must be a positive integer, got ${raw}`);
|
|
1429
1414
|
}
|
|
1430
1415
|
/**
|
|
1431
1416
|
* Resolve the retained Doctor case runs root from Issue identity.
|
|
1432
|
-
* Default is the #78 book locator; optional --runs must stay project-confined
|
|
1433
|
-
*
|
|
1417
|
+
* Default is the #78 book locator; optional --runs must stay project-confined.
|
|
1418
|
+
* loadDoctorCase owns retained-root grammar at IO; admission matches its case identity.
|
|
1434
1419
|
*/
|
|
1435
1420
|
export async function resolveDoctorCaseRunsPath(options) {
|
|
1436
1421
|
const ledgerHome = resolveActivationLedgerHome(options.home);
|
|
@@ -1460,14 +1445,6 @@ export async function resolveDoctorCaseRunsPath(options) {
|
|
|
1460
1445
|
const detail = errorText(error);
|
|
1461
1446
|
throw new CliUsageError(`doctor --runs is not a readable retained runs root: ${detail}`, { cause: error });
|
|
1462
1447
|
}
|
|
1463
|
-
const normalized = real.split(sep).join("/");
|
|
1464
|
-
const match = normalized.match(DOCTOR_CASE_RUNS_PATH_PATTERN);
|
|
1465
|
-
if (!match) {
|
|
1466
|
-
throw new CliUsageError("doctor --runs must be an .ak-roles/books/<book>/<n>/runs directory");
|
|
1467
|
-
}
|
|
1468
|
-
if (Number(match[1]) !== options.issueNumber) {
|
|
1469
|
-
throw new CliUsageError(`doctor --runs issue ${match[1]} does not match --issue ${options.issueNumber}`);
|
|
1470
|
-
}
|
|
1471
1448
|
return real;
|
|
1472
1449
|
}
|
|
1473
1450
|
/** Frozen Skill arg projection shared by initial and resume (never reverse-parsed). */
|
|
@@ -1501,23 +1478,13 @@ export async function deriveMergerEnvelopeFromActiveMerge(projectRoot, gitState
|
|
|
1501
1478
|
resolutionScope,
|
|
1502
1479
|
});
|
|
1503
1480
|
}
|
|
1504
|
-
const ANALYST_TICKET_NUMBER_PATTERN = /^[1-9]\d*$/;
|
|
1505
1481
|
/**
|
|
1506
1482
|
* Parse a positive ticket / issue number for public analyst admission.
|
|
1507
1483
|
* Leading zeros and non-integers are structural rejects (same face as #176).
|
|
1508
1484
|
* `flag` names the actual argv face in diagnostics (cohort group lists reuse this).
|
|
1509
1485
|
*/
|
|
1510
1486
|
export function parseAnalystTicketNumber(raw, flag = "--ticket") {
|
|
1511
|
-
|
|
1512
|
-
if (!ANALYST_TICKET_NUMBER_PATTERN.test(trimmed)) {
|
|
1513
|
-
throw new CliUsageError(`analyst ${flag} must be a positive integer, got ${raw}`);
|
|
1514
|
-
}
|
|
1515
|
-
const value = Number(trimmed);
|
|
1516
|
-
// Digit-only strings beyond MAX_SAFE_INTEGER round or become Infinity — reject.
|
|
1517
|
-
if (!Number.isSafeInteger(value) || value < 1) {
|
|
1518
|
-
throw new CliUsageError(`analyst ${flag} must be a positive integer, got ${raw}`);
|
|
1519
|
-
}
|
|
1520
|
-
return value;
|
|
1487
|
+
return rejectUnlessTicketNumber(raw, `analyst ${flag} must be a positive integer, got ${raw}`);
|
|
1521
1488
|
}
|
|
1522
1489
|
/**
|
|
1523
1490
|
* Parse one cohort issue token: bare positive integer or `book:N`.
|
|
@@ -1532,7 +1499,7 @@ export function parseAnalystCohortIssueToken(raw, flag) {
|
|
|
1532
1499
|
const sep = trimmed.lastIndexOf(":");
|
|
1533
1500
|
if (sep > 0) {
|
|
1534
1501
|
const rhs = trimmed.slice(sep + 1);
|
|
1535
|
-
if (
|
|
1502
|
+
if (parseTicketNumber(rhs) !== undefined) {
|
|
1536
1503
|
const bookKey = trimmed.slice(0, sep);
|
|
1537
1504
|
if (bookKey.trim() === "") {
|
|
1538
1505
|
throw new CliUsageError(`${flag} book:N requires a non-empty book key, got ${raw}`);
|