@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,340 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 起居录 volume helpers — ADR 0075 / #582.
|
|
3
|
+
* Write/read via sitian facade only; no parallel destination logic.
|
|
4
|
+
*/
|
|
5
|
+
import { createHash } from "node:crypto";
|
|
6
|
+
import { appendFileSync, existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
7
|
+
import { join } from "node:path";
|
|
8
|
+
import { resolveSitianRecordPath, sitianReport, readSitianRecords, } from "./sitian-facade.js";
|
|
9
|
+
import { TICKET_PROVENANCE_HUMAN_VIEW, TICKET_PROVENANCE_KIND, TICKET_PROVENANCE_OFFERED_WATERMARK, TICKET_PROVENANCE_RECORD_CLASS_DIAGNOSTIC, projectTicketProvenanceDiagnostic, projectTicketProvenanceEntry, } from "./ticket-provenance-contracts.js";
|
|
10
|
+
/** Subject string for ticket-keyed volumes — history follows the ticket. */
|
|
11
|
+
export function ticketProvenanceSubject(ticketNumber) {
|
|
12
|
+
if (!Number.isSafeInteger(ticketNumber) || ticketNumber < 1) {
|
|
13
|
+
throw new Error(`ticket-provenance subject requires a positive ticket number, got ${String(ticketNumber)}`);
|
|
14
|
+
}
|
|
15
|
+
return String(ticketNumber);
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Deterministic entry identity for sitian entry-level idempotency.
|
|
19
|
+
* Same ticket + source pointer + transcript → same identity → no re-append.
|
|
20
|
+
*/
|
|
21
|
+
export function ticketProvenanceEntryIdentity(input) {
|
|
22
|
+
const ref = input.sourceRef;
|
|
23
|
+
const refKey = [
|
|
24
|
+
ref.sessionFile ?? "",
|
|
25
|
+
ref.entryId === undefined ? "" : String(ref.entryId),
|
|
26
|
+
ref.path ?? "",
|
|
27
|
+
ref.url ?? "",
|
|
28
|
+
].join("\u0001");
|
|
29
|
+
const material = [
|
|
30
|
+
String(input.ticketNumber),
|
|
31
|
+
input.sourceKind,
|
|
32
|
+
refKey,
|
|
33
|
+
input.transcript,
|
|
34
|
+
].join("\u0000");
|
|
35
|
+
return createHash("sha256").update(material, "utf8").digest("hex");
|
|
36
|
+
}
|
|
37
|
+
/** Append one transcribed block; returns existing pointer on identity hit. */
|
|
38
|
+
export function appendTicketProvenanceEntry(input) {
|
|
39
|
+
const subject = ticketProvenanceSubject(input.ticketNumber);
|
|
40
|
+
const identity = ticketProvenanceEntryIdentity({
|
|
41
|
+
ticketNumber: input.ticketNumber,
|
|
42
|
+
sourceKind: input.entry.sourceKind,
|
|
43
|
+
sourceRef: input.entry.sourceRef,
|
|
44
|
+
transcript: input.entry.transcript,
|
|
45
|
+
});
|
|
46
|
+
return sitianReport({
|
|
47
|
+
level: "event",
|
|
48
|
+
kind: TICKET_PROVENANCE_KIND,
|
|
49
|
+
identity,
|
|
50
|
+
subject,
|
|
51
|
+
cwd: input.cwd,
|
|
52
|
+
...(input.home === undefined ? {} : { home: input.home }),
|
|
53
|
+
host: input.host ?? "diarist",
|
|
54
|
+
source: input.source ?? "diarist",
|
|
55
|
+
payload: input.entry,
|
|
56
|
+
raw: input.entry.sourceRef.sessionFile !== undefined &&
|
|
57
|
+
input.entry.sourceRef.entryId !== undefined
|
|
58
|
+
? {
|
|
59
|
+
sessionFile: input.entry.sourceRef.sessionFile,
|
|
60
|
+
entryId: input.entry.sourceRef.entryId,
|
|
61
|
+
}
|
|
62
|
+
: undefined,
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
/** Resolve volume paths for a ticket without writing. */
|
|
66
|
+
export function resolveTicketProvenanceVolume(ticketNumber, cwd, home) {
|
|
67
|
+
const path = resolveSitianRecordPath({
|
|
68
|
+
level: "event",
|
|
69
|
+
kind: TICKET_PROVENANCE_KIND,
|
|
70
|
+
subject: ticketProvenanceSubject(ticketNumber),
|
|
71
|
+
cwd,
|
|
72
|
+
...(home === undefined ? {} : { home }),
|
|
73
|
+
});
|
|
74
|
+
return {
|
|
75
|
+
recordFile: path.recordFile,
|
|
76
|
+
volumeDir: path.sessionDir,
|
|
77
|
+
humanViewFile: join(path.sessionDir, TICKET_PROVENANCE_HUMAN_VIEW),
|
|
78
|
+
offeredWatermarkFile: join(path.sessionDir, TICKET_PROVENANCE_OFFERED_WATERMARK),
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
/** Honest failure when the offered-identity watermark cannot be read as written. */
|
|
82
|
+
export class TicketProvenanceWatermarkError extends Error {
|
|
83
|
+
code = "ticket-provenance-watermark";
|
|
84
|
+
reason;
|
|
85
|
+
constructor(reason, message, options) {
|
|
86
|
+
super(message, options);
|
|
87
|
+
this.name = "TicketProvenanceWatermarkError";
|
|
88
|
+
this.reason = reason;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Read identities already offered to the collector (append-only watermark).
|
|
93
|
+
* Absent file → empty set. Any non-empty line that is not a JSON object with a
|
|
94
|
+
* non-empty string `identity` throws TicketProvenanceWatermarkError (失败诚实:
|
|
95
|
+
* never interpret corruption as "unseen" and silently re-offer).
|
|
96
|
+
*/
|
|
97
|
+
export function readOfferedIdentities(ticketNumber, cwd, home) {
|
|
98
|
+
const { offeredWatermarkFile } = resolveTicketProvenanceVolume(ticketNumber, cwd, home);
|
|
99
|
+
if (!existsSync(offeredWatermarkFile))
|
|
100
|
+
return new Set();
|
|
101
|
+
let text;
|
|
102
|
+
try {
|
|
103
|
+
text = readFileSync(offeredWatermarkFile, "utf8");
|
|
104
|
+
}
|
|
105
|
+
catch (error) {
|
|
106
|
+
throw new TicketProvenanceWatermarkError("unreadable", `ticket-provenance offered watermark unreadable (${offeredWatermarkFile})`, { cause: error });
|
|
107
|
+
}
|
|
108
|
+
const seen = new Set();
|
|
109
|
+
const lines = text.split("\n");
|
|
110
|
+
for (let index = 0; index < lines.length; index += 1) {
|
|
111
|
+
const trimmed = lines[index].trim();
|
|
112
|
+
if (!trimmed)
|
|
113
|
+
continue;
|
|
114
|
+
let parsed;
|
|
115
|
+
try {
|
|
116
|
+
parsed = JSON.parse(trimmed);
|
|
117
|
+
}
|
|
118
|
+
catch (error) {
|
|
119
|
+
throw new TicketProvenanceWatermarkError("malformed-json", `ticket-provenance offered watermark malformed JSON at line ${index + 1} (${offeredWatermarkFile})`, { cause: error });
|
|
120
|
+
}
|
|
121
|
+
if (typeof parsed !== "object" ||
|
|
122
|
+
parsed === null ||
|
|
123
|
+
Array.isArray(parsed) ||
|
|
124
|
+
typeof parsed.identity !== "string" ||
|
|
125
|
+
parsed.identity.length === 0) {
|
|
126
|
+
throw new TicketProvenanceWatermarkError("bad-shape", `ticket-provenance offered watermark bad shape at line ${index + 1} (${offeredWatermarkFile})`);
|
|
127
|
+
}
|
|
128
|
+
seen.add(parsed.identity);
|
|
129
|
+
}
|
|
130
|
+
return seen;
|
|
131
|
+
}
|
|
132
|
+
/**
|
|
133
|
+
* Append offered identities after a successful collector pass (selected or not).
|
|
134
|
+
* Idempotent per identity within the file (skip already-present). Creates volume
|
|
135
|
+
* dir only when writing the first watermark row.
|
|
136
|
+
*/
|
|
137
|
+
export function recordOfferedIdentities(input) {
|
|
138
|
+
if (input.identities.length === 0)
|
|
139
|
+
return;
|
|
140
|
+
const { volumeDir, offeredWatermarkFile } = resolveTicketProvenanceVolume(input.ticketNumber, input.cwd, input.home);
|
|
141
|
+
const already = new Set(readOfferedIdentities(input.ticketNumber, input.cwd, input.home));
|
|
142
|
+
const rows = [];
|
|
143
|
+
for (const identity of input.identities) {
|
|
144
|
+
if (identity.length === 0 || already.has(identity))
|
|
145
|
+
continue;
|
|
146
|
+
rows.push(`${JSON.stringify({ identity })}\n`);
|
|
147
|
+
already.add(identity);
|
|
148
|
+
}
|
|
149
|
+
if (rows.length === 0)
|
|
150
|
+
return;
|
|
151
|
+
mkdirSync(volumeDir, { recursive: true });
|
|
152
|
+
appendFileSync(offeredWatermarkFile, rows.join(""), "utf8");
|
|
153
|
+
}
|
|
154
|
+
/** Read projected diary entries + diagnostics for a ticket (empty when volume absent). */
|
|
155
|
+
export async function readTicketProvenance(ticketNumber, cwd, home) {
|
|
156
|
+
const { recordFile } = resolveTicketProvenanceVolume(ticketNumber, cwd, home);
|
|
157
|
+
const { records } = await readSitianRecords(recordFile);
|
|
158
|
+
const entries = [];
|
|
159
|
+
const diagnostics = [];
|
|
160
|
+
let skipped = 0;
|
|
161
|
+
for (const record of records) {
|
|
162
|
+
if (record.kind !== TICKET_PROVENANCE_KIND) {
|
|
163
|
+
skipped += 1;
|
|
164
|
+
continue;
|
|
165
|
+
}
|
|
166
|
+
// Diagnostics first — same partition, separate projection (never body).
|
|
167
|
+
const diagnostic = projectTicketProvenanceDiagnostic(record.payload);
|
|
168
|
+
if (diagnostic !== undefined) {
|
|
169
|
+
diagnostics.push(diagnostic);
|
|
170
|
+
continue;
|
|
171
|
+
}
|
|
172
|
+
const entry = projectTicketProvenanceEntry(record.payload);
|
|
173
|
+
if (entry === undefined) {
|
|
174
|
+
skipped += 1;
|
|
175
|
+
continue;
|
|
176
|
+
}
|
|
177
|
+
entries.push(entry);
|
|
178
|
+
}
|
|
179
|
+
return { entries, diagnostics, records, recordFile, skipped };
|
|
180
|
+
}
|
|
181
|
+
/**
|
|
182
|
+
* Fence long enough that any backtick run inside `text` cannot close the block.
|
|
183
|
+
* Presentation helper only — not a machine contract; not exported.
|
|
184
|
+
*/
|
|
185
|
+
function markdownFenceFor(text) {
|
|
186
|
+
let longest = 0;
|
|
187
|
+
const runs = text.match(/`+/g);
|
|
188
|
+
if (runs !== null) {
|
|
189
|
+
for (const run of runs) {
|
|
190
|
+
if (run.length > longest)
|
|
191
|
+
longest = run.length;
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
return "`".repeat(Math.max(3, longest + 1));
|
|
195
|
+
}
|
|
196
|
+
/**
|
|
197
|
+
* Render a local human-read markdown view from entries.
|
|
198
|
+
* Presentation only — machines bite JSONL. No wording lock for consumers.
|
|
199
|
+
*/
|
|
200
|
+
export function renderTicketProvenanceMarkdown(input) {
|
|
201
|
+
const lines = [
|
|
202
|
+
`# 起居录 · #${input.ticketNumber}`,
|
|
203
|
+
"",
|
|
204
|
+
`条目数:${input.entries.length}`,
|
|
205
|
+
"",
|
|
206
|
+
];
|
|
207
|
+
let index = 0;
|
|
208
|
+
for (const entry of input.entries) {
|
|
209
|
+
index += 1;
|
|
210
|
+
lines.push(`## ${index}. ${entry.sourceKind} · ${entry.timestamp}`);
|
|
211
|
+
lines.push("");
|
|
212
|
+
lines.push(`- basis.method: \`${entry.basis.method}\``);
|
|
213
|
+
if (entry.basis.anchors !== undefined && entry.basis.anchors.length > 0) {
|
|
214
|
+
lines.push(`- anchors: ${entry.basis.anchors.map((a) => `\`${a}\``).join(", ")}`);
|
|
215
|
+
}
|
|
216
|
+
if (entry.basis.note !== undefined) {
|
|
217
|
+
lines.push(`- note: ${entry.basis.note}`);
|
|
218
|
+
}
|
|
219
|
+
const refParts = [];
|
|
220
|
+
if (entry.sourceRef.sessionFile !== undefined) {
|
|
221
|
+
refParts.push(`sessionFile=${entry.sourceRef.sessionFile}`);
|
|
222
|
+
}
|
|
223
|
+
if (entry.sourceRef.entryId !== undefined) {
|
|
224
|
+
refParts.push(`entryId=${String(entry.sourceRef.entryId)}`);
|
|
225
|
+
}
|
|
226
|
+
if (entry.sourceRef.path !== undefined) {
|
|
227
|
+
refParts.push(`path=${entry.sourceRef.path}`);
|
|
228
|
+
}
|
|
229
|
+
if (entry.sourceRef.url !== undefined) {
|
|
230
|
+
refParts.push(`url=${entry.sourceRef.url}`);
|
|
231
|
+
}
|
|
232
|
+
if (refParts.length > 0) {
|
|
233
|
+
lines.push(`- sourceRef: ${refParts.join(" · ")}`);
|
|
234
|
+
}
|
|
235
|
+
lines.push("");
|
|
236
|
+
const fence = markdownFenceFor(entry.transcript);
|
|
237
|
+
lines.push(fence);
|
|
238
|
+
lines.push(entry.transcript);
|
|
239
|
+
lines.push(fence);
|
|
240
|
+
lines.push("");
|
|
241
|
+
}
|
|
242
|
+
return lines.join("\n");
|
|
243
|
+
}
|
|
244
|
+
/**
|
|
245
|
+
* Ensure the per-ticket JSONL volume partition exists (empty file OK).
|
|
246
|
+
* Every bound-ticket court establishes the volume even with zero diary entries
|
|
247
|
+
* (ADR 0075 ticket-provenance-file — 每票一份起居录). Does not forge entries.
|
|
248
|
+
*/
|
|
249
|
+
export function ensureTicketProvenanceVolume(ticketNumber, cwd, home) {
|
|
250
|
+
const volume = resolveTicketProvenanceVolume(ticketNumber, cwd, home);
|
|
251
|
+
mkdirSync(volume.volumeDir, { recursive: true });
|
|
252
|
+
// Append-open creates an absent volume without ever truncating rows committed
|
|
253
|
+
// by a concurrent first writer.
|
|
254
|
+
appendFileSync(volume.recordFile, "", "utf8");
|
|
255
|
+
return volume;
|
|
256
|
+
}
|
|
257
|
+
/**
|
|
258
|
+
* Append a typed diagnostic onto the ticket-provenance volume (append-only).
|
|
259
|
+
* Same sitian kind + subject partition as diary entries; payload uses recordClass
|
|
260
|
+
* discriminator — never forged as a source entry. No sidecar / parallel ledger.
|
|
261
|
+
*/
|
|
262
|
+
export function appendTicketProvenanceDiagnostic(input) {
|
|
263
|
+
const subject = ticketProvenanceSubject(input.ticketNumber);
|
|
264
|
+
// quote-verify: content-stable identity so mid-batch retry does not duplicate.
|
|
265
|
+
// collector/issue-source: recordedAt keeps per-court failure history.
|
|
266
|
+
const identityParts = input.diagnostic.diagnosticKind === "quote-verify-failed"
|
|
267
|
+
? [
|
|
268
|
+
subject,
|
|
269
|
+
input.diagnostic.recordClass,
|
|
270
|
+
input.diagnostic.diagnosticKind,
|
|
271
|
+
input.diagnostic.cause,
|
|
272
|
+
input.diagnostic.reason ?? "",
|
|
273
|
+
]
|
|
274
|
+
: [
|
|
275
|
+
subject,
|
|
276
|
+
input.diagnostic.recordClass,
|
|
277
|
+
input.diagnostic.diagnosticKind,
|
|
278
|
+
input.diagnostic.recordedAt,
|
|
279
|
+
input.diagnostic.cause,
|
|
280
|
+
input.diagnostic.reason ?? "",
|
|
281
|
+
];
|
|
282
|
+
const identity = createHash("sha256")
|
|
283
|
+
.update(identityParts.join("\u0000"), "utf8")
|
|
284
|
+
.digest("hex");
|
|
285
|
+
return sitianReport({
|
|
286
|
+
level: "event",
|
|
287
|
+
kind: TICKET_PROVENANCE_KIND,
|
|
288
|
+
identity,
|
|
289
|
+
subject,
|
|
290
|
+
cwd: input.cwd,
|
|
291
|
+
...(input.home === undefined ? {} : { home: input.home }),
|
|
292
|
+
host: input.host ?? "diarist",
|
|
293
|
+
source: input.source ?? "diarist-diagnostic",
|
|
294
|
+
payload: input.diagnostic,
|
|
295
|
+
});
|
|
296
|
+
}
|
|
297
|
+
/** Issue-face source acquisition failure — typed diagnostic on the ticket volume. */
|
|
298
|
+
export function appendIssueSourceFailureDiagnostic(input) {
|
|
299
|
+
const recordedAt = input.recordedAt ?? new Date().toISOString();
|
|
300
|
+
return appendTicketProvenanceDiagnostic({
|
|
301
|
+
ticketNumber: input.ticketNumber,
|
|
302
|
+
cwd: input.cwd,
|
|
303
|
+
...(input.home === undefined ? {} : { home: input.home }),
|
|
304
|
+
source: "diarist-issue-source",
|
|
305
|
+
diagnostic: {
|
|
306
|
+
recordClass: TICKET_PROVENANCE_RECORD_CLASS_DIAGNOSTIC,
|
|
307
|
+
diagnosticKind: "issue-source-failed",
|
|
308
|
+
cause: input.cause,
|
|
309
|
+
recordedAt,
|
|
310
|
+
reason: input.reason,
|
|
311
|
+
},
|
|
312
|
+
});
|
|
313
|
+
}
|
|
314
|
+
/** Quote reverse-verify failure — single diagnostic expression (not a diary entry). */
|
|
315
|
+
export function appendQuoteVerifyFailureDiagnostic(input) {
|
|
316
|
+
const recordedAt = input.recordedAt ?? new Date().toISOString();
|
|
317
|
+
return appendTicketProvenanceDiagnostic({
|
|
318
|
+
ticketNumber: input.ticketNumber,
|
|
319
|
+
cwd: input.cwd,
|
|
320
|
+
...(input.home === undefined ? {} : { home: input.home }),
|
|
321
|
+
source: "diarist-quote-verify",
|
|
322
|
+
diagnostic: {
|
|
323
|
+
recordClass: TICKET_PROVENANCE_RECORD_CLASS_DIAGNOSTIC,
|
|
324
|
+
diagnosticKind: "quote-verify-failed",
|
|
325
|
+
cause: input.cause,
|
|
326
|
+
recordedAt,
|
|
327
|
+
},
|
|
328
|
+
});
|
|
329
|
+
}
|
|
330
|
+
/** Write the co-located human view next to the JSONL volume (derived, not dual-source).
|
|
331
|
+
* Ensures volume dir exists so empty courts still get the md face. */
|
|
332
|
+
export function writeTicketProvenanceHumanView(input) {
|
|
333
|
+
const volume = ensureTicketProvenanceVolume(input.ticketNumber, input.cwd, input.home);
|
|
334
|
+
const md = renderTicketProvenanceMarkdown({
|
|
335
|
+
ticketNumber: input.ticketNumber,
|
|
336
|
+
entries: input.entries,
|
|
337
|
+
});
|
|
338
|
+
writeFileSync(volume.humanViewFile, md, "utf8");
|
|
339
|
+
return volume.humanViewFile;
|
|
340
|
+
}
|
|
@@ -36,10 +36,15 @@ import {
|
|
|
36
36
|
formatNavigatorRoleHelp,
|
|
37
37
|
} from "../src/role-runtime.ts";
|
|
38
38
|
import { createPiJudgeAuditor } from "../src/judge-auditor.ts";
|
|
39
|
+
import { loadAuditorSoulFromSubjectInput } from "../src/auditor-soul.ts";
|
|
39
40
|
import { loadGatekeeperSessionMaterials, loadMainRoleSessionMaterials } from "../src/session-opening-materials.ts";
|
|
40
41
|
const extensionPath = fileURLToPath(import.meta.url);
|
|
41
42
|
const packageRoot = fileURLToPath(new URL("..", import.meta.url));
|
|
42
43
|
const navigatorRoutePlaybookPath = fileURLToPath(new URL("../resources/navigator-route-playbook.md", import.meta.url));
|
|
44
|
+
// #675: nested public summons (gate/auditor/evidence-child) resolve root via env under jiti.
|
|
45
|
+
if (process.env.AK_ROLE_PACKAGE_ROOT === undefined || process.env.AK_ROLE_PACKAGE_ROOT.trim() === "") {
|
|
46
|
+
process.env.AK_ROLE_PACKAGE_ROOT = packageRoot;
|
|
47
|
+
}
|
|
43
48
|
|
|
44
49
|
// Cold `pi -e <extension> --help` must cover installed-package process startup under CI load.
|
|
45
50
|
// This bound is process-startup budget only — not settlement-to-visible presentation latency.
|
|
@@ -131,6 +136,8 @@ export default function roleRuntime(pi: ExtensionAPI): void {
|
|
|
131
136
|
loadInspectorSoul: () => loadMainRoleSessionMaterials("inspector"),
|
|
132
137
|
loadGatekeeperSoul: () => loadGatekeeperSessionMaterials("gatekeeper"),
|
|
133
138
|
loadNavigatorSoul: () => loadMainRoleSessionMaterials("navigator"),
|
|
139
|
+
loadAuditorSoul: () => loadAuditorSoulFromSubjectInput(),
|
|
140
|
+
loadEvidenceChildSoul: () => loadMainRoleSessionMaterials("evidence-child"),
|
|
134
141
|
loadNotarySourceRun: loadNotarySourceRunLocator,
|
|
135
142
|
loadNavigatorWorkContext: (options) => loadNavigatorWorkContext(pi, options),
|
|
136
143
|
createNavigatorAttendance: (options) => {
|
package/package.json
CHANGED
|
@@ -14,8 +14,9 @@ const entries = [
|
|
|
14
14
|
// Navigator package-graph dependencies used by attendance settlement.
|
|
15
15
|
// navigator-session-contracts is a static import of the published attendance root
|
|
16
16
|
// (#590: published non-bundle graph must stay closed under its own relative edges).
|
|
17
|
-
"
|
|
17
|
+
"navigator-public-session",
|
|
18
18
|
"navigator-session-contracts",
|
|
19
|
+
"public-role-summons",
|
|
19
20
|
"pi/in-process-session",
|
|
20
21
|
"activation-ledger-git",
|
|
21
22
|
"activation-ledger-topology",
|
package/souls/doctor-auditor.md
CHANGED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
# 取证子
|
|
@@ -23,6 +23,7 @@ import { loadPackagedCanonicalSkillBinding } from "../package-resources/method-s
|
|
|
23
23
|
import { createPerDispatchReviewerAgent } from "../reviewer-agent.ts";
|
|
24
24
|
import { formatNavigatorRoleHelp, type RoleRuntimeDependencies } from "../role-runtime.ts";
|
|
25
25
|
import { createReviewerPinnedGitReader } from "../reviewer-pinned-git.ts";
|
|
26
|
+
import { loadAuditorSoulFromSubjectInput } from "../auditor-soul.ts";
|
|
26
27
|
import { loadGatekeeperSessionMaterials, loadMainRoleSessionMaterials } from "../session-opening-materials.ts";
|
|
27
28
|
import { acpStdioArgs, resolveAcpBinary, type AcpHostDescription } from "./description.ts";
|
|
28
29
|
import { createComposedAcpRoleTurnHost } from "./role-envelope.ts";
|
|
@@ -61,6 +62,8 @@ export function createAcpRoleRuntimeDependencies(packageRoot: string): RoleRunti
|
|
|
61
62
|
loadInspectorSoul: () => loadMainRoleSessionMaterials("inspector"),
|
|
62
63
|
loadGatekeeperSoul: () => loadGatekeeperSessionMaterials("gatekeeper"),
|
|
63
64
|
loadNavigatorSoul: () => loadMainRoleSessionMaterials("navigator"),
|
|
65
|
+
loadAuditorSoul: () => loadAuditorSoulFromSubjectInput(),
|
|
66
|
+
loadEvidenceChildSoul: () => loadMainRoleSessionMaterials("evidence-child"),
|
|
64
67
|
loadNotarySoul: () => loadMainRoleSessionMaterials("notary"),
|
|
65
68
|
loadCountersignSoul: () => loadMainRoleSessionMaterials("countersign"),
|
|
66
69
|
loadGleanerLeftSoul: () => loadMainRoleSessionMaterials("gleaner-left"),
|
|
@@ -4,7 +4,7 @@ import { createServer, type Server, type Socket } from "node:net";
|
|
|
4
4
|
import { basename, dirname, join } from "node:path";
|
|
5
5
|
import { fileURLToPath } from "node:url";
|
|
6
6
|
|
|
7
|
-
import { requireGatekeeperPass } from "../gatekeeper-
|
|
7
|
+
import { requireGatekeeperPass } from "../gatekeeper-pass-envelope.ts";
|
|
8
8
|
import type {
|
|
9
9
|
HostContext,
|
|
10
10
|
HostEventRegistration,
|
|
@@ -298,7 +298,7 @@ export async function prepareAcpRoleEnvelope(options: {
|
|
|
298
298
|
if (record.kind === "role_infrastructure_failure") return;
|
|
299
299
|
const code = typeof record.code === "string" && record.code.length > 0
|
|
300
300
|
? record.code
|
|
301
|
-
: record.status === "bounce" || record.status === "no_receipt"
|
|
301
|
+
: record.status === "bounce" || record.status === "no_receipt" || record.status === "unreadable"
|
|
302
302
|
? record.status
|
|
303
303
|
: undefined;
|
|
304
304
|
if (code === undefined) return;
|
|
@@ -270,7 +270,15 @@ export function createAcpRoleTurnHost(config: AcpRoleTurnHostConfig): RoleTurnHo
|
|
|
270
270
|
priorNativePaths !== undefined && priorNativePaths.length > 0
|
|
271
271
|
? `${prepared.prompt}\n${priorNativePaths.join("\n")}`
|
|
272
272
|
: prepared.prompt;
|
|
273
|
-
|
|
273
|
+
// Envelope infra abort and parent cancellation (#675 nested summons) are
|
|
274
|
+
// the same race face here: either one ends the ACP round and the finally
|
|
275
|
+
// block cancels + closes the child session.
|
|
276
|
+
const abortSignal =
|
|
277
|
+
request.signal === undefined
|
|
278
|
+
? prepared.abortSignal
|
|
279
|
+
: prepared.abortSignal === undefined
|
|
280
|
+
? request.signal
|
|
281
|
+
: AbortSignal.any([prepared.abortSignal, request.signal]);
|
|
274
282
|
const activeConnection = connection;
|
|
275
283
|
|
|
276
284
|
/** Race ACP prompt against envelope abort so infra failInfrastructure cannot hang (#593). */
|
|
@@ -434,8 +434,34 @@ function pairGateRounds(
|
|
|
434
434
|
.map((round, index) => ({ ...round, roundIndex: index + 1 }));
|
|
435
435
|
}
|
|
436
436
|
|
|
437
|
+
/** Resolve a direct-officer-run-pointer file to the officer session 正本 path. */
|
|
438
|
+
async function resolveOfficerSessionFromPointerFile(
|
|
439
|
+
pointerPath: string,
|
|
440
|
+
): Promise<string | undefined> {
|
|
441
|
+
const { readFile } = await import("node:fs/promises");
|
|
442
|
+
let raw: unknown;
|
|
443
|
+
try {
|
|
444
|
+
raw = JSON.parse(await readFile(pointerPath, "utf8"));
|
|
445
|
+
} catch (error) {
|
|
446
|
+
throw new Error(
|
|
447
|
+
`direct officer run pointer unreadable in ${pointerPath}: ${error instanceof Error ? error.message : String(error)}`,
|
|
448
|
+
{ cause: error },
|
|
449
|
+
);
|
|
450
|
+
}
|
|
451
|
+
if (!isRecord(raw) || raw.kind !== "direct-officer-run-pointer" || raw.version !== 1) {
|
|
452
|
+
throw new Error(`direct officer run pointer has unknown shape in ${pointerPath}`);
|
|
453
|
+
}
|
|
454
|
+
const sessionFile = raw.sessionFile;
|
|
455
|
+
if (typeof sessionFile !== "string" || sessionFile.trim() === "") {
|
|
456
|
+
throw new Error(`direct officer run pointer missing sessionFile in ${pointerPath}`);
|
|
457
|
+
}
|
|
458
|
+
return sessionFile;
|
|
459
|
+
}
|
|
460
|
+
|
|
437
461
|
/**
|
|
438
462
|
* Read and pair gate-cycle rounds from one or more auditor-roles directories.
|
|
463
|
+
* Accepts historical nested JSONL volumes and #675 direct-officer-run-pointer
|
|
464
|
+
* files that name the independent officer session 正本.
|
|
439
465
|
* ENOENT (directory truly absent) → []. ENOTDIR and other errors propagate
|
|
440
466
|
* (failure honesty — damaged topology must not wash to zero rounds).
|
|
441
467
|
*
|
|
@@ -456,7 +482,11 @@ export async function readAnalystGateCyclesFromAuditorRoles(
|
|
|
456
482
|
try {
|
|
457
483
|
const entries = await readdir(directory, { withFileTypes: true });
|
|
458
484
|
names = entries
|
|
459
|
-
.filter(
|
|
485
|
+
.filter(
|
|
486
|
+
(e) =>
|
|
487
|
+
e.isFile()
|
|
488
|
+
&& (e.name.endsWith(".jsonl") || e.name.endsWith(".pointer.json")),
|
|
489
|
+
)
|
|
460
490
|
.map((e) => e.name)
|
|
461
491
|
.sort();
|
|
462
492
|
} catch (error) {
|
|
@@ -464,7 +494,12 @@ export async function readAnalystGateCyclesFromAuditorRoles(
|
|
|
464
494
|
throw error;
|
|
465
495
|
}
|
|
466
496
|
for (const name of names) {
|
|
467
|
-
const
|
|
497
|
+
const path = join(directory, name);
|
|
498
|
+
const sessionPath = name.endsWith(".pointer.json")
|
|
499
|
+
? await resolveOfficerSessionFromPointerFile(path)
|
|
500
|
+
: path;
|
|
501
|
+
if (sessionPath === undefined) continue;
|
|
502
|
+
const classified = await classifyAuditorVolume(sessionPath);
|
|
468
503
|
for (const volume of classified) {
|
|
469
504
|
if (
|
|
470
505
|
options.parentSessionFile !== undefined &&
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* 调用方只声明自己是谁的什么;落点由候簿拓扑算出,签名不含任何落点/路径参数。
|
|
4
4
|
* 「谁调了谁」复用 Pi parentSession + ADR 0047 correlation,不新增 caller 字段。
|
|
5
5
|
*/
|
|
6
|
-
import { existsSync, readFileSync, realpathSync, writeFileSync } from "node:fs";
|
|
6
|
+
import { existsSync, mkdirSync, readFileSync, realpathSync, writeFileSync } from "node:fs";
|
|
7
7
|
import { dirname, resolve, join } from "node:path";
|
|
8
8
|
import { SessionManager } from "@earendil-works/pi-coding-agent";
|
|
9
9
|
|
|
@@ -224,3 +224,47 @@ export function createRecordSessionOpen(options: CreateRecordSessionOptions): Re
|
|
|
224
224
|
export function createRecordSession(options: CreateRecordSessionOptions): SessionManager {
|
|
225
225
|
return createRecordSessionOpen(options).session;
|
|
226
226
|
}
|
|
227
|
+
|
|
228
|
+
|
|
229
|
+
/** Typed parent-side pointer to an independent officer run 正本 (ADR 0079 / #675). */
|
|
230
|
+
export const DIRECT_OFFICER_RUN_POINTER_KIND = "direct-officer-run-pointer" as const;
|
|
231
|
+
|
|
232
|
+
export type DirectOfficerRunPointer = {
|
|
233
|
+
readonly version: 1;
|
|
234
|
+
readonly kind: typeof DIRECT_OFFICER_RUN_POINTER_KIND;
|
|
235
|
+
readonly officer: "inspector" | "notary";
|
|
236
|
+
/** Absolute path to the officer session.jsonl 正本. */
|
|
237
|
+
readonly sessionFile: string;
|
|
238
|
+
/** Officer run directory when known. */
|
|
239
|
+
readonly runDirectory?: string;
|
|
240
|
+
};
|
|
241
|
+
|
|
242
|
+
/**
|
|
243
|
+
* Book a typed pointer under parent session/auditor-roles (same nest owner as
|
|
244
|
+
* createRecordSession). Never fabricates user/assistant/toolResult rows (#675).
|
|
245
|
+
* Directory placement stays with the archivist record entry (ADR 0018 / 0065).
|
|
246
|
+
*/
|
|
247
|
+
export function bookDirectOfficerRunPointer(options: {
|
|
248
|
+
readonly parentSessionFile: string;
|
|
249
|
+
readonly officer: "inspector" | "notary";
|
|
250
|
+
readonly sessionFile: string;
|
|
251
|
+
readonly runDirectory?: string;
|
|
252
|
+
}): DirectOfficerRunPointer {
|
|
253
|
+
const nest = join(dirname(options.parentSessionFile), "auditor-roles");
|
|
254
|
+
mkdirSync(nest, { recursive: true });
|
|
255
|
+
const pointer: DirectOfficerRunPointer = {
|
|
256
|
+
version: 1,
|
|
257
|
+
kind: DIRECT_OFFICER_RUN_POINTER_KIND,
|
|
258
|
+
officer: options.officer,
|
|
259
|
+
sessionFile: options.sessionFile,
|
|
260
|
+
...(options.runDirectory !== undefined && options.runDirectory.trim() !== ""
|
|
261
|
+
? { runDirectory: options.runDirectory }
|
|
262
|
+
: {}),
|
|
263
|
+
};
|
|
264
|
+
writeFileSync(
|
|
265
|
+
join(nest, `${options.officer}-${Date.now().toString(36)}.pointer.json`),
|
|
266
|
+
`${JSON.stringify(pointer)}\n`,
|
|
267
|
+
"utf8",
|
|
268
|
+
);
|
|
269
|
+
return pointer;
|
|
270
|
+
}
|
package/src/audit-escalation.ts
CHANGED
|
@@ -175,6 +175,14 @@ export type ComplianceDecisionHandlers<T> = {
|
|
|
175
175
|
facts: Extract<ComplianceDecision, { status: "no-receipt" }>,
|
|
176
176
|
usageProjection: { usage?: Usage },
|
|
177
177
|
) => T | PromiseLike<T>;
|
|
178
|
+
/**
|
|
179
|
+
* Parent work stands with typed unreadable audit fact (ADR 0055 / §0).
|
|
180
|
+
* Must not collapse into ordinary pass (forged role decision).
|
|
181
|
+
*/
|
|
182
|
+
unreadable?: (
|
|
183
|
+
facts: Extract<ComplianceDecision, { status: "unreadable" }>,
|
|
184
|
+
usageProjection: { usage?: Usage },
|
|
185
|
+
) => T | PromiseLike<T>;
|
|
178
186
|
revise: (violations: readonly unknown[]) => T | PromiseLike<T>;
|
|
179
187
|
escalate: (result: AuditEscalationToolResult) => T | PromiseLike<T>;
|
|
180
188
|
};
|
|
@@ -201,6 +209,15 @@ export async function disposeComplianceDecision<T>(
|
|
|
201
209
|
decision,
|
|
202
210
|
decision.usage === undefined ? {} : { usage: decision.usage },
|
|
203
211
|
);
|
|
212
|
+
case "unreadable":
|
|
213
|
+
// Parent candidate stands; audit shape failure is a typed parallel fact — not pass.
|
|
214
|
+
if (handlers.unreadable === undefined) {
|
|
215
|
+
throw new Error("Compliance unreadable projection handler is unavailable");
|
|
216
|
+
}
|
|
217
|
+
return await handlers.unreadable(
|
|
218
|
+
decision,
|
|
219
|
+
decision.usage === undefined ? {} : { usage: decision.usage },
|
|
220
|
+
);
|
|
204
221
|
case "revise":
|
|
205
222
|
return await handlers.revise(decision.violations);
|
|
206
223
|
case "escalate":
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Public Auditor (审刑院) role — filed-officer envelope (#675).
|
|
3
|
+
*/
|
|
4
|
+
import {
|
|
5
|
+
AUDITOR_ACCEPTED_TEXT,
|
|
6
|
+
AUDITOR_OUTPUT_TOOL_NAME,
|
|
7
|
+
auditorOutputSchema,
|
|
8
|
+
} from "./package-contracts/auditor-output.ts";
|
|
9
|
+
|
|
10
|
+
export { AUDITOR_ACCEPTED_TEXT, AUDITOR_OUTPUT_TOOL_NAME };
|
|
11
|
+
|
|
12
|
+
export type AuditorRuntimeDependencies = {
|
|
13
|
+
loadSoul(): Promise<string>;
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
export const AUDITOR_TOOL_SPEC = {
|
|
17
|
+
name: AUDITOR_OUTPUT_TOOL_NAME,
|
|
18
|
+
label: "审刑院输出",
|
|
19
|
+
description: "审刑院终局回执,状态为 pass、revise 或 escalate。",
|
|
20
|
+
promptSnippet: "审刑院终局回执",
|
|
21
|
+
parameters: auditorOutputSchema,
|
|
22
|
+
} as const;
|