@akagilnc/pi-workflow-roles 0.1.3565 → 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.
Files changed (142) hide show
  1. package/README.md +4 -1
  2. package/README.zh-CN.md +1 -1
  3. package/dist/acp-host/production-host.js +18477 -8099
  4. package/dist/analyst-gate-cycles-read.js +387 -0
  5. package/dist/archivist-record-entry.js +22 -1
  6. package/dist/audit-escalation.js +6 -0
  7. package/dist/auditor-soul.js +74 -0
  8. package/dist/collector-config.js +86 -0
  9. package/dist/collector-evidence.js +316 -0
  10. package/dist/collector-github.js +527 -0
  11. package/dist/collector-ledger.js +853 -0
  12. package/dist/collector-tool-schemas.js +33 -0
  13. package/dist/compliance-transport.js +104 -52
  14. package/dist/diarist-contracts.js +72 -0
  15. package/dist/diarist-mechanical.js +411 -0
  16. package/dist/diarist-ticket-resolution.js +177 -0
  17. package/dist/diarist.js +311 -0
  18. package/dist/doctor-auditor.js +25 -0
  19. package/dist/doctor-contracts.js +2 -0
  20. package/dist/doctor-evidence.js +142 -0
  21. package/dist/gatekeeper-role.js +104 -81
  22. package/dist/host-transition-prior-native.js +78 -0
  23. package/dist/institutional-resolution.js +1 -95
  24. package/dist/judge-auditor.js +26 -0
  25. package/dist/ledger-session-read.js +227 -0
  26. package/dist/merger-git-state.js +78 -0
  27. package/dist/navigator-attendance.js +15 -7
  28. package/dist/navigator-public-session.js +170 -0
  29. package/dist/navigator-session-contracts.js +36 -40
  30. package/dist/notary-source-run.js +122 -0
  31. package/dist/package-contracts/auditor-output.js +66 -0
  32. package/dist/package-contracts/evidence-child-output.js +34 -0
  33. package/dist/package-contracts/judge-output.js +2 -0
  34. package/dist/package-contracts/terminating-tools.js +44 -3
  35. package/dist/package-resources/method-skill.js +254 -0
  36. package/dist/packaged-role-registry.js +56 -0
  37. package/dist/pi/durable-principal.js +61 -0
  38. package/dist/pi/in-process-session.js +25 -6
  39. package/dist/pi/known-failure.js +52 -0
  40. package/dist/pi/role-turn-host.js +430 -0
  41. package/dist/public-cli/auto-resume.js +414 -0
  42. package/dist/public-cli/cli-errors.js +8 -0
  43. package/dist/public-cli/cli-io.js +1 -0
  44. package/dist/public-cli/command-renderer.js +5 -0
  45. package/dist/public-cli/doctor-run.js +84 -0
  46. package/dist/public-cli/inspector-run.js +136 -0
  47. package/dist/public-cli/instruction-seat-run.js +256 -0
  48. package/dist/public-cli/invocation.js +2225 -0
  49. package/dist/public-cli/judge-run.js +118 -0
  50. package/dist/public-cli/load-production-acp-host.js +40 -0
  51. package/dist/public-cli/main.js +1511 -1124
  52. package/dist/public-cli/notary-run.js +161 -0
  53. package/dist/public-cli/option-definitions.js +1192 -0
  54. package/dist/public-cli/post-admission.js +667 -0
  55. package/dist/public-cli/public-run-credentials.js +49 -0
  56. package/dist/public-cli/registry.js +8 -2
  57. package/dist/public-cli/reviewer-dispatch-rejection.js +77 -0
  58. package/dist/public-cli/run-lifecycle.js +1344 -0
  59. package/dist/public-cli/seat-ticket-binding.js +113 -0
  60. package/dist/public-cli/settlement.js +3441 -0
  61. package/dist/public-cli/terminal.js +135 -0
  62. package/dist/public-cli/turn-request.js +25 -0
  63. package/dist/public-role-summons.js +301 -0
  64. package/dist/receipt-delivery-policy.js +10 -0
  65. package/dist/reviewer-child-executor.js +80 -8
  66. package/dist/reviewer-execution-ledger.js +2 -1
  67. package/dist/run-terminal-artifacts.js +195 -0
  68. package/dist/run-ticket-number.js +40 -0
  69. package/dist/session-assistant-usage.js +107 -0
  70. package/dist/shape-unreadable-failure.js +34 -0
  71. package/dist/submission-errors.js +3 -0
  72. package/dist/submission-ledger.js +388 -0
  73. package/dist/ticket-provenance-contracts.js +115 -0
  74. package/dist/ticket-provenance.js +340 -0
  75. package/extensions/role-runtime.ts +8 -0
  76. package/package.json +1 -1
  77. package/resources/diarist-collect.md +6 -10
  78. package/scripts/build-package.mjs +2 -1
  79. package/souls/diarist.md +9 -0
  80. package/souls/doctor-auditor.md +1 -0
  81. package/souls/evidence-child.md +1 -0
  82. package/src/acp-host/production-host.ts +4 -0
  83. package/src/acp-host/role-envelope.ts +4 -3
  84. package/src/acp-host/role-turn-host.ts +9 -1
  85. package/src/analyst-gate-cycles-read.ts +37 -2
  86. package/src/archivist-record-entry.ts +45 -1
  87. package/src/audit-escalation.ts +17 -0
  88. package/src/auditor-role.ts +22 -0
  89. package/src/auditor-soul.ts +42 -0
  90. package/src/compliance-transport.ts +177 -66
  91. package/src/diarist-contracts.ts +88 -0
  92. package/src/diarist-role.ts +60 -0
  93. package/src/diarist.ts +253 -179
  94. package/src/doctor-auditor.ts +10 -14
  95. package/src/doctor-contracts.ts +1 -0
  96. package/src/doctor-role.ts +2 -2
  97. package/src/evidence-child-role.ts +22 -0
  98. package/src/gatekeeper-pass-envelope.ts +109 -0
  99. package/src/gatekeeper-role.ts +145 -104
  100. package/src/host-contracts.ts +15 -2
  101. package/src/institutional-resolution.ts +8 -163
  102. package/src/judge-auditor.ts +10 -15
  103. package/src/judge-role.ts +8 -0
  104. package/src/navigator-attendance.ts +25 -9
  105. package/src/navigator-public-session.ts +217 -0
  106. package/src/navigator-session-contracts.ts +61 -54
  107. package/src/notary-source-run.ts +3 -1
  108. package/src/package-contracts/auditor-output.ts +82 -0
  109. package/src/package-contracts/evidence-child-output.ts +51 -0
  110. package/src/package-contracts/judge-output.ts +1 -0
  111. package/src/package-contracts/reviewer-output.ts +2 -2
  112. package/src/package-contracts/terminating-tools.ts +53 -3
  113. package/src/packaged-role-registry.ts +63 -0
  114. package/src/pi/adapter.ts +2 -1
  115. package/src/pi/in-process-session.ts +38 -12
  116. package/src/pi/role-turn-host.ts +42 -0
  117. package/src/public-cli/cli.ts +43 -6
  118. package/src/public-cli/countersign-run.ts +9 -129
  119. package/src/public-cli/diarist-run.ts +312 -0
  120. package/src/public-cli/instruction-seat-run.ts +289 -50
  121. package/src/public-cli/invocation.ts +87 -25
  122. package/src/public-cli/option-definitions.ts +79 -0
  123. package/src/public-cli/post-admission.ts +9 -3
  124. package/src/public-cli/registry.ts +7 -1
  125. package/src/public-cli/run-lifecycle.ts +53 -6
  126. package/src/public-cli/settlement.ts +170 -7
  127. package/src/public-cli/terminal.ts +4 -1
  128. package/src/public-role-summons.ts +431 -0
  129. package/src/receipt-delivery-policy.ts +10 -0
  130. package/src/reviewer-agent.ts +1 -1
  131. package/src/reviewer-child-executor.ts +114 -12
  132. package/src/reviewer-execution-ledger.ts +3 -2
  133. package/src/role-runtime.ts +275 -23
  134. package/src/session-assistant-usage.ts +119 -0
  135. package/src/session-opening-materials.ts +1 -1
  136. package/src/shape-unreadable-failure.ts +49 -0
  137. package/src/submission-errors.ts +3 -0
  138. package/src/ticket-provenance-contracts.ts +1 -0
  139. package/src/ticket-provenance.ts +0 -23
  140. package/dist/evidence-child-executor.js +0 -829
  141. package/src/diarist-llm-collector.ts +0 -324
  142. 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.
@@ -127,9 +132,12 @@ export default function roleRuntime(pi: ExtensionAPI): void {
127
132
  loadNotarySoul: () => loadMainRoleSessionMaterials("notary"),
128
133
  loadCountersignSoul: () => loadMainRoleSessionMaterials("countersign"),
129
134
  loadGleanerLeftSoul: () => loadMainRoleSessionMaterials("gleaner-left"),
135
+ loadDiaristSoul: () => loadMainRoleSessionMaterials("diarist"),
130
136
  loadInspectorSoul: () => loadMainRoleSessionMaterials("inspector"),
131
137
  loadGatekeeperSoul: () => loadGatekeeperSessionMaterials("gatekeeper"),
132
138
  loadNavigatorSoul: () => loadMainRoleSessionMaterials("navigator"),
139
+ loadAuditorSoul: () => loadAuditorSoulFromSubjectInput(),
140
+ loadEvidenceChildSoul: () => loadMainRoleSessionMaterials("evidence-child"),
133
141
  loadNotarySourceRun: loadNotarySourceRunLocator,
134
142
  loadNavigatorWorkContext: (options) => loadNavigatorWorkContext(pi, options),
135
143
  createNavigatorAttendance: (options) => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@akagilnc/pi-workflow-roles",
3
- "version": "0.1.3565",
3
+ "version": "0.1.3621",
4
4
  "description": "Soul-bound workflow roles for Pi",
5
5
  "type": "module",
6
6
  "license": "Apache-2.0",
@@ -1,10 +1,10 @@
1
1
  # 起居郎收集方法(owner-domain)
2
2
 
3
- 本文件是起居郎 LLM 收集器的语义方法真源(ADR 0075 `diarist-llm-collector` / `transcribe-whole-blocks`)。机器经共享 engine 接缝 staged prompt 递送本文件字节与冻结候选目录(不入 argv);判断以本方法为准。
3
+ 本文件是起居郎语义收集的方法真源(ADR 0075 `diarist-llm-collector` / `transcribe-whole-blocks`)。
4
4
 
5
5
  ## 任务
6
6
 
7
- 从已冻结的来源对话块目录中,选出**与本票决策相关**的整块。相关性由你裁决;不得因缺票号、缺引语或关键词未命中而排除。
7
+ 从本票已冻结的来源块文件中,选出**与本票决策相关**的整块。文件路径见本轮材料中的路径指针,自行读取;不要等别人把正文贴进对话。
8
8
 
9
9
  ## 何谓决策相关
10
10
 
@@ -14,16 +14,12 @@
14
14
  - 命名、工序先后、调用面、闸位、存量是否补档等裁决
15
15
  - 对既有 ADR / 法源的采纳、修正或明确不适用
16
16
  - 陛下(owner)原话、确认、否决、收口条件
17
- - 为上述裁决提供必要上下文的紧邻块(可标 `triage: context`)
17
+ - 为上述裁决提供必要上下文的紧邻块
18
18
 
19
- 无关闲聊、纯工具噪声、与本票决策无涉的他票材料,标 `irrelevant` 或不选。
19
+ 无关闲聊、纯工具噪声、与本票决策无涉的他票材料不选。
20
20
 
21
21
  ## 誊录整块
22
22
 
23
- - 入选即整块保留:以 `candidateIndex` 指向目录中的完整 transcript,不指针化、不摘要替代正文。
24
- - `quotes` 必须是该块 transcript 内的**连续原文子串**(可空数组)。拼接、改写、跨段拼贴会在机械反验中失败并拒入录。
23
+ - 入选即整块保留:指向文件中的完整 transcript,不指针化、不摘要替代正文。
24
+ - 引语必须是该块 transcript 内的**连续原文子串**。拼接、改写、跨段拼贴会在机械反验中失败并拒入录。
25
25
  - 宁多勿漏:拿不准是否决策相关时入选,由下游反验与人读面承接。
26
-
27
- ## 输出
28
-
29
- 只输出一个 JSON 对象,形状由机器 prompt 给出的 `selections` 约定。不要输出 JSON 以外的文字。
@@ -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
- "evidence-child-executor",
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",
@@ -0,0 +1,9 @@
1
+ # Diarist Soul(起居郎)
2
+
3
+ 你为本案找齐并整理决策依据,收进本票已有的起居录。你只记录已经作出的决定,不立法、不批准设计、不指挥施工。
4
+
5
+ - 相关性由你裁决:不把票面已列出的材料当完整候选集合,也不以票号、引语或关键词未命中作为排除依据。
6
+ - 分清身份:陛下(owner)的决定、助手的提议、正式裁决、你自己的整理解释,四者不得混同;建议不得写成授权。
7
+ - 导航而非重写:已有设计与法源以原件指针加简短关系说明引用,不复制成第二份规范。
8
+ - 分清现行与历史:被修订替代的决定保留出处,但不与当前方向并列。
9
+ - 缺录、不可读、引用无法核实时如实说明,不猜成事实,不以缺失充当"没有问题"。
@@ -21,3 +21,4 @@
21
21
  冲突导致无法判断合规时,提交 `escalate` 并写明问题和可选项。
22
22
 
23
23
  输出调用 `ak_doctor_audit_decision`。
24
+
@@ -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,9 +62,12 @@ 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"),
70
+ loadDiaristSoul: () => loadMainRoleSessionMaterials("diarist"),
67
71
  loadNotarySourceRun: loadNotarySourceRunLocator,
68
72
  loadMergerSoul: () => loadMainRoleSessionMaterials("merger"),
69
73
  loadMergerInput: async (path) => JSON.parse(await readFile(path, "utf8")),
@@ -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-role.ts";
7
+ import { requireGatekeeperPass } from "../gatekeeper-pass-envelope.ts";
8
8
  import type {
9
9
  HostContext,
10
10
  HostEventRegistration,
@@ -78,7 +78,8 @@ export function projectAcpActivationFlags(request: RoleTurnRequest): Map<string,
78
78
  : "packetPath" in activation ? activation.packetPath
79
79
  : "casePath" in activation ? activation.casePath
80
80
  : "inputPath" in activation ? activation.inputPath
81
- : "sourceRun" in activation ? activation.sourceRun : undefined;
81
+ : "sourceRun" in activation ? activation.sourceRun
82
+ : "sourcesPath" in activation ? activation.sourcesPath : undefined;
82
83
  if (path !== undefined) flags.set(inputFlag, path);
83
84
  }
84
85
  if (activation.role === "fixer" && activation.prerequisitesPath !== undefined) flags.set("ak-fixer-prerequisites", activation.prerequisitesPath);
@@ -297,7 +298,7 @@ export async function prepareAcpRoleEnvelope(options: {
297
298
  if (record.kind === "role_infrastructure_failure") return;
298
299
  const code = typeof record.code === "string" && record.code.length > 0
299
300
  ? record.code
300
- : record.status === "bounce" || record.status === "no_receipt"
301
+ : record.status === "bounce" || record.status === "no_receipt" || record.status === "unreadable"
301
302
  ? record.status
302
303
  : undefined;
303
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
- const abortSignal = prepared.abortSignal;
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((e) => e.isFile() && e.name.endsWith(".jsonl"))
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 classified = await classifyAuditorVolume(join(directory, name));
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
+ }
@@ -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":