@akagilnc/pi-workflow-roles 0.1.3572 → 0.1.3621
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 +20239 -10556
- package/dist/analyst-gate-cycles-read.js +387 -0
- package/dist/archivist-record-entry.js +22 -1
- package/dist/audit-escalation.js +6 -0
- package/dist/auditor-soul.js +74 -0
- package/dist/collector-config.js +86 -0
- package/dist/collector-evidence.js +316 -0
- package/dist/collector-github.js +527 -0
- package/dist/collector-ledger.js +853 -0
- package/dist/collector-tool-schemas.js +33 -0
- package/dist/compliance-transport.js +104 -52
- package/dist/diarist-mechanical.js +411 -0
- package/dist/diarist-ticket-resolution.js +177 -0
- package/dist/diarist.js +311 -0
- package/dist/doctor-auditor.js +25 -0
- package/dist/doctor-contracts.js +2 -0
- package/dist/doctor-evidence.js +142 -0
- package/dist/gatekeeper-role.js +104 -81
- package/dist/host-transition-prior-native.js +78 -0
- package/dist/institutional-resolution.js +1 -95
- package/dist/judge-auditor.js +26 -0
- package/dist/ledger-session-read.js +227 -0
- package/dist/merger-git-state.js +78 -0
- package/dist/navigator-attendance.js +15 -7
- package/dist/navigator-public-session.js +170 -0
- package/dist/navigator-session-contracts.js +36 -40
- package/dist/notary-source-run.js +122 -0
- package/dist/package-contracts/auditor-output.js +66 -0
- package/dist/package-contracts/evidence-child-output.js +34 -0
- package/dist/package-contracts/judge-output.js +2 -0
- package/dist/package-contracts/terminating-tools.js +25 -2
- package/dist/package-resources/method-skill.js +254 -0
- package/dist/packaged-role-registry.js +36 -0
- package/dist/pi/durable-principal.js +61 -0
- package/dist/pi/in-process-session.js +25 -6
- package/dist/pi/known-failure.js +52 -0
- package/dist/pi/role-turn-host.js +430 -0
- package/dist/public-cli/auto-resume.js +414 -0
- package/dist/public-cli/cli-errors.js +8 -0
- package/dist/public-cli/cli-io.js +1 -0
- package/dist/public-cli/command-renderer.js +5 -0
- package/dist/public-cli/doctor-run.js +84 -0
- package/dist/public-cli/inspector-run.js +136 -0
- package/dist/public-cli/instruction-seat-run.js +256 -0
- package/dist/public-cli/invocation.js +2225 -0
- package/dist/public-cli/judge-run.js +118 -0
- package/dist/public-cli/load-production-acp-host.js +40 -0
- package/dist/public-cli/main.js +1140 -652
- package/dist/public-cli/notary-run.js +161 -0
- package/dist/public-cli/option-definitions.js +1192 -0
- package/dist/public-cli/post-admission.js +667 -0
- package/dist/public-cli/public-run-credentials.js +49 -0
- package/dist/public-cli/registry.js +4 -1
- package/dist/public-cli/reviewer-dispatch-rejection.js +77 -0
- package/dist/public-cli/run-lifecycle.js +1344 -0
- package/dist/public-cli/seat-ticket-binding.js +113 -0
- package/dist/public-cli/settlement.js +3441 -0
- package/dist/public-cli/terminal.js +135 -0
- package/dist/public-cli/turn-request.js +25 -0
- package/dist/public-role-summons.js +301 -0
- package/dist/receipt-delivery-policy.js +10 -0
- package/dist/reviewer-child-executor.js +80 -8
- package/dist/reviewer-execution-ledger.js +2 -1
- package/dist/run-terminal-artifacts.js +195 -0
- package/dist/run-ticket-number.js +40 -0
- package/dist/session-assistant-usage.js +107 -0
- package/dist/shape-unreadable-failure.js +34 -0
- package/dist/submission-errors.js +3 -0
- package/dist/submission-ledger.js +388 -0
- package/dist/ticket-provenance-contracts.js +115 -0
- package/dist/ticket-provenance.js +340 -0
- package/extensions/role-runtime.ts +7 -0
- package/package.json +1 -1
- package/scripts/build-package.mjs +2 -1
- package/souls/doctor-auditor.md +1 -0
- package/souls/evidence-child.md +1 -0
- package/src/acp-host/production-host.ts +3 -0
- package/src/acp-host/role-envelope.ts +2 -2
- package/src/acp-host/role-turn-host.ts +9 -1
- package/src/analyst-gate-cycles-read.ts +37 -2
- package/src/archivist-record-entry.ts +45 -1
- package/src/audit-escalation.ts +17 -0
- package/src/auditor-role.ts +22 -0
- package/src/auditor-soul.ts +42 -0
- package/src/compliance-transport.ts +177 -66
- package/src/doctor-auditor.ts +10 -14
- package/src/doctor-contracts.ts +1 -0
- package/src/doctor-role.ts +2 -2
- package/src/evidence-child-role.ts +22 -0
- package/src/gatekeeper-pass-envelope.ts +109 -0
- package/src/gatekeeper-role.ts +145 -104
- package/src/host-contracts.ts +9 -1
- package/src/institutional-resolution.ts +8 -163
- package/src/judge-auditor.ts +10 -15
- package/src/judge-role.ts +8 -0
- package/src/navigator-attendance.ts +25 -9
- package/src/navigator-public-session.ts +217 -0
- package/src/navigator-session-contracts.ts +61 -54
- package/src/notary-source-run.ts +3 -1
- package/src/package-contracts/auditor-output.ts +82 -0
- package/src/package-contracts/evidence-child-output.ts +51 -0
- package/src/package-contracts/judge-output.ts +1 -0
- package/src/package-contracts/reviewer-output.ts +2 -2
- package/src/package-contracts/terminating-tools.ts +31 -1
- package/src/packaged-role-registry.ts +43 -0
- package/src/pi/adapter.ts +2 -1
- package/src/pi/in-process-session.ts +38 -12
- package/src/pi/role-turn-host.ts +34 -0
- package/src/public-cli/cli.ts +14 -6
- package/src/public-cli/instruction-seat-run.ts +289 -50
- package/src/public-cli/invocation.ts +66 -25
- package/src/public-cli/option-definitions.ts +61 -0
- package/src/public-cli/post-admission.ts +7 -1
- package/src/public-cli/registry.ts +4 -1
- package/src/public-cli/run-lifecycle.ts +22 -3
- package/src/public-cli/settlement.ts +120 -5
- package/src/public-cli/terminal.ts +2 -0
- package/src/public-role-summons.ts +431 -0
- package/src/receipt-delivery-policy.ts +10 -0
- package/src/reviewer-agent.ts +1 -1
- package/src/reviewer-child-executor.ts +114 -12
- package/src/reviewer-execution-ledger.ts +3 -2
- package/src/role-runtime.ts +158 -17
- package/src/session-assistant-usage.ts +119 -0
- package/src/session-opening-materials.ts +1 -1
- package/src/shape-unreadable-failure.ts +49 -0
- package/src/submission-errors.ts +3 -0
- package/dist/evidence-child-executor.js +0 -829
- package/src/evidence-child-executor.ts +0 -1140
|
@@ -0,0 +1,387 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Sole nested-volume reader for gate-cycle facts under session/auditor-roles/.
|
|
3
|
+
*
|
|
4
|
+
* Consumers: Analyst sole ledger scan (classifyScopedRun) and Terminal gate
|
|
5
|
+
* projection (#478). Metric families must not open a second disk scan — they
|
|
6
|
+
* consume retained facts. Terminal settlement reuses this same pairing seam
|
|
7
|
+
* (no second auditor-roles scanner).
|
|
8
|
+
*
|
|
9
|
+
* Naming: records may carry pre-#440 menxia/jishizhong/fubaolang tool faces or
|
|
10
|
+
* the current gatekeeper/inspector/notary English face. Projection always uses
|
|
11
|
+
* the current English officer identity (inspector | notary).
|
|
12
|
+
*
|
|
13
|
+
* Missing auditor-roles directory (ENOENT only) → empty rounds (lawful zero).
|
|
14
|
+
* Path present but not a directory (ENOTDIR) and discovered nested JSONL that
|
|
15
|
+
* fails canonical read/parse must fail loudly (never silently under-count).
|
|
16
|
+
* An accepted gate terminating receipt (isError:false pair on dispatch/officer
|
|
17
|
+
* tool) whose required typed facts are unusable — status, dispatch officer, or
|
|
18
|
+
* first/last span missing/unknown/unparseable/inverted — also fails loudly via
|
|
19
|
+
* the same throw→ledger `auditor-roles` unreadable seam. Unknown/non-contract
|
|
20
|
+
* dispatch status stays loud (#475 abolished Gatekeeper incomplete special-case).
|
|
21
|
+
* Lawful province non-dispatch release (`pass` on a dispatch tool) opens no
|
|
22
|
+
* round and must not throw (#597 / ADR 0074 gate-non-mandatory).
|
|
23
|
+
* True non-gate volumes (soul-audit noise, etc.) stay omitted from pairing.
|
|
24
|
+
* Historical dispatch↔officer pairing requires a shared typed
|
|
25
|
+
* `ak_auditor_parent_attempt_binding.parent.attemptEntryId` — never seat/time guessing.
|
|
26
|
+
* An orphan accepted dispatch must not consume a later same-seat direct officer.
|
|
27
|
+
*/
|
|
28
|
+
import { readdir } from "node:fs/promises";
|
|
29
|
+
import { join } from "node:path";
|
|
30
|
+
import { AUDITOR_PARENT_ATTEMPT_BINDING_ENTRY_TYPE, } from "./compliance-transport.js";
|
|
31
|
+
import { extractSessionTimestampSpan, intervalRowsAroundAnchor, readLedgerSessionJsonl, } from "./ledger-session-read.js";
|
|
32
|
+
const DISPATCH_TOOLS = new Set(["ak_menxia_output", "ak_gatekeeper_output"]);
|
|
33
|
+
/** Officer terminating tool → current English officer identity. */
|
|
34
|
+
const OFFICER_TOOL_TO_FACE = {
|
|
35
|
+
ak_jishizhong_output: "inspector",
|
|
36
|
+
ak_inspector_output: "inspector",
|
|
37
|
+
ak_fubaolang_output: "notary",
|
|
38
|
+
ak_notary_output: "notary",
|
|
39
|
+
};
|
|
40
|
+
/** Dispatch `officer` argument aliases → current English face. */
|
|
41
|
+
const OFFICER_ARG_ALIASES = {
|
|
42
|
+
jishizhong: "inspector",
|
|
43
|
+
inspector: "inspector",
|
|
44
|
+
fubaolang: "notary",
|
|
45
|
+
notary: "notary",
|
|
46
|
+
};
|
|
47
|
+
function isRecord(value) {
|
|
48
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
49
|
+
}
|
|
50
|
+
function isParentAttemptBindingRow(row) {
|
|
51
|
+
return row.type === "custom" && row.customType === AUDITOR_PARENT_ATTEMPT_BINDING_ENTRY_TYPE;
|
|
52
|
+
}
|
|
53
|
+
/** One summons' interval via shared #636 binding-slice (parent-attempt markers). */
|
|
54
|
+
function intervalRowsForGateCall(rows, callRowIndex) {
|
|
55
|
+
return intervalRowsAroundAnchor(rows, callRowIndex, isParentAttemptBindingRow).rows;
|
|
56
|
+
}
|
|
57
|
+
/** Only true absence (ENOENT). ENOTDIR is damaged topology — must stay loud. */
|
|
58
|
+
function isMissingDirectoryError(error) {
|
|
59
|
+
return (error instanceof Error
|
|
60
|
+
&& "code" in error
|
|
61
|
+
&& error.code === "ENOENT");
|
|
62
|
+
}
|
|
63
|
+
function normalizeOfficerArg(raw) {
|
|
64
|
+
if (typeof raw !== "string")
|
|
65
|
+
return undefined;
|
|
66
|
+
return OFFICER_ARG_ALIASES[raw.trim()];
|
|
67
|
+
}
|
|
68
|
+
/** Typed string findings only — non-strings dropped; missing/non-array → []. */
|
|
69
|
+
function asStringFindings(value) {
|
|
70
|
+
if (!Array.isArray(value))
|
|
71
|
+
return [];
|
|
72
|
+
return value.filter((item) => typeof item === "string");
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Optional non-empty dispatch reason. Trim only decides emptiness; a non-empty
|
|
76
|
+
* durable reason is returned as written (gatekeeper keeps reason as-is).
|
|
77
|
+
*/
|
|
78
|
+
function optionalDispatchReason(raw) {
|
|
79
|
+
if (typeof raw !== "string")
|
|
80
|
+
return undefined;
|
|
81
|
+
if (raw.trim() === "")
|
|
82
|
+
return undefined;
|
|
83
|
+
return raw;
|
|
84
|
+
}
|
|
85
|
+
function isGateTerminatingToolName(toolName) {
|
|
86
|
+
return DISPATCH_TOOLS.has(toolName) || OFFICER_TOOL_TO_FACE[toolName] !== undefined;
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* toolCallIds whose paired toolResult is an accepted receipt (`isError === false`).
|
|
90
|
+
* Receipt is the sole lawful role product — rejected / missing results never qualify.
|
|
91
|
+
*/
|
|
92
|
+
function acceptedGateReceiptIds(rows) {
|
|
93
|
+
const accepted = new Set();
|
|
94
|
+
for (const row of rows) {
|
|
95
|
+
const message = isRecord(row.message) ? row.message : undefined;
|
|
96
|
+
if (message?.role !== "toolResult")
|
|
97
|
+
continue;
|
|
98
|
+
if (typeof message.toolCallId !== "string" || message.toolCallId.length === 0)
|
|
99
|
+
continue;
|
|
100
|
+
if (message.isError === false)
|
|
101
|
+
accepted.add(message.toolCallId);
|
|
102
|
+
}
|
|
103
|
+
return accepted;
|
|
104
|
+
}
|
|
105
|
+
function requireAcceptedGateStatus(args, filePath) {
|
|
106
|
+
if (args === undefined || typeof args.status !== "string" || args.status.trim() === "") {
|
|
107
|
+
throw new Error(`accepted gate receipt missing usable status in ${filePath}`);
|
|
108
|
+
}
|
|
109
|
+
return args.status.trim();
|
|
110
|
+
}
|
|
111
|
+
function requireAcceptedGateSpan(rows, filePath) {
|
|
112
|
+
const span = extractSessionTimestampSpan(rows);
|
|
113
|
+
if (span.startedAt === undefined || span.endedAt === undefined) {
|
|
114
|
+
throw new Error(`accepted gate volume missing session timestamp span in ${filePath}`);
|
|
115
|
+
}
|
|
116
|
+
const startedMs = Date.parse(span.startedAt);
|
|
117
|
+
const endedMs = Date.parse(span.endedAt);
|
|
118
|
+
if (!Number.isFinite(startedMs) || !Number.isFinite(endedMs) || endedMs < startedMs) {
|
|
119
|
+
throw new Error(`accepted gate volume has unusable timestamp span in ${filePath}`);
|
|
120
|
+
}
|
|
121
|
+
return {
|
|
122
|
+
startedAt: span.startedAt,
|
|
123
|
+
endedAt: span.endedAt,
|
|
124
|
+
wallMs: endedMs - startedMs,
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
/** Nearest preceding attempt binding (id + parent session file) before row index. */
|
|
128
|
+
function nearestAttemptBindingBefore(rows, beforeIndex) {
|
|
129
|
+
for (let i = beforeIndex - 1; i >= 0; i -= 1) {
|
|
130
|
+
const row = rows[i];
|
|
131
|
+
if (!isParentAttemptBindingRow(row))
|
|
132
|
+
continue;
|
|
133
|
+
if (!isRecord(row.data) || !isRecord(row.data.parent))
|
|
134
|
+
continue;
|
|
135
|
+
const id = row.data.parent.attemptEntryId;
|
|
136
|
+
const sessionFile = row.data.parent.sessionFile;
|
|
137
|
+
return {
|
|
138
|
+
...(typeof id === "string" && id.length > 0 ? { attemptEntryId: id } : {}),
|
|
139
|
+
...(typeof sessionFile === "string" && sessionFile.length > 0
|
|
140
|
+
? { parentSessionFile: sessionFile }
|
|
141
|
+
: {}),
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
return {};
|
|
145
|
+
}
|
|
146
|
+
/**
|
|
147
|
+
* All accepted gate terminating toolCalls on a volume (dispatch or officer),
|
|
148
|
+
* each with nearest preceding attempt binding. Ticket-seat continuous memory
|
|
149
|
+
* (#636) may carry multiple parent summons in one file — last-only would drop rounds.
|
|
150
|
+
*/
|
|
151
|
+
function extractAllAcceptedGateToolCalls(rows) {
|
|
152
|
+
const acceptedIds = acceptedGateReceiptIds(rows);
|
|
153
|
+
const out = [];
|
|
154
|
+
for (let rowIndex = 0; rowIndex < rows.length; rowIndex += 1) {
|
|
155
|
+
const row = rows[rowIndex];
|
|
156
|
+
const message = isRecord(row.message) ? row.message : undefined;
|
|
157
|
+
if (message?.role !== "assistant" || !Array.isArray(message.content))
|
|
158
|
+
continue;
|
|
159
|
+
for (const part of message.content) {
|
|
160
|
+
if (!isRecord(part) || part.type !== "toolCall")
|
|
161
|
+
continue;
|
|
162
|
+
if (typeof part.id !== "string" || part.id.length === 0)
|
|
163
|
+
continue;
|
|
164
|
+
if (typeof part.name !== "string" || part.name.length === 0)
|
|
165
|
+
continue;
|
|
166
|
+
if (!isGateTerminatingToolName(part.name))
|
|
167
|
+
continue;
|
|
168
|
+
if (!acceptedIds.has(part.id))
|
|
169
|
+
continue;
|
|
170
|
+
const binding = nearestAttemptBindingBefore(rows, rowIndex);
|
|
171
|
+
out.push({
|
|
172
|
+
toolName: part.name,
|
|
173
|
+
args: isRecord(part.arguments) ? part.arguments : undefined,
|
|
174
|
+
accepted: true,
|
|
175
|
+
rowIndex,
|
|
176
|
+
...binding,
|
|
177
|
+
});
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
return out;
|
|
181
|
+
}
|
|
182
|
+
function projectAcceptedGateCall(filePath, rows, call) {
|
|
183
|
+
// Continuous memory volumes carry many summons; span only this binding's interval.
|
|
184
|
+
const span = requireAcceptedGateSpan(intervalRowsForGateCall(rows, call.rowIndex), filePath);
|
|
185
|
+
const status = requireAcceptedGateStatus(call.args, filePath);
|
|
186
|
+
const findings = asStringFindings(call.args?.findings);
|
|
187
|
+
const findingsCount = findings.length;
|
|
188
|
+
const bindingFields = {
|
|
189
|
+
...(call.attemptEntryId === undefined ? {} : { attemptEntryId: call.attemptEntryId }),
|
|
190
|
+
...(call.parentSessionFile === undefined ? {} : { parentSessionFile: call.parentSessionFile }),
|
|
191
|
+
};
|
|
192
|
+
if (DISPATCH_TOOLS.has(call.toolName)) {
|
|
193
|
+
// Lawful province non-dispatch release — opens no round, never unreadable (#597).
|
|
194
|
+
if (status === "pass") {
|
|
195
|
+
return undefined;
|
|
196
|
+
}
|
|
197
|
+
// Pairing terminal is dispatch; unknown/non-contract status stays loud (#475).
|
|
198
|
+
if (status !== "dispatch") {
|
|
199
|
+
throw new Error(`accepted dispatch receipt has non-dispatch status ${JSON.stringify(status)} in ${filePath}`);
|
|
200
|
+
}
|
|
201
|
+
const officer = normalizeOfficerArg(call.args?.officer);
|
|
202
|
+
if (officer === undefined) {
|
|
203
|
+
throw new Error(`accepted dispatch receipt missing or unknown officer in ${filePath}`);
|
|
204
|
+
}
|
|
205
|
+
const reason = optionalDispatchReason(call.args?.reason);
|
|
206
|
+
return {
|
|
207
|
+
kind: "dispatch",
|
|
208
|
+
startedAt: span.startedAt,
|
|
209
|
+
officer,
|
|
210
|
+
status,
|
|
211
|
+
...(reason === undefined ? {} : { reason }),
|
|
212
|
+
...bindingFields,
|
|
213
|
+
};
|
|
214
|
+
}
|
|
215
|
+
const officer = OFFICER_TOOL_TO_FACE[call.toolName];
|
|
216
|
+
if (officer === undefined) {
|
|
217
|
+
// isGateTerminatingToolName already screened; keep loud if tables drift.
|
|
218
|
+
throw new Error(`accepted gate receipt has unknown officer tool ${call.toolName} in ${filePath}`);
|
|
219
|
+
}
|
|
220
|
+
return {
|
|
221
|
+
kind: "officer",
|
|
222
|
+
startedAt: span.startedAt,
|
|
223
|
+
endedAt: span.endedAt,
|
|
224
|
+
officer,
|
|
225
|
+
status,
|
|
226
|
+
findings,
|
|
227
|
+
findingsCount,
|
|
228
|
+
officerWallMs: span.wallMs,
|
|
229
|
+
...bindingFields,
|
|
230
|
+
};
|
|
231
|
+
}
|
|
232
|
+
async function classifyAuditorVolume(filePath) {
|
|
233
|
+
// Canonical JSONL errors propagate — failure honesty (never wash to fewer rounds).
|
|
234
|
+
const rows = await readLedgerSessionJsonl(filePath);
|
|
235
|
+
const accepted = extractAllAcceptedGateToolCalls(rows);
|
|
236
|
+
// Rejected-only / non-gate volumes omit (empty accepted set).
|
|
237
|
+
if (accepted.length === 0)
|
|
238
|
+
return [];
|
|
239
|
+
const volumes = [];
|
|
240
|
+
for (const call of accepted) {
|
|
241
|
+
const projected = projectAcceptedGateCall(filePath, rows, call);
|
|
242
|
+
if (projected !== undefined)
|
|
243
|
+
volumes.push(projected);
|
|
244
|
+
}
|
|
245
|
+
return volumes;
|
|
246
|
+
}
|
|
247
|
+
function pairGateRounds(volumes) {
|
|
248
|
+
const ordered = [...volumes].sort((a, b) => {
|
|
249
|
+
if (a.startedAt !== b.startedAt)
|
|
250
|
+
return a.startedAt.localeCompare(b.startedAt);
|
|
251
|
+
// Stable tie-break: dispatch before officer at identical start (should not happen).
|
|
252
|
+
if (a.kind !== b.kind)
|
|
253
|
+
return a.kind === "dispatch" ? -1 : 1;
|
|
254
|
+
return 0;
|
|
255
|
+
});
|
|
256
|
+
const usedOfficerIdx = new Set();
|
|
257
|
+
const rounds = [];
|
|
258
|
+
for (let i = 0; i < ordered.length; i += 1) {
|
|
259
|
+
const vol = ordered[i];
|
|
260
|
+
if (vol.kind !== "dispatch")
|
|
261
|
+
continue;
|
|
262
|
+
let match;
|
|
263
|
+
for (let j = i + 1; j < ordered.length; j += 1) {
|
|
264
|
+
if (usedOfficerIdx.has(j))
|
|
265
|
+
continue;
|
|
266
|
+
const candidate = ordered[j];
|
|
267
|
+
if (candidate.kind !== "officer")
|
|
268
|
+
continue;
|
|
269
|
+
if (candidate.officer !== vol.officer)
|
|
270
|
+
continue;
|
|
271
|
+
// Durable invocation association — seat/time alone must not pair.
|
|
272
|
+
if (vol.attemptEntryId === undefined
|
|
273
|
+
|| candidate.attemptEntryId === undefined
|
|
274
|
+
|| candidate.attemptEntryId !== vol.attemptEntryId) {
|
|
275
|
+
continue;
|
|
276
|
+
}
|
|
277
|
+
match = { index: j, officer: candidate };
|
|
278
|
+
break;
|
|
279
|
+
}
|
|
280
|
+
if (match === undefined)
|
|
281
|
+
continue;
|
|
282
|
+
usedOfficerIdx.add(match.index);
|
|
283
|
+
rounds.push({
|
|
284
|
+
roundIndex: rounds.length + 1,
|
|
285
|
+
officer: match.officer.officer,
|
|
286
|
+
status: match.officer.status,
|
|
287
|
+
officerWallMs: match.officer.officerWallMs,
|
|
288
|
+
officerStartedAt: match.officer.startedAt,
|
|
289
|
+
officerEndedAt: match.officer.endedAt,
|
|
290
|
+
findings: match.officer.findings,
|
|
291
|
+
findingsCount: match.officer.findingsCount,
|
|
292
|
+
origin: {
|
|
293
|
+
kind: "historical_dispatch",
|
|
294
|
+
...(vol.reason === undefined ? {} : { reason: vol.reason }),
|
|
295
|
+
},
|
|
296
|
+
});
|
|
297
|
+
}
|
|
298
|
+
// Current direct-summons volumes have no preceding province dispatch. Every
|
|
299
|
+
// accepted officer receipt not consumed by a historical pair is its own round.
|
|
300
|
+
for (let i = 0; i < ordered.length; i += 1) {
|
|
301
|
+
const vol = ordered[i];
|
|
302
|
+
if (vol.kind !== "officer" || usedOfficerIdx.has(i))
|
|
303
|
+
continue;
|
|
304
|
+
rounds.push({
|
|
305
|
+
roundIndex: 0,
|
|
306
|
+
officer: vol.officer,
|
|
307
|
+
status: vol.status,
|
|
308
|
+
officerWallMs: vol.officerWallMs,
|
|
309
|
+
officerStartedAt: vol.startedAt,
|
|
310
|
+
officerEndedAt: vol.endedAt,
|
|
311
|
+
findings: vol.findings,
|
|
312
|
+
findingsCount: vol.findingsCount,
|
|
313
|
+
origin: { kind: "direct" },
|
|
314
|
+
});
|
|
315
|
+
}
|
|
316
|
+
return rounds
|
|
317
|
+
.sort((a, b) => a.officerStartedAt.localeCompare(b.officerStartedAt))
|
|
318
|
+
.map((round, index) => ({ ...round, roundIndex: index + 1 }));
|
|
319
|
+
}
|
|
320
|
+
/** Resolve a direct-officer-run-pointer file to the officer session 正本 path. */
|
|
321
|
+
async function resolveOfficerSessionFromPointerFile(pointerPath) {
|
|
322
|
+
const { readFile } = await import("node:fs/promises");
|
|
323
|
+
let raw;
|
|
324
|
+
try {
|
|
325
|
+
raw = JSON.parse(await readFile(pointerPath, "utf8"));
|
|
326
|
+
}
|
|
327
|
+
catch (error) {
|
|
328
|
+
throw new Error(`direct officer run pointer unreadable in ${pointerPath}: ${error instanceof Error ? error.message : String(error)}`, { cause: error });
|
|
329
|
+
}
|
|
330
|
+
if (!isRecord(raw) || raw.kind !== "direct-officer-run-pointer" || raw.version !== 1) {
|
|
331
|
+
throw new Error(`direct officer run pointer has unknown shape in ${pointerPath}`);
|
|
332
|
+
}
|
|
333
|
+
const sessionFile = raw.sessionFile;
|
|
334
|
+
if (typeof sessionFile !== "string" || sessionFile.trim() === "") {
|
|
335
|
+
throw new Error(`direct officer run pointer missing sessionFile in ${pointerPath}`);
|
|
336
|
+
}
|
|
337
|
+
return sessionFile;
|
|
338
|
+
}
|
|
339
|
+
/**
|
|
340
|
+
* Read and pair gate-cycle rounds from one or more auditor-roles directories.
|
|
341
|
+
* Accepts historical nested JSONL volumes and #675 direct-officer-run-pointer
|
|
342
|
+
* files that name the independent officer session 正本.
|
|
343
|
+
* ENOENT (directory truly absent) → []. ENOTDIR and other errors propagate
|
|
344
|
+
* (failure honesty — damaged topology must not wash to zero rounds).
|
|
345
|
+
*
|
|
346
|
+
* Pass `parentSessionFile` to keep only rounds whose attempt binding names that parent.
|
|
347
|
+
*/
|
|
348
|
+
export async function readAnalystGateCyclesFromAuditorRoles(auditorRolesDirectory, options = {}) {
|
|
349
|
+
const directories = typeof auditorRolesDirectory === "string"
|
|
350
|
+
? [auditorRolesDirectory]
|
|
351
|
+
: auditorRolesDirectory;
|
|
352
|
+
const volumes = [];
|
|
353
|
+
for (const directory of directories) {
|
|
354
|
+
let names;
|
|
355
|
+
try {
|
|
356
|
+
const entries = await readdir(directory, { withFileTypes: true });
|
|
357
|
+
names = entries
|
|
358
|
+
.filter((e) => e.isFile()
|
|
359
|
+
&& (e.name.endsWith(".jsonl") || e.name.endsWith(".pointer.json")))
|
|
360
|
+
.map((e) => e.name)
|
|
361
|
+
.sort();
|
|
362
|
+
}
|
|
363
|
+
catch (error) {
|
|
364
|
+
if (isMissingDirectoryError(error))
|
|
365
|
+
continue;
|
|
366
|
+
throw error;
|
|
367
|
+
}
|
|
368
|
+
for (const name of names) {
|
|
369
|
+
const path = join(directory, name);
|
|
370
|
+
const sessionPath = name.endsWith(".pointer.json")
|
|
371
|
+
? await resolveOfficerSessionFromPointerFile(path)
|
|
372
|
+
: path;
|
|
373
|
+
if (sessionPath === undefined)
|
|
374
|
+
continue;
|
|
375
|
+
const classified = await classifyAuditorVolume(sessionPath);
|
|
376
|
+
for (const volume of classified) {
|
|
377
|
+
if (options.parentSessionFile !== undefined &&
|
|
378
|
+
volume.parentSessionFile !== undefined &&
|
|
379
|
+
volume.parentSessionFile !== options.parentSessionFile) {
|
|
380
|
+
continue;
|
|
381
|
+
}
|
|
382
|
+
volumes.push(volume);
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
return pairGateRounds(volumes);
|
|
387
|
+
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { existsSync, readFileSync, realpathSync, writeFileSync } from "node:fs";
|
|
1
|
+
import { existsSync, mkdirSync, readFileSync, realpathSync, writeFileSync } from "node:fs";
|
|
2
2
|
import { dirname, resolve, join } from "node:path";
|
|
3
3
|
import { SessionManager } from "@earendil-works/pi-coding-agent";
|
|
4
4
|
import { resolveBookKeyFromGit } from "./activation-ledger-git.js";
|
|
@@ -130,8 +130,29 @@ function createRecordSessionOpen(options) {
|
|
|
130
130
|
function createRecordSession(options) {
|
|
131
131
|
return createRecordSessionOpen(options).session;
|
|
132
132
|
}
|
|
133
|
+
const DIRECT_OFFICER_RUN_POINTER_KIND = "direct-officer-run-pointer";
|
|
134
|
+
function bookDirectOfficerRunPointer(options) {
|
|
135
|
+
const nest = join(dirname(options.parentSessionFile), "auditor-roles");
|
|
136
|
+
mkdirSync(nest, { recursive: true });
|
|
137
|
+
const pointer = {
|
|
138
|
+
version: 1,
|
|
139
|
+
kind: DIRECT_OFFICER_RUN_POINTER_KIND,
|
|
140
|
+
officer: options.officer,
|
|
141
|
+
sessionFile: options.sessionFile,
|
|
142
|
+
...options.runDirectory !== void 0 && options.runDirectory.trim() !== "" ? { runDirectory: options.runDirectory } : {}
|
|
143
|
+
};
|
|
144
|
+
writeFileSync(
|
|
145
|
+
join(nest, `${options.officer}-${Date.now().toString(36)}.pointer.json`),
|
|
146
|
+
`${JSON.stringify(pointer)}
|
|
147
|
+
`,
|
|
148
|
+
"utf8"
|
|
149
|
+
);
|
|
150
|
+
return pointer;
|
|
151
|
+
}
|
|
133
152
|
export {
|
|
153
|
+
DIRECT_OFFICER_RUN_POINTER_KIND,
|
|
134
154
|
WORKER_SUBMISSION_GATE_KIND,
|
|
155
|
+
bookDirectOfficerRunPointer,
|
|
135
156
|
createRecordSession,
|
|
136
157
|
createRecordSessionOpen,
|
|
137
158
|
subjectKeyedRecordDirectory2 as subjectKeyedRecordDirectory
|
package/dist/audit-escalation.js
CHANGED
|
@@ -121,6 +121,12 @@ export async function disposeComplianceDecision(decision, handlers, deliveredOut
|
|
|
121
121
|
throw new Error("Compliance no-receipt projection handler is unavailable");
|
|
122
122
|
}
|
|
123
123
|
return await handlers.noReceipt(decision, decision.usage === undefined ? {} : { usage: decision.usage });
|
|
124
|
+
case "unreadable":
|
|
125
|
+
// Parent candidate stands; audit shape failure is a typed parallel fact — not pass.
|
|
126
|
+
if (handlers.unreadable === undefined) {
|
|
127
|
+
throw new Error("Compliance unreadable projection handler is unavailable");
|
|
128
|
+
}
|
|
129
|
+
return await handlers.unreadable(decision, decision.usage === undefined ? {} : { usage: decision.usage });
|
|
124
130
|
case "revise":
|
|
125
131
|
return await handlers.revise(decision.violations);
|
|
126
132
|
case "escalate":
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { joinPackageMaterials, readPackageMaterial, } from "./session-opening-materials.js";
|
|
2
|
+
/**
|
|
3
|
+
* Active auditor seats.
|
|
4
|
+
* Fixer LLM auditor retired by #242; reviewer-side 审刑院 gate retired by #495 S6.
|
|
5
|
+
* souls/fixer-auditor.md and souls/reviewer-auditor.md retained-or-deleted on disk as owner decisions; not active.
|
|
6
|
+
*/
|
|
7
|
+
export const AUDITOR_SOUL_ROLES = [
|
|
8
|
+
"judge",
|
|
9
|
+
"doctor",
|
|
10
|
+
];
|
|
11
|
+
/**
|
|
12
|
+
* Audited-subject input for public 审刑院 (#675 owner):
|
|
13
|
+
* 「审的是谁」is an input selecting judge-auditor.md / doctor-auditor.md —
|
|
14
|
+
* not a caller-identity fork. Same env for direct `ak-role auditor --subject`
|
|
15
|
+
* and nested compliance summons.
|
|
16
|
+
*/
|
|
17
|
+
export const AK_ROLE_AUDITOR_SUBJECT_ENV = "AK_ROLE_AUDITOR_SUBJECT";
|
|
18
|
+
/**
|
|
19
|
+
* Audited source-run input for public 审刑院 (#675):
|
|
20
|
+
* same --source-run face for direct `ak-role auditor` and nested compliance summons.
|
|
21
|
+
* Never falls back to the auditor's own run directory.
|
|
22
|
+
*/
|
|
23
|
+
export const AK_ROLE_AUDITOR_SOURCE_RUN_ENV = "AK_ROLE_AUDITOR_SOURCE_RUN";
|
|
24
|
+
function auditorSoulRelativePath(role) {
|
|
25
|
+
return `souls/${role}-auditor.md`;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* #470 auditor session materials. Judge carries audit-law + quality-law; doctor does
|
|
29
|
+
* not (御批四: 参审席 = 大理寺主会话 + 其审计席; 太医线不动).
|
|
30
|
+
* Reviewer auditor roster removed with #495 S6 gate retirement.
|
|
31
|
+
* #675 owner: no generic auditor.md — subject selects this table.
|
|
32
|
+
*/
|
|
33
|
+
export const AUDITOR_SESSION_MATERIALS = {
|
|
34
|
+
judge: [
|
|
35
|
+
"CLAUDE.md",
|
|
36
|
+
"souls/judge-auditor.md",
|
|
37
|
+
"souls/audit-law.md",
|
|
38
|
+
"souls/quality-law.md",
|
|
39
|
+
],
|
|
40
|
+
doctor: ["CLAUDE.md", "souls/doctor-auditor.md"],
|
|
41
|
+
};
|
|
42
|
+
export function isAuditorSoulRole(value) {
|
|
43
|
+
return value === "judge" || value === "doctor";
|
|
44
|
+
}
|
|
45
|
+
/** Resolve audited subject from explicit value or the subject-input env. */
|
|
46
|
+
export function resolveAuditorSubject(raw) {
|
|
47
|
+
const value = typeof raw === "string" && raw.trim() !== ""
|
|
48
|
+
? raw.trim()
|
|
49
|
+
: typeof process.env[AK_ROLE_AUDITOR_SUBJECT_ENV] === "string"
|
|
50
|
+
? process.env[AK_ROLE_AUDITOR_SUBJECT_ENV].trim()
|
|
51
|
+
: "";
|
|
52
|
+
if (!isAuditorSoulRole(value)) {
|
|
53
|
+
throw new Error(`auditor subject must be judge|doctor (input --subject / ${AK_ROLE_AUDITOR_SUBJECT_ENV}), got ${value === "" ? "(missing)" : value}`);
|
|
54
|
+
}
|
|
55
|
+
return value;
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Load one complete auditor session afresh for each audit invocation.
|
|
59
|
+
* Blank-soul identity stays owned here; composition reuses joinPackageMaterials.
|
|
60
|
+
* Subject selects the materials table (#675 owner — same for direct and nested).
|
|
61
|
+
*/
|
|
62
|
+
export async function loadAuditorSoul(role) {
|
|
63
|
+
const materials = AUDITOR_SESSION_MATERIALS[role];
|
|
64
|
+
const soulPath = auditorSoulRelativePath(role);
|
|
65
|
+
const soul = await readPackageMaterial(soulPath);
|
|
66
|
+
if (soul.trim().length === 0) {
|
|
67
|
+
throw new Error(`The ${role} auditor Soul is blank`);
|
|
68
|
+
}
|
|
69
|
+
return joinPackageMaterials(materials);
|
|
70
|
+
}
|
|
71
|
+
/** Runtime loader: subject input decides which soul file to assemble. */
|
|
72
|
+
export async function loadAuditorSoulFromSubjectInput(raw) {
|
|
73
|
+
return loadAuditorSoul(resolveAuditorSubject(raw));
|
|
74
|
+
}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { readFile } from "node:fs/promises";
|
|
3
|
+
export const COLLECTOR_HOST = "github.com";
|
|
4
|
+
export const COLLECTOR_OWNER_PATTERN = /^[A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?$/;
|
|
5
|
+
export const COLLECTOR_REPO_PATTERN = /^[A-Za-z0-9](?:[A-Za-z0-9._-]{0,98}[A-Za-z0-9])?$/;
|
|
6
|
+
export const COLLECTOR_FIXED_KICKOFF = "采集目标已受理,本局开始。";
|
|
7
|
+
function fail(message, cause) {
|
|
8
|
+
throw new Error(message, cause === undefined ? undefined : { cause });
|
|
9
|
+
}
|
|
10
|
+
function conservativeAscii(input) {
|
|
11
|
+
for (let i = 0; i < input.length; i += 1) {
|
|
12
|
+
const code = input.charCodeAt(i);
|
|
13
|
+
if (code <= 0x1f || code === 0x7f || code > 0x7f)
|
|
14
|
+
return false;
|
|
15
|
+
}
|
|
16
|
+
return true;
|
|
17
|
+
}
|
|
18
|
+
export function parseCollectorRepository(raw) {
|
|
19
|
+
if (typeof raw !== "string" || raw.trim() !== raw || raw.length === 0)
|
|
20
|
+
fail("Collector repository must be a string owner/repo");
|
|
21
|
+
if (!conservativeAscii(raw) || raw.includes("://") || /[?#@%\\ ]/.test(raw))
|
|
22
|
+
fail("Collector repository rejects URL syntax and non-identity bytes");
|
|
23
|
+
const parts = raw.split("/");
|
|
24
|
+
if (parts.length !== 2)
|
|
25
|
+
fail("Collector repository must contain exactly one '/' separating owner and repo");
|
|
26
|
+
const [ownerDisplay, repoDisplay] = parts;
|
|
27
|
+
if (!COLLECTOR_OWNER_PATTERN.test(ownerDisplay) || !COLLECTOR_REPO_PATTERN.test(repoDisplay))
|
|
28
|
+
fail("Collector repository does not match the conservative owner/repo grammar");
|
|
29
|
+
const owner = ownerDisplay.toLowerCase();
|
|
30
|
+
const repo = repoDisplay.toLowerCase();
|
|
31
|
+
return { display: raw, canonical: `${owner}/${repo}`, owner, repo };
|
|
32
|
+
}
|
|
33
|
+
export function parseCollectorPrNumber(raw) {
|
|
34
|
+
if (typeof raw === "string" && !/^[1-9][0-9]*$/.test(raw))
|
|
35
|
+
fail("Collector pull request number must be a positive safe integer string");
|
|
36
|
+
if (typeof raw !== "string" && typeof raw !== "number")
|
|
37
|
+
fail("Collector pull request number is required");
|
|
38
|
+
const value = Number(raw);
|
|
39
|
+
if (!Number.isSafeInteger(value) || value < 1)
|
|
40
|
+
fail("Collector pull request number must be a positive safe integer");
|
|
41
|
+
return value;
|
|
42
|
+
}
|
|
43
|
+
function record(value) {
|
|
44
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
45
|
+
}
|
|
46
|
+
function canonicalManifest(requests) {
|
|
47
|
+
return `${JSON.stringify({ requests: requests.map((request) => ({ id: request.id, body: request.requestBody })) })}\n`;
|
|
48
|
+
}
|
|
49
|
+
export function emptyCollectorManifest() {
|
|
50
|
+
const canonicalJson = canonicalManifest([]);
|
|
51
|
+
return { requests: [], canonicalJson, digest: createHash("sha256").update(canonicalJson).digest("hex") };
|
|
52
|
+
}
|
|
53
|
+
/** Optional request configuration. It names requests, never expected observers. */
|
|
54
|
+
export async function loadCollectorManifest(path) {
|
|
55
|
+
let bytes;
|
|
56
|
+
try {
|
|
57
|
+
bytes = await readFile(path);
|
|
58
|
+
}
|
|
59
|
+
catch (error) {
|
|
60
|
+
fail(`Collector request manifest is unreadable at ${path}`, error);
|
|
61
|
+
}
|
|
62
|
+
let parsed;
|
|
63
|
+
try {
|
|
64
|
+
parsed = JSON.parse(new TextDecoder("utf-8", { fatal: true }).decode(bytes));
|
|
65
|
+
}
|
|
66
|
+
catch (error) {
|
|
67
|
+
fail("Collector request manifest must be UTF-8 JSON", error);
|
|
68
|
+
}
|
|
69
|
+
if (!record(parsed))
|
|
70
|
+
fail("Collector request manifest must be an object");
|
|
71
|
+
const rawRequests = parsed.requests ?? [];
|
|
72
|
+
if (!Array.isArray(rawRequests))
|
|
73
|
+
fail("Collector request manifest requests must be an array");
|
|
74
|
+
const requests = [];
|
|
75
|
+
const ids = new Set();
|
|
76
|
+
for (const [index, item] of rawRequests.entries()) {
|
|
77
|
+
if (!record(item) || typeof item.id !== "string" || item.id.length === 0 || typeof item.body !== "string" || item.body.trim() === "")
|
|
78
|
+
fail(`Collector request manifest requests[${index}] is invalid`);
|
|
79
|
+
if (ids.has(item.id))
|
|
80
|
+
fail(`Collector request manifest has duplicate request id "${item.id}"`);
|
|
81
|
+
ids.add(item.id);
|
|
82
|
+
requests.push({ id: item.id, requestBody: item.body });
|
|
83
|
+
}
|
|
84
|
+
const canonicalJson = canonicalManifest(requests);
|
|
85
|
+
return { requests, canonicalJson, digest: createHash("sha256").update(canonicalJson).digest("hex"), sourcePath: path };
|
|
86
|
+
}
|