@akagilnc/pi-workflow-roles 0.1.4035 → 0.1.4062
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/acp-host/production-host.js +476 -250
- package/dist/doctor-auditor.js +1 -1
- package/dist/dossier-resolution.js +21 -8
- package/dist/gatekeeper-role.js +34 -27
- package/dist/headless-host/description.js +7 -8
- package/dist/headless-host/production-host.js +503 -264
- package/dist/host-contracts.js +16 -0
- package/dist/inspector-contracts.js +8 -0
- package/dist/packaged-role-registry.js +1 -1
- package/dist/pi/role-turn-host.js +43 -13
- package/dist/public-cli/case-dossier-delivery.js +60 -1
- package/dist/public-cli/inspector-run.js +17 -3
- package/dist/public-cli/instruction-seat-run.js +9 -6
- package/dist/public-cli/main.js +239 -112
- package/dist/public-cli/notary-run.js +7 -4
- package/dist/public-cli/post-admission.js +66 -59
- package/dist/public-cli/run-lifecycle.js +3 -0
- package/dist/public-cli/settlement.js +24 -7
- package/dist/public-role-summons.js +8 -0
- package/dist/submission-ledger.js +30 -8
- package/dist/user-dialogue-stdin.js +33 -0
- package/extensions/role-runtime.ts +1 -0
- package/package.json +1 -1
- package/resources/836-deleted-machine-instruction-inventory.md +1 -1
- package/src/doctor-auditor.ts +1 -1
- package/src/dossier-resolution.ts +26 -8
- package/src/external-host-turn-loop.ts +9 -0
- package/src/gatekeeper-pass-envelope.ts +6 -0
- package/src/gatekeeper-role.ts +47 -28
- package/src/headless-host/description.ts +8 -11
- package/src/headless-host/role-turn-host.ts +20 -5
- package/src/host-contracts.ts +22 -3
- package/src/inspector-contracts.ts +7 -0
- package/src/judge-role.ts +4 -0
- package/src/navigator-work-context.ts +3 -4
- package/src/notary-role.ts +1 -0
- package/src/packaged-role-registry.ts +1 -1
- package/src/pi/adapter.ts +19 -4
- package/src/pi/role-turn-host.ts +48 -14
- package/src/public-cli/case-dossier-delivery.ts +86 -1
- package/src/public-cli/inspector-run.ts +26 -2
- package/src/public-cli/instruction-seat-run.ts +17 -9
- package/src/public-cli/invocation.ts +2 -0
- package/src/public-cli/notary-run.ts +13 -4
- package/src/public-cli/post-admission.ts +74 -65
- package/src/public-cli/run-lifecycle.ts +3 -0
- package/src/public-cli/settlement.ts +32 -6
- package/src/public-role-summons.ts +31 -7
- package/src/role-envelope.ts +9 -30
- package/src/role-runtime-dependencies.ts +1 -0
- package/src/role-runtime.ts +95 -19
- package/src/submission-ledger.ts +48 -13
- package/src/user-dialogue-stdin.ts +37 -0
- package/src/worker-role.ts +4 -0
|
@@ -52,10 +52,10 @@ export async function runPublicNotary(argv, env, io, parseNotaryArgv) {
|
|
|
52
52
|
throw error;
|
|
53
53
|
}
|
|
54
54
|
// #747: officer resume key is this parent source-run path (not ticket number).
|
|
55
|
-
// #753/#
|
|
56
|
-
// (reask wins when both present; no parallel stack).
|
|
55
|
+
// #753/#879: gate re-ask and verbatim submission body share summons.instruction
|
|
56
|
+
// (reask wins when both present; no parallel stack). Binding stays sourceRunPath.
|
|
57
57
|
{
|
|
58
|
-
const resumeInstruction = env.reviewReask;
|
|
58
|
+
const resumeInstruction = env.reviewReask ?? env.gateReviewInstruction;
|
|
59
59
|
const summons = {
|
|
60
60
|
sourceRunPath: source.runDirectory,
|
|
61
61
|
sourceRun: source,
|
|
@@ -112,7 +112,10 @@ export async function runPublicNotary(argv, env, io, parseNotaryArgv) {
|
|
|
112
112
|
: { correlationId: env.correlationId }),
|
|
113
113
|
continuation: {
|
|
114
114
|
kind: "initial",
|
|
115
|
-
|
|
115
|
+
// #879: gate path first mint carries parent payload as dialogue content;
|
|
116
|
+
// external zero-prompt kickoff unchanged when no body/reask.
|
|
117
|
+
prompt: (env.reviewReask ?? env.gateReviewInstruction)
|
|
118
|
+
?? buildNotaryTransportPrompt(admitted, engineMaterial),
|
|
116
119
|
},
|
|
117
120
|
});
|
|
118
121
|
return await runPostAdmissionOneShot({
|
|
@@ -8,13 +8,14 @@
|
|
|
8
8
|
import { randomUUID } from "node:crypto";
|
|
9
9
|
import { readFile, writeFile } from "node:fs/promises";
|
|
10
10
|
import { isAbsolute, join, resolve } from "node:path";
|
|
11
|
-
import { buildResumeContinuationPrompt,
|
|
11
|
+
import { buildResumeContinuationPrompt, RESUME_TRANSPORT_ENVELOPE, } from "./run-lifecycle.js";
|
|
12
12
|
import { CliUsageError } from "./cli-errors.js";
|
|
13
13
|
import { buildInstructionTransportPrompt, freezeAttachmentsIntoRun, } from "./invocation.js";
|
|
14
14
|
import { pathContainedIn } from "../activation-ledger-topology.js";
|
|
15
15
|
import { pickEngineAxis } from "../package-resources/engine-material.js";
|
|
16
16
|
import { resolveHostAwareSessionAvailability } from "../session-identity.js";
|
|
17
|
-
import {
|
|
17
|
+
import { isOfficerReviewSeat } from "../host-contracts.js";
|
|
18
|
+
import { deliverCaseDossierAsAttachment, projectCaseDossierPointerSection, } from "./case-dossier-delivery.js";
|
|
18
19
|
/** Original error bytes, never relabeled — a secondary fact riding beside a classified cause. */
|
|
19
20
|
function describeCaughtError(error) {
|
|
20
21
|
if (error instanceof Error) {
|
|
@@ -30,6 +31,14 @@ function appendContinuationSection(continuation, section) {
|
|
|
30
31
|
? { kind: "initial", prompt }
|
|
31
32
|
: { kind: "resume", prompt };
|
|
32
33
|
}
|
|
34
|
+
/**
|
|
35
|
+
* Nested gate summons (station child) on an officer seat: dialogue content is
|
|
36
|
+
* peer words only (#879). ADR 0081 case dossier still hangs via the existing
|
|
37
|
+
* attachments freeze seam — never into the peer body, never RoleTurnRequest.materials.
|
|
38
|
+
*/
|
|
39
|
+
function isStationChildOfficerDialogue(role, env) {
|
|
40
|
+
return env.stationChild === true && isOfficerReviewSeat(role);
|
|
41
|
+
}
|
|
33
42
|
import { projectHostTransitionPriorNative } from "../host-transition-prior-native.js";
|
|
34
43
|
import { missingCredentialPreDispatchFailure, postRunMissingCredentialFailure, } from "./public-run-credentials.js";
|
|
35
44
|
import { acquireRunWriterLease, clearCurrentCourt, clearTypedProviderHttpObservation, describeErrorIdentity, markRunRunning, readCurrentCourt, recordCurrentCourt, renderResumeCommand, RunWriterLeaseHeldError, } from "./run-lifecycle.js";
|
|
@@ -324,9 +333,9 @@ export async function dispatchPostAdmissionTurn(input) {
|
|
|
324
333
|
// Turn request is assembled after beforeDispatch so this turn sees whatever it
|
|
325
334
|
// settled — the seat's ticket bind re-projection and any court diarist station
|
|
326
335
|
// writes (#742). Case dossier delivery (ADR 0081 / #709) rides here once for
|
|
327
|
-
// every public entry:
|
|
328
|
-
//
|
|
329
|
-
//
|
|
336
|
+
// every public entry. #879: station-child officer dialogue keeps peer body
|
|
337
|
+
// intact — 起居录 hangs via existing attachments freeze (not prompt wrap,
|
|
338
|
+
// not RoleTurnRequest.materials). Other entries keep the neutral prompt section.
|
|
330
339
|
let turnRequest = env.signal === undefined ? request : { ...request, signal: env.signal };
|
|
331
340
|
if (env.stationChild !== undefined) {
|
|
332
341
|
turnRequest = { ...turnRequest, stationChild: env.stationChild };
|
|
@@ -334,16 +343,31 @@ export async function dispatchPostAdmissionTurn(input) {
|
|
|
334
343
|
if (hostTransition !== undefined) {
|
|
335
344
|
turnRequest = { ...turnRequest, hostTransition };
|
|
336
345
|
}
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
346
|
+
if (isStationChildOfficerDialogue(admitted.role, env)) {
|
|
347
|
+
// 0081 non-body face: freeze pointer section under run/attachments/.
|
|
348
|
+
// Peer dialogue continuation.prompt stays parent payload only — the seat
|
|
349
|
+
// consumes the freeze via loadCaseDossierReadingMaterial → existing
|
|
350
|
+
// agent-start readingMaterial / systemPrompt.materials fold (not prompt splice,
|
|
351
|
+
// not RoleTurnRequest.materials).
|
|
352
|
+
await deliverCaseDossierAsAttachment({
|
|
353
|
+
ticketNumber: admitted.ticketNumber,
|
|
354
|
+
projectRoot: admitted.projectRoot,
|
|
355
|
+
home: env.home,
|
|
356
|
+
runDirectory: admitted.runDirectory,
|
|
357
|
+
});
|
|
358
|
+
}
|
|
359
|
+
else {
|
|
360
|
+
const dossierSection = await projectCaseDossierPointerSection({
|
|
361
|
+
ticketNumber: admitted.ticketNumber,
|
|
362
|
+
projectRoot: admitted.projectRoot,
|
|
363
|
+
home: env.home,
|
|
364
|
+
});
|
|
365
|
+
if (dossierSection !== undefined) {
|
|
366
|
+
turnRequest = {
|
|
367
|
+
...turnRequest,
|
|
368
|
+
continuation: appendContinuationSection(turnRequest.continuation, dossierSection),
|
|
369
|
+
};
|
|
370
|
+
}
|
|
347
371
|
}
|
|
348
372
|
// Authoritative host write happens here, at the real dispatch boundary —
|
|
349
373
|
// immediately before the turn actually starts, after every retryable
|
|
@@ -643,46 +667,40 @@ export async function dispatchPostAdmissionTurn(input) {
|
|
|
643
667
|
}
|
|
644
668
|
}
|
|
645
669
|
/**
|
|
646
|
-
* Shared resume continuation projection (#471 / #600 / #633 / #637 / #755):
|
|
670
|
+
* Shared resume continuation projection (#471 / #600 / #633 / #637 / #755 / #879):
|
|
647
671
|
* seat-table model/engine/timeout axes, restored correlation, and either
|
|
648
672
|
* - manual resume (no same-ticket summons): package envelope / optional caller
|
|
649
673
|
* message, with engine-axis handbook via buildResumeContinuationPrompt, or
|
|
650
674
|
* - same-ticket summons (审核循环续话): caller/peer words + optional frozen
|
|
651
|
-
* attachment paths only — no
|
|
652
|
-
* (#750/#755)
|
|
675
|
+
* attachment paths only — no「请重读」、no code-authored content substitute,
|
|
676
|
+
* no engine handbook packaging (#750/#755/#879).
|
|
653
677
|
* Caller message wins as prompt base when supplied (bytes unchanged, including
|
|
654
|
-
* blank/whitespace); else summons instruction
|
|
655
|
-
*
|
|
678
|
+
* blank/whitespace); else summons instruction (parent payload or reask).
|
|
679
|
+
* Binding pointer stays on summons.sourceRunPath / activation — not dialogue content.
|
|
656
680
|
* Seats add only their activation projection. Call prepareSummonsResumeMaterials
|
|
657
681
|
* first when request.summons carries instruction or attachment paths.
|
|
658
682
|
*/
|
|
659
683
|
export function resumeTurnRequestProjectionOptions(admitted, request, env, summonsPrepared) {
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
&& admitted.role !== "inspector"
|
|
665
|
-
&& admitted.role !== "auditor")) {
|
|
666
|
-
return body;
|
|
667
|
-
}
|
|
668
|
-
if (body.startsWith("请重读"))
|
|
669
|
-
return body;
|
|
670
|
-
return `请重读\n${body}`;
|
|
671
|
-
};
|
|
684
|
+
// #879: officer dialogue content is caller/peer words only — no「请重读」,
|
|
685
|
+
// no code-authored constant substitute, no attachment-list wrap of peer body.
|
|
686
|
+
// Binding pointer stays on summons sourceRunPath / activation / attachments.
|
|
687
|
+
const officerDialogue = isStationChildOfficerDialogue(admitted.role, env);
|
|
672
688
|
let prompt;
|
|
673
689
|
if (request.message !== undefined) {
|
|
674
690
|
if (summonsPrepared !== undefined) {
|
|
675
|
-
// #755: same-ticket review / open-court — caller words
|
|
676
|
-
//
|
|
677
|
-
prompt =
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
691
|
+
// #755: same-ticket review / open-court — caller words.
|
|
692
|
+
// #879 station-child officer: words only (attachments are independent freeze).
|
|
693
|
+
prompt = officerDialogue
|
|
694
|
+
? request.message
|
|
695
|
+
: buildInstructionTransportPrompt({
|
|
696
|
+
instruction: request.message,
|
|
697
|
+
instructionEmpty: false,
|
|
698
|
+
attachments: summonsPrepared.attachments,
|
|
699
|
+
});
|
|
682
700
|
}
|
|
683
701
|
else if (request.summons !== undefined) {
|
|
684
702
|
// #755: same-ticket summons without prepared materials — caller words only.
|
|
685
|
-
prompt =
|
|
703
|
+
prompt = request.message;
|
|
686
704
|
}
|
|
687
705
|
else {
|
|
688
706
|
// Bare manual resume — outsourcing engine axis keeps handbook (#600/#736).
|
|
@@ -694,27 +712,16 @@ export function resumeTurnRequestProjectionOptions(admitted, request, env, summo
|
|
|
694
712
|
}
|
|
695
713
|
}
|
|
696
714
|
else if (summonsPrepared !== undefined) {
|
|
697
|
-
// #
|
|
698
|
-
|
|
715
|
+
// #879 station-child officer: instruction bytes === peer body/reask (no wrap).
|
|
716
|
+
// Other seats keep #755 instruction + optional attachment path listing.
|
|
717
|
+
prompt = officerDialogue
|
|
718
|
+
? (summonsPrepared.instructionEmpty ? "" : summonsPrepared.instruction)
|
|
719
|
+
: buildInstructionTransportPrompt(summonsPrepared);
|
|
699
720
|
}
|
|
700
721
|
else if (request.summons !== undefined) {
|
|
701
|
-
// #
|
|
702
|
-
//
|
|
703
|
-
|
|
704
|
-
if (path !== undefined &&
|
|
705
|
-
(admitted.role === "notary" ||
|
|
706
|
-
admitted.role === "inspector" ||
|
|
707
|
-
admitted.role === "auditor")) {
|
|
708
|
-
prompt = `请重读\n${GATE_DOSSIER_POINTER_PREFIX}${path}`;
|
|
709
|
-
}
|
|
710
|
-
else if (admitted.role === "notary" ||
|
|
711
|
-
admitted.role === "inspector" ||
|
|
712
|
-
admitted.role === "auditor") {
|
|
713
|
-
prompt = "请重读";
|
|
714
|
-
}
|
|
715
|
-
else {
|
|
716
|
-
prompt = "";
|
|
717
|
-
}
|
|
722
|
+
// #879: same-ticket summons with no instruction (e.g. notary source-run binding
|
|
723
|
+
// only). Pointer is activation/sourceRun material — not dialogue content.
|
|
724
|
+
prompt = "";
|
|
718
725
|
}
|
|
719
726
|
else {
|
|
720
727
|
// Bare manual resume — outsourcing engine axis keeps handbook (#600/#736).
|
|
@@ -1389,6 +1389,9 @@ export async function loadResumableInspectorRun(home, runId, authority) {
|
|
|
1389
1389
|
const admitted = {
|
|
1390
1390
|
role: "inspector",
|
|
1391
1391
|
...resumedBaseAdmitted(loaded),
|
|
1392
|
+
...(loaded.admittedFields.sourceRunPath === undefined
|
|
1393
|
+
? {}
|
|
1394
|
+
: { sourceRunPath: loaded.admittedFields.sourceRunPath }),
|
|
1392
1395
|
};
|
|
1393
1396
|
return seatLoadedResult(loaded, admitted);
|
|
1394
1397
|
}
|
|
@@ -8,7 +8,7 @@ import { appendFile, lstat, mkdir, readFile, readdir, writeFile } from "node:fs/
|
|
|
8
8
|
import { dirname, join } from "node:path";
|
|
9
9
|
import { readAnalystGateCyclesFromAuditorRoles, } from "../analyst-gate-cycles-read.js";
|
|
10
10
|
import { readSitianRecords, resolveSitianRecordPath, sitianReport } from "../sitian-facade.js";
|
|
11
|
-
import { hasFreshAttemptSubmission, readRecordedSubmissionRows, readRecordedSubmissions, } from "../submission-ledger.js";
|
|
11
|
+
import { hasFreshAttemptSubmission, readAttemptScopedSubmissionRows, readRecordedSubmissionRows, readRecordedSubmissions, } from "../submission-ledger.js";
|
|
12
12
|
import { isAuditEscalationResult } from "../audit-escalation.js";
|
|
13
13
|
import { AUDITOR_SOUL_ROLES } from "../auditor-soul.js";
|
|
14
14
|
import { DOCTOR_AUDIT_TOOL_NAME } from "../doctor-auditor.js";
|
|
@@ -67,6 +67,17 @@ function roleOutcomeFromRows(role, rows) {
|
|
|
67
67
|
return { kind: "accepted", role, payloads };
|
|
68
68
|
}
|
|
69
69
|
async function sealedLedgerOutcome(admitted, role, scope) {
|
|
70
|
+
const home = sealedLedgerHome(admitted);
|
|
71
|
+
// #879: when court scope is present, roleOutcome is this-court original only —
|
|
72
|
+
// never last-wins over an undivided historical array, and never falls back to
|
|
73
|
+
// full-run history when this court sealed zero rows. #836 presentation of full
|
|
74
|
+
// history stays on attachRecordedSubmissions / terminal.submissions (run-scoped).
|
|
75
|
+
if (scope?.courtAttemptId !== undefined && scope.courtAttemptId.length > 0) {
|
|
76
|
+
const thisCourt = await readAttemptScopedSubmissionRows(admitted.projectRoot, admitted.runId, scope.courtAttemptId, home);
|
|
77
|
+
// Scope present + zero this-court rows → no this-court outcome (not run history).
|
|
78
|
+
return roleOutcomeFromRows(role, thisCourt);
|
|
79
|
+
}
|
|
80
|
+
// No court scope: keep run-scoped ledger face (#836).
|
|
70
81
|
const rows = await readRecordedSubmissionRows(admitted.projectRoot, admitted.runId, ledgerReadScope(admitted, scope));
|
|
71
82
|
return roleOutcomeFromRows(role, rows);
|
|
72
83
|
}
|
|
@@ -93,16 +104,22 @@ export function withSubmissions(terminal, submissions) {
|
|
|
93
104
|
if (submissions.length === 0)
|
|
94
105
|
return terminal;
|
|
95
106
|
const roleOutcome = terminal.roleOutcome;
|
|
96
|
-
//
|
|
97
|
-
//
|
|
98
|
-
const withPayloads = roleOutcome.kind === "accepted" || roleOutcome.kind === "audit_escalation"
|
|
99
|
-
? { ...roleOutcome, payloads: submissions }
|
|
100
|
-
: roleOutcome
|
|
107
|
+
// #879 keeps an already scoped this-court result; #881 full run history is
|
|
108
|
+
// presented independently on terminal.submissions. Only fill a missing result payload.
|
|
109
|
+
const withPayloads = roleOutcome.kind === "accepted" || roleOutcome.kind === "audit_escalation"
|
|
110
|
+
? { ...roleOutcome, payloads: (roleOutcome.payloads ?? []).length > 0 ? roleOutcome.payloads : submissions }
|
|
111
|
+
: roleOutcome.kind === "failure"
|
|
112
|
+
? { ...roleOutcome, payloads: roleOutcome.payloads ?? submissions }
|
|
113
|
+
: roleOutcome;
|
|
101
114
|
return { ...terminal, roleOutcome: withPayloads, submissions };
|
|
102
115
|
}
|
|
103
116
|
/** Attach full ledger submissions onto any settled terminal (#836). */
|
|
104
117
|
export async function attachRecordedSubmissions(admitted, terminal, scope) {
|
|
105
|
-
|
|
118
|
+
// #836 / #879: submissions presentation is always run-scoped history. Do not
|
|
119
|
+
// pass courtAttemptId here — roleOutcome already carries this-court payloads
|
|
120
|
+
// from sealedLedgerOutcome when scoped; withSubmissions keeps them.
|
|
121
|
+
void scope;
|
|
122
|
+
return withSubmissions(terminal, await recordedSubmissionPayloads(admitted, undefined));
|
|
106
123
|
}
|
|
107
124
|
/**
|
|
108
125
|
* Host ended with no recorded submission — lawful no_receipt (exit 0).
|
|
@@ -159,6 +159,8 @@ async function summonPublicRole(options) {
|
|
|
159
159
|
...options.signal === void 0 ? {} : { signal: options.signal },
|
|
160
160
|
// #753: reask rides the existing notary same-ticket resume summons.instruction.
|
|
161
161
|
...options.reviewReask === void 0 ? {} : { reviewReask: options.reviewReask },
|
|
162
|
+
// #879: verbatim submission body on officer dialogue content channel.
|
|
163
|
+
...options.gateReviewInstruction === void 0 ? {} : { gateReviewInstruction: options.gateReviewInstruction },
|
|
162
164
|
...options.boundTicketNumber === void 0 ? {} : { boundTicketNumber: options.boundTicketNumber },
|
|
163
165
|
// createRunId is the only remaining env overlay — host is seat-selected above.
|
|
164
166
|
...options.createRunId === void 0 ? {} : { createRunId: options.createRunId }
|
|
@@ -266,6 +268,11 @@ async function summonGateOfficer(options) {
|
|
|
266
268
|
const { homeFromRunDirectory } = await import("./activation-ledger-topology.js");
|
|
267
269
|
home = homeFromRunDirectory(options.sourceRunDirectory);
|
|
268
270
|
}
|
|
271
|
+
let gateReviewInstruction;
|
|
272
|
+
if (options.reask === void 0 && options.submission !== void 0) {
|
|
273
|
+
const { readableGateItem } = await import("./readable-gate-item.js");
|
|
274
|
+
gateReviewInstruction = readableGateItem(options.submission);
|
|
275
|
+
}
|
|
269
276
|
const common = {
|
|
270
277
|
cwd: options.cwd,
|
|
271
278
|
...home === void 0 ? {} : { home },
|
|
@@ -273,6 +280,7 @@ async function summonGateOfficer(options) {
|
|
|
273
280
|
...options.io === void 0 ? {} : { io: options.io },
|
|
274
281
|
...options.signal === void 0 ? {} : { signal: options.signal },
|
|
275
282
|
...options.reask === void 0 ? {} : { reviewReask: options.reask },
|
|
283
|
+
...gateReviewInstruction === void 0 ? {} : { gateReviewInstruction },
|
|
276
284
|
...options.roleTurnHost === void 0 ? {} : { roleTurnHost: options.roleTurnHost },
|
|
277
285
|
...options.hostAdapters === void 0 ? {} : { hostAdapters: options.hostAdapters },
|
|
278
286
|
...options.createRunId === void 0 ? {} : { createRunId: options.createRunId }
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { resolveActivationLedgerHome, tryHomeFromAkRolesPath, } from "./activation-ledger-topology.js";
|
|
2
|
+
import { courtAttemptIdFromHostContext, runDirectoryFromHostContext, } from "./host-contracts.js";
|
|
2
3
|
import { isAuditEscalationProjection } from "./audit-escalation.js";
|
|
3
4
|
import { runIdFromRunDirectory } from "./run-terminal-artifacts.js";
|
|
4
5
|
import { readSitianRecords, resolveSitianRecordPathInLedger, sitianReport } from "./sitian-facade.js";
|
|
@@ -10,8 +11,8 @@ import { failOnInfrastructureFailureDeclaration } from "./package-contracts/term
|
|
|
10
11
|
* otherwise session header id. Never a shared "unbound" bucket.
|
|
11
12
|
*/
|
|
12
13
|
function runIdentity(context) {
|
|
13
|
-
const directory =
|
|
14
|
-
if (
|
|
14
|
+
const directory = runDirectoryFromHostContext(context);
|
|
15
|
+
if (directory !== undefined) {
|
|
15
16
|
const fromDir = runIdFromRunDirectory(directory);
|
|
16
17
|
if (fromDir !== undefined)
|
|
17
18
|
return fromDir;
|
|
@@ -27,8 +28,8 @@ function runIdentity(context) {
|
|
|
27
28
|
*/
|
|
28
29
|
export const COURT_ATTEMPT_ENV = "AK_ROLE_COURT_ATTEMPT";
|
|
29
30
|
function attemptIdentity(context, runId) {
|
|
30
|
-
const courtAttempt =
|
|
31
|
-
if (
|
|
31
|
+
const courtAttempt = courtAttemptIdFromHostContext(context);
|
|
32
|
+
if (courtAttempt !== undefined)
|
|
32
33
|
return courtAttempt;
|
|
33
34
|
return context.sessionManager.getHeader?.()?.id ?? context.sessionManager.getLeafId?.() ?? `${runId}:initial`;
|
|
34
35
|
}
|
|
@@ -151,10 +152,8 @@ function rowFromPayload(kind, payload, accepted, roleFallback) {
|
|
|
151
152
|
* recorded attemptId + toolCallId so distinct court attempts stay distinct (#881).
|
|
152
153
|
* `accepted` is the original payload; never rebuilt from a status/facts envelope.
|
|
153
154
|
*/
|
|
154
|
-
|
|
155
|
-
const
|
|
156
|
-
const { owned } = await readOwnedSubmissionRecords(cwd, runId, scope.home);
|
|
157
|
-
const scoped = recordsForAttempt(owned, scope.attemptId);
|
|
155
|
+
function mapOwnedToSubmissionRows(owned) {
|
|
156
|
+
const scoped = owned;
|
|
158
157
|
const out = [];
|
|
159
158
|
const indexByCall = new Map();
|
|
160
159
|
// Recover seat identity for historical non-sealed rows that omitted role (#881).
|
|
@@ -238,6 +237,29 @@ export async function readRecordedSubmissionRows(cwd, runId, homeOrScope) {
|
|
|
238
237
|
}
|
|
239
238
|
return out;
|
|
240
239
|
}
|
|
240
|
+
/**
|
|
241
|
+
* All recorded role submissions in ledger order (#836 multi-submit).
|
|
242
|
+
* `accepted` is the original payload; never rebuilt from a status/facts envelope.
|
|
243
|
+
* Presentation stays run-scoped: attemptId on the read scope is a recording tag,
|
|
244
|
+
* not a visibility gate (#836).
|
|
245
|
+
*/
|
|
246
|
+
export async function readRecordedSubmissionRows(cwd, runId, homeOrScope) {
|
|
247
|
+
const scope = resolveReadScope(homeOrScope);
|
|
248
|
+
const { owned } = await readOwnedSubmissionRecords(cwd, runId, scope.home);
|
|
249
|
+
const scoped = recordsForAttempt(owned, scope.attemptId);
|
|
250
|
+
return mapOwnedToSubmissionRows(scoped);
|
|
251
|
+
}
|
|
252
|
+
/**
|
|
253
|
+
* Settlement-only this-court rows (#879 return path).
|
|
254
|
+
* Filters by attemptId for court-scoped roleOutcome; does not replace the
|
|
255
|
+
* run-scoped presentation API above (#836 visibility gate stays pass-through).
|
|
256
|
+
*/
|
|
257
|
+
export async function readAttemptScopedSubmissionRows(cwd, runId, attemptId, home) {
|
|
258
|
+
if (attemptId.length === 0)
|
|
259
|
+
return [];
|
|
260
|
+
const { owned } = await readOwnedSubmissionRecords(cwd, runId, home);
|
|
261
|
+
return mapOwnedToSubmissionRows(owned.filter((record) => recordAttemptId(record) === attemptId));
|
|
262
|
+
}
|
|
241
263
|
/**
|
|
242
264
|
* All raw role payloads in ledger order (#836 multi-submit).
|
|
243
265
|
* Returns `accepted` bytes as stored — unknown, never re-wrapped.
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Typed user-dialogue stdin transport (#879 / #471).
|
|
3
|
+
* Pi's piped-stdin reader trims the wrapper; the body field keeps original bytes.
|
|
4
|
+
* No size cap and no path/pointer substitute — the whole dialogue rides the envelope.
|
|
5
|
+
*/
|
|
6
|
+
export const USER_DIALOGUE_STDIN_KIND = "ak-user-dialogue";
|
|
7
|
+
export function encodeUserDialogueStdin(body) {
|
|
8
|
+
return JSON.stringify({ kind: USER_DIALOGUE_STDIN_KIND, body });
|
|
9
|
+
}
|
|
10
|
+
function isUserDialogueEnvelope(value) {
|
|
11
|
+
if (value === null || typeof value !== "object" || Array.isArray(value))
|
|
12
|
+
return false;
|
|
13
|
+
const record = value;
|
|
14
|
+
return record.kind === USER_DIALOGUE_STDIN_KIND && typeof record.body === "string";
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Host read: recover original dialogue after a trim-capable pipe.
|
|
18
|
+
* Non-envelope text (interactive Pi, officer JSON, headless raw body) is unchanged.
|
|
19
|
+
*/
|
|
20
|
+
export function readUserDialogueStdin(raw) {
|
|
21
|
+
const trimmed = raw.trim();
|
|
22
|
+
if (!trimmed.startsWith("{"))
|
|
23
|
+
return raw;
|
|
24
|
+
try {
|
|
25
|
+
const parsed = JSON.parse(trimmed);
|
|
26
|
+
if (isUserDialogueEnvelope(parsed))
|
|
27
|
+
return parsed.body;
|
|
28
|
+
}
|
|
29
|
+
catch {
|
|
30
|
+
// Not our envelope — leave caller bytes intact.
|
|
31
|
+
}
|
|
32
|
+
return raw;
|
|
33
|
+
}
|
|
@@ -111,6 +111,7 @@ export default function roleRuntime(pi: ExtensionAPI): void {
|
|
|
111
111
|
registerNavigatorModelCommand(pi);
|
|
112
112
|
const navigatorSessionFactory = createNativeNavigatorSessionFactory();
|
|
113
113
|
createPiRoleRuntimeExtension({
|
|
114
|
+
packageRoot,
|
|
114
115
|
loadJudgeSoul: () => loadMainRoleSessionMaterials("judge"),
|
|
115
116
|
loadFixerSoul: () => loadMainRoleSessionMaterials("fixer"),
|
|
116
117
|
loadFixPacket: (path) => readFile(path, "utf8"),
|
package/package.json
CHANGED
package/src/doctor-auditor.ts
CHANGED
|
@@ -26,7 +26,7 @@ export type DoctorAuditOptions = {
|
|
|
26
26
|
*/
|
|
27
27
|
export function createPiDoctorAuditor(): (options: DoctorAuditOptions) => Promise<ComplianceDecision> {
|
|
28
28
|
return async (options) => {
|
|
29
|
-
const dossier = resolveAuditDossier();
|
|
29
|
+
const dossier = resolveAuditDossier(options.context);
|
|
30
30
|
requireAuditMaterials(dossier);
|
|
31
31
|
const subjects = readDoctorAuditSubjects(options.context);
|
|
32
32
|
requireAuditMaterials(subjects);
|
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Unique dossier-resolution seam for 审刑院 (#233).
|
|
3
|
-
* Machine pointers only: cwd +
|
|
3
|
+
* Machine pointers only: cwd + per-turn HostContext (Pi child env fallback).
|
|
4
|
+
* No latest-run / mtime / global scan.
|
|
4
5
|
*/
|
|
5
6
|
import { existsSync, statSync } from "node:fs";
|
|
6
7
|
import { resolve } from "node:path";
|
|
7
8
|
|
|
9
|
+
import { runDirectoryFromHostContext, type HostContext } from "./host-contracts.ts";
|
|
10
|
+
|
|
8
11
|
export const AUDIT_RUN_DIR_ENV = "AK_ROLE_RUN_DIR" as const;
|
|
9
12
|
export const DOCTOR_CANDIDATE_ENTRY_TYPE = "ak_doctor_audit_candidate" as const;
|
|
10
13
|
|
|
@@ -33,18 +36,33 @@ export type SubjectIncomplete = {
|
|
|
33
36
|
};
|
|
34
37
|
export type SubjectResolution = SubjectOk | SubjectIncomplete;
|
|
35
38
|
|
|
39
|
+
function isHostContext(value: object): value is HostContext {
|
|
40
|
+
return "sessionManager" in value;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function dossierPointerFrom(source?: HostContext | NodeJS.ProcessEnv): string | undefined {
|
|
44
|
+
if (source !== undefined && isHostContext(source)) {
|
|
45
|
+
return runDirectoryFromHostContext(source);
|
|
46
|
+
}
|
|
47
|
+
const env = source ?? process.env;
|
|
48
|
+
const raw = env[AUDIT_RUN_DIR_ENV];
|
|
49
|
+
return typeof raw === "string" && raw.trim() !== "" ? raw : undefined;
|
|
50
|
+
}
|
|
51
|
+
|
|
36
52
|
/**
|
|
37
|
-
* Resolve the per-run dossier pointer
|
|
53
|
+
* Resolve the per-run dossier pointer.
|
|
38
54
|
*
|
|
55
|
+
* ACP/headless: typed HostContext.runDirectory.
|
|
56
|
+
* Pi child: HostContext projected from env, or env fallback when no context.
|
|
39
57
|
* Absent pointer = bare Pi internal seam (ADR 0052): audit proceeds; the model
|
|
40
|
-
* self-locates the dossier from its own fall-volume position per soul.
|
|
41
|
-
* always injects the pointer — only then does the machine validate the path.
|
|
42
|
-
* Concurrent runs stay isolated because a present pointer is per-process.
|
|
58
|
+
* self-locates the dossier from its own fall-volume position per soul.
|
|
43
59
|
*/
|
|
44
|
-
export function resolveAuditDossier(
|
|
45
|
-
|
|
60
|
+
export function resolveAuditDossier(
|
|
61
|
+
source?: HostContext | NodeJS.ProcessEnv,
|
|
62
|
+
): DossierResolution {
|
|
63
|
+
const raw = dossierPointerFrom(source);
|
|
46
64
|
// Bare Pi activation seam: no machine gate when the pointer was never injected.
|
|
47
|
-
if (
|
|
65
|
+
if (raw === undefined) {
|
|
48
66
|
return { status: "ok" };
|
|
49
67
|
}
|
|
50
68
|
const runDirectory = resolve(raw);
|
|
@@ -9,6 +9,7 @@ import type {
|
|
|
9
9
|
RoleTurnRequest,
|
|
10
10
|
RoleTurnResult,
|
|
11
11
|
} from "./host-contracts.ts";
|
|
12
|
+
import { isOfficerReviewSeat } from "./host-contracts.ts";
|
|
12
13
|
|
|
13
14
|
export const EXTERNAL_ROLE_TURN_ROUND_LIMIT = 8 as const;
|
|
14
15
|
|
|
@@ -54,8 +55,16 @@ export function mergeRoleTurnAbortSignals(
|
|
|
54
55
|
return AbortSignal.any([prepared, request]);
|
|
55
56
|
}
|
|
56
57
|
|
|
58
|
+
/**
|
|
59
|
+
* Host-transition prior-native paths are machine handoff material (#617 DK-4).
|
|
60
|
+
* #879: station-child officer dialogue must not receive those paths spliced into
|
|
61
|
+
* the review prompt — officers already hold --source-run / binding pointers.
|
|
62
|
+
*/
|
|
57
63
|
export function promptWithPriorNativePaths(basePrompt: string, request: RoleTurnRequest): string {
|
|
58
64
|
if (request.continuation.kind !== "resume") return basePrompt;
|
|
65
|
+
if (request.stationChild === true && isOfficerReviewSeat(request.activation.role)) {
|
|
66
|
+
return basePrompt;
|
|
67
|
+
}
|
|
59
68
|
const paths = request.hostTransition?.priorNativePaths;
|
|
60
69
|
if (paths === undefined || paths.length === 0) return basePrompt;
|
|
61
70
|
return `${basePrompt}\n${paths.join("\n")}`;
|
|
@@ -74,6 +74,11 @@ export async function requireGatekeeperPass(options: {
|
|
|
74
74
|
readonly signal?: AbortSignal;
|
|
75
75
|
readonly hostActions: GatekeeperPassHostActions;
|
|
76
76
|
readonly toolCallId: string;
|
|
77
|
+
/**
|
|
78
|
+
* In-flight parent typed payload for this gate turn (#879). Relayed verbatim
|
|
79
|
+
* as officer dialogue content; binding pointer stays the parent run directory.
|
|
80
|
+
*/
|
|
81
|
+
readonly submission?: unknown;
|
|
77
82
|
/** Lowest seam: same as runGatekeeper options.summonOfficer — offline tracers only. */
|
|
78
83
|
readonly summonOfficer?: GateOfficerSummon;
|
|
79
84
|
}): Promise<void> {
|
|
@@ -85,6 +90,7 @@ export async function requireGatekeeperPass(options: {
|
|
|
85
90
|
subject: options.subject,
|
|
86
91
|
...(options.signal === undefined ? {} : { signal: options.signal }),
|
|
87
92
|
...(options.summonOfficer === undefined ? {} : { summonOfficer: options.summonOfficer }),
|
|
93
|
+
...(options.submission === undefined ? {} : { submission: options.submission }),
|
|
88
94
|
...(reask === undefined ? {} : { reask }),
|
|
89
95
|
});
|
|
90
96
|
const gatekeeper = projected.result;
|