@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.
Files changed (129) hide show
  1. package/dist/acp-host/production-host.js +20239 -10556
  2. package/dist/analyst-gate-cycles-read.js +387 -0
  3. package/dist/archivist-record-entry.js +22 -1
  4. package/dist/audit-escalation.js +6 -0
  5. package/dist/auditor-soul.js +74 -0
  6. package/dist/collector-config.js +86 -0
  7. package/dist/collector-evidence.js +316 -0
  8. package/dist/collector-github.js +527 -0
  9. package/dist/collector-ledger.js +853 -0
  10. package/dist/collector-tool-schemas.js +33 -0
  11. package/dist/compliance-transport.js +104 -52
  12. package/dist/diarist-mechanical.js +411 -0
  13. package/dist/diarist-ticket-resolution.js +177 -0
  14. package/dist/diarist.js +311 -0
  15. package/dist/doctor-auditor.js +25 -0
  16. package/dist/doctor-contracts.js +2 -0
  17. package/dist/doctor-evidence.js +142 -0
  18. package/dist/gatekeeper-role.js +104 -81
  19. package/dist/host-transition-prior-native.js +78 -0
  20. package/dist/institutional-resolution.js +1 -95
  21. package/dist/judge-auditor.js +26 -0
  22. package/dist/ledger-session-read.js +227 -0
  23. package/dist/merger-git-state.js +78 -0
  24. package/dist/navigator-attendance.js +15 -7
  25. package/dist/navigator-public-session.js +170 -0
  26. package/dist/navigator-session-contracts.js +36 -40
  27. package/dist/notary-source-run.js +122 -0
  28. package/dist/package-contracts/auditor-output.js +66 -0
  29. package/dist/package-contracts/evidence-child-output.js +34 -0
  30. package/dist/package-contracts/judge-output.js +2 -0
  31. package/dist/package-contracts/terminating-tools.js +25 -2
  32. package/dist/package-resources/method-skill.js +254 -0
  33. package/dist/packaged-role-registry.js +36 -0
  34. package/dist/pi/durable-principal.js +61 -0
  35. package/dist/pi/in-process-session.js +25 -6
  36. package/dist/pi/known-failure.js +52 -0
  37. package/dist/pi/role-turn-host.js +430 -0
  38. package/dist/public-cli/auto-resume.js +414 -0
  39. package/dist/public-cli/cli-errors.js +8 -0
  40. package/dist/public-cli/cli-io.js +1 -0
  41. package/dist/public-cli/command-renderer.js +5 -0
  42. package/dist/public-cli/doctor-run.js +84 -0
  43. package/dist/public-cli/inspector-run.js +136 -0
  44. package/dist/public-cli/instruction-seat-run.js +256 -0
  45. package/dist/public-cli/invocation.js +2225 -0
  46. package/dist/public-cli/judge-run.js +118 -0
  47. package/dist/public-cli/load-production-acp-host.js +40 -0
  48. package/dist/public-cli/main.js +1140 -652
  49. package/dist/public-cli/notary-run.js +161 -0
  50. package/dist/public-cli/option-definitions.js +1192 -0
  51. package/dist/public-cli/post-admission.js +667 -0
  52. package/dist/public-cli/public-run-credentials.js +49 -0
  53. package/dist/public-cli/registry.js +4 -1
  54. package/dist/public-cli/reviewer-dispatch-rejection.js +77 -0
  55. package/dist/public-cli/run-lifecycle.js +1344 -0
  56. package/dist/public-cli/seat-ticket-binding.js +113 -0
  57. package/dist/public-cli/settlement.js +3441 -0
  58. package/dist/public-cli/terminal.js +135 -0
  59. package/dist/public-cli/turn-request.js +25 -0
  60. package/dist/public-role-summons.js +301 -0
  61. package/dist/receipt-delivery-policy.js +10 -0
  62. package/dist/reviewer-child-executor.js +80 -8
  63. package/dist/reviewer-execution-ledger.js +2 -1
  64. package/dist/run-terminal-artifacts.js +195 -0
  65. package/dist/run-ticket-number.js +40 -0
  66. package/dist/session-assistant-usage.js +107 -0
  67. package/dist/shape-unreadable-failure.js +34 -0
  68. package/dist/submission-errors.js +3 -0
  69. package/dist/submission-ledger.js +388 -0
  70. package/dist/ticket-provenance-contracts.js +115 -0
  71. package/dist/ticket-provenance.js +340 -0
  72. package/extensions/role-runtime.ts +7 -0
  73. package/package.json +1 -1
  74. package/scripts/build-package.mjs +2 -1
  75. package/souls/doctor-auditor.md +1 -0
  76. package/souls/evidence-child.md +1 -0
  77. package/src/acp-host/production-host.ts +3 -0
  78. package/src/acp-host/role-envelope.ts +2 -2
  79. package/src/acp-host/role-turn-host.ts +9 -1
  80. package/src/analyst-gate-cycles-read.ts +37 -2
  81. package/src/archivist-record-entry.ts +45 -1
  82. package/src/audit-escalation.ts +17 -0
  83. package/src/auditor-role.ts +22 -0
  84. package/src/auditor-soul.ts +42 -0
  85. package/src/compliance-transport.ts +177 -66
  86. package/src/doctor-auditor.ts +10 -14
  87. package/src/doctor-contracts.ts +1 -0
  88. package/src/doctor-role.ts +2 -2
  89. package/src/evidence-child-role.ts +22 -0
  90. package/src/gatekeeper-pass-envelope.ts +109 -0
  91. package/src/gatekeeper-role.ts +145 -104
  92. package/src/host-contracts.ts +9 -1
  93. package/src/institutional-resolution.ts +8 -163
  94. package/src/judge-auditor.ts +10 -15
  95. package/src/judge-role.ts +8 -0
  96. package/src/navigator-attendance.ts +25 -9
  97. package/src/navigator-public-session.ts +217 -0
  98. package/src/navigator-session-contracts.ts +61 -54
  99. package/src/notary-source-run.ts +3 -1
  100. package/src/package-contracts/auditor-output.ts +82 -0
  101. package/src/package-contracts/evidence-child-output.ts +51 -0
  102. package/src/package-contracts/judge-output.ts +1 -0
  103. package/src/package-contracts/reviewer-output.ts +2 -2
  104. package/src/package-contracts/terminating-tools.ts +31 -1
  105. package/src/packaged-role-registry.ts +43 -0
  106. package/src/pi/adapter.ts +2 -1
  107. package/src/pi/in-process-session.ts +38 -12
  108. package/src/pi/role-turn-host.ts +34 -0
  109. package/src/public-cli/cli.ts +14 -6
  110. package/src/public-cli/instruction-seat-run.ts +289 -50
  111. package/src/public-cli/invocation.ts +66 -25
  112. package/src/public-cli/option-definitions.ts +61 -0
  113. package/src/public-cli/post-admission.ts +7 -1
  114. package/src/public-cli/registry.ts +4 -1
  115. package/src/public-cli/run-lifecycle.ts +22 -3
  116. package/src/public-cli/settlement.ts +120 -5
  117. package/src/public-cli/terminal.ts +2 -0
  118. package/src/public-role-summons.ts +431 -0
  119. package/src/receipt-delivery-policy.ts +10 -0
  120. package/src/reviewer-agent.ts +1 -1
  121. package/src/reviewer-child-executor.ts +114 -12
  122. package/src/reviewer-execution-ledger.ts +3 -2
  123. package/src/role-runtime.ts +158 -17
  124. package/src/session-assistant-usage.ts +119 -0
  125. package/src/session-opening-materials.ts +1 -1
  126. package/src/shape-unreadable-failure.ts +49 -0
  127. package/src/submission-errors.ts +3 -0
  128. package/dist/evidence-child-executor.js +0 -829
  129. package/src/evidence-child-executor.ts +0 -1140
@@ -1,11 +1,8 @@
1
- import { Type } from "typebox";
2
- import { auditorRunDirectory, createAuditorDossierTool, persistGateSubmissionCandidate, } from "./auditor-dossier-tool.js";
3
- import { executeAuditorChild } from "./evidence-child-executor.js";
4
- import { openToolObject } from "./open-tool-schema.js";
5
- import { loadGatekeeperSessionMaterials } from "./session-opening-materials.js";
1
+ import { auditorRunDirectory, persistGateSubmissionCandidate, } from "./auditor-dossier-tool.js";
6
2
  import { GatekeeperDecisionError } from "./submission-errors.js";
7
3
  import { INSPECTOR_OUTPUT_TOOL_NAME } from "./inspector-contracts.js";
8
4
  import { GATEKEEPER_OUTPUT_TOOL_NAME, gatekeeperDecisionSchema, gatekeeperOutputSchema, } from "./package-contracts/gatekeeper-output.js";
5
+ import { retainedShapeUnreadable } from "./shape-unreadable-failure.js";
9
6
  export const INSPECTOR_OUTPUT_TOOL = INSPECTOR_OUTPUT_TOOL_NAME;
10
7
  export const NOTARY_OUTPUT_TOOL = "ak_notary_output";
11
8
  function gateSeatLabel(stage) {
@@ -20,13 +17,6 @@ export class GatekeeperEscalationError extends Error {
20
17
  this.gatekeeper = gatekeeper;
21
18
  }
22
19
  }
23
- // Unknown fields so wrong types/spellings still reach projection (ADR 0055/0057; 仓第 0 条).
24
- // Opening goes through the sole openToolObject owner — no parallel transport helper.
25
- const officerDecisionSchema = openToolObject(Type.Object({
26
- status: Type.Unknown({ description: "pass | bounce | escalate — 形状指引,非 schema 闸" }),
27
- reason: Type.Optional(Type.Unknown({ description: "status 为 escalate 时的理由" })),
28
- findings: Type.Unknown({ description: "string[] findings,随 pass、bounce 或 escalate 留存" }),
29
- }));
30
20
  /**
31
21
  * Direct-seat decision tool spec (#639). Lifecycle assembly stays on the
32
22
  * registration envelope — src/role-runtime.ts (ADR 0018). Schema authority is
@@ -42,27 +32,17 @@ export const GATEKEEPER_TOOL_SPEC = {
42
32
  function result(content, details) {
43
33
  return { content: [{ type: "text", text: content }], details };
44
34
  }
45
- /** Shared officer decision tool — open transport; projection owns legality. */
46
- export function createOfficerDecisionTool(name) {
47
- return {
48
- name,
49
- description: "提交一份 typed pass/bounce/escalate 决议。",
50
- parameters: officerDecisionSchema,
51
- async execute(_id, args) { return result(`已收 ${String(args?.status)}`, args); },
52
- };
53
- }
54
35
  /** Gatekeeper province decision tool — open transport; package-contract projection owns legality. */
55
36
  export function createGatekeeperOutputTool() {
56
37
  return {
57
38
  name: GATEKEEPER_OUTPUT_TOOL_NAME,
58
39
  description: "提交门下省派官决定。",
59
40
  parameters: gatekeeperDecisionSchema,
60
- async execute(_id, args) { return result(`已收 ${String(args?.status)}`, args); },
41
+ async execute(_id, args) {
42
+ return result(`已收 ${String(args?.status)}`, args);
43
+ },
61
44
  };
62
45
  }
63
- async function defaultLoadSoul(role) {
64
- return loadGatekeeperSessionMaterials(role);
65
- }
66
46
  function failureReason(error) {
67
47
  if (error instanceof AggregateError)
68
48
  return error.errors.map(failureReason).join("; ");
@@ -88,14 +68,15 @@ function retainedSubmission(decision) {
88
68
  : decision;
89
69
  }
90
70
  /**
91
- * No usable explicit release is infrastructure failure, not a judgment status.
92
- * Original submission is retained for the failure channel (#475).
71
+ * Shape-unreadable officer decision — retain original candidate + typed reason.
72
+ * Not a forged bounce, not transport abort (CLAUDE.md §0 / ADR 0055).
73
+ * Real provider/engine/disk failures stay transport_failure elsewhere.
93
74
  */
94
- function noUsableReleaseFailure(stage, decision) {
75
+ function shapeUnreadable(officer, decision, reason = "decision 无显式 pass/bounce/escalate") {
95
76
  return {
96
- status: "transport_failure",
97
- stage,
98
- reason: "decision 无显式 pass/bounce/escalate",
77
+ status: "unreadable",
78
+ officer,
79
+ reason,
99
80
  submission: retainedSubmission(decision),
100
81
  };
101
82
  }
@@ -107,7 +88,7 @@ function readRecord(value) {
107
88
  function projectOfficerDecision(officer, decision) {
108
89
  const record = readRecord(decision);
109
90
  if (record === undefined)
110
- return noUsableReleaseFailure(officer, decision);
91
+ return shapeUnreadable(officer, decision);
111
92
  if (record.status === "bounce") {
112
93
  return {
113
94
  status: "bounce",
@@ -133,63 +114,105 @@ function projectOfficerDecision(officer, decision) {
133
114
  submission: retainedSubmission(decision),
134
115
  };
135
116
  }
136
- return noUsableReleaseFailure(officer, decision);
117
+ return shapeUnreadable(officer, decision);
137
118
  }
138
- /** Submission-gate summons: subject kind determines the officer without a province child. */
139
- export async function runGatekeeper(options) {
140
- const loadSoul = options.loadSoul ?? defaultLoadSoul;
119
+ /** Map a public-role terminal onto the gate officer result surface. */
120
+ function projectOfficerTerminal(officer, summoned) {
121
+ const terminal = summoned.terminal;
122
+ const outcome = terminal?.roleOutcome;
123
+ if (outcome === undefined) {
124
+ const detail = summoned.stderr?.trim();
125
+ return {
126
+ status: "transport_failure",
127
+ stage: officer,
128
+ reason: detail && detail.length > 0
129
+ ? `${gateSeatLabel(officer)} public summon exit ${summoned.exitCode}: ${detail}`
130
+ : `${gateSeatLabel(officer)} public summon produced no terminal (exit ${summoned.exitCode})`,
131
+ submission: summoned,
132
+ };
133
+ }
134
+ if (outcome.kind === "no_receipt") {
135
+ return {
136
+ status: "no_receipt",
137
+ stage: officer,
138
+ reason: `${gateSeatLabel(officer)}未产生已接受回执即散局`,
139
+ facts: outcome,
140
+ };
141
+ }
142
+ if (outcome.kind === "failure") {
143
+ // Single settlement marker only (ADR 0055 / #675) — no cause=output re-derivation.
144
+ const shape = retainedShapeUnreadable(outcome.decisiveFacts);
145
+ if (shape !== undefined) {
146
+ return shapeUnreadable(officer, shape.candidate, outcome.diagnostic);
147
+ }
148
+ return {
149
+ status: "transport_failure",
150
+ stage: officer,
151
+ reason: outcome.diagnostic,
152
+ submission: outcome.decisiveFacts,
153
+ };
154
+ }
155
+ if (outcome.kind === "accepted") {
156
+ return projectOfficerDecision(officer, {
157
+ status: outcome.status,
158
+ ...outcome.decisiveFacts,
159
+ });
160
+ }
161
+ return {
162
+ status: "transport_failure",
163
+ stage: officer,
164
+ reason: `${gateSeatLabel(officer)} public summon returned unusable terminal kind`,
165
+ submission: outcome,
166
+ };
167
+ }
168
+ /**
169
+ * Summon + project only. Lifecycle book and host abort face live on the shared
170
+ * submit envelope (`gatekeeper-pass-envelope.ts`, ADR 0018 / #675).
171
+ */
172
+ export async function projectGatekeeperRun(options) {
141
173
  const officer = options.subject.kind === "worker_completion" ? "inspector" : "notary";
142
- // Resolve once so dossier tool and child session share the same run binding (#632).
143
174
  const runDirectory = options.runDirectory ?? auditorRunDirectory(options.context);
175
+ if (runDirectory === undefined) {
176
+ return {
177
+ officer,
178
+ result: {
179
+ status: "transport_failure",
180
+ stage: officer,
181
+ reason: `${gateSeatLabel(officer)} requires a parent run directory pointer`,
182
+ },
183
+ };
184
+ }
144
185
  // Pointer-only summons need a resolvable leaf: Grok session.jsonl is header-only
145
186
  // (#617 DK-4); write the in-memory tool-call candidate as a run artifact first (#632).
146
- const submissionCandidate = runDirectory === undefined
147
- ? undefined
148
- : persistGateSubmissionCandidate(runDirectory, options.context);
187
+ persistGateSubmissionCandidate(runDirectory, options.context);
188
+ let summoned;
149
189
  try {
150
- const officerRun = await executeAuditorChild({
151
- context: options.context,
152
- roleLabel: officer === "inspector" ? "Inspector" : "Notary",
153
- gateSeat: officer,
154
- systemPrompt: await loadSoul(officer),
155
- prompt: "卷宗已受理。",
156
- tool: createOfficerDecisionTool(officer === "inspector" ? INSPECTOR_OUTPUT_TOOL : NOTARY_OUTPUT_TOOL),
157
- // Same locator as 审刑院 — run directory + path identifiers only; no receipt body (#632).
158
- dossierTool: createAuditorDossierTool(runDirectory, submissionCandidate === undefined ? undefined : { submissionCandidate }),
159
- ...(options.signal === undefined ? {} : { signal: options.signal }),
160
- ...(runDirectory === undefined ? {} : { runDirectory }),
161
- });
162
- if (officerRun.noReceiptLifecycle !== undefined) {
163
- return { status: "no_receipt", stage: officer, reason: `${gateSeatLabel(officer)}未产生已接受回执即散局`, facts: officerRun.noReceiptLifecycle };
164
- }
165
- return projectOfficerDecision(officer, officerRun.decision);
190
+ const summon = options.summonOfficer
191
+ ?? (async (nextOfficer, sourceRunDirectory, officerSignal) => {
192
+ const { summonGateOfficer } = await import("./public-role-summons.js");
193
+ return summonGateOfficer({
194
+ officer: nextOfficer,
195
+ sourceRunDirectory,
196
+ cwd: options.context.cwd ?? process.cwd(),
197
+ ...(officerSignal === undefined ? {} : { signal: officerSignal }),
198
+ });
199
+ });
200
+ summoned = await summon(officer, runDirectory, options.signal);
166
201
  }
167
202
  catch (error) {
168
- return { status: "transport_failure", stage: officer, reason: failureReason(error) };
203
+ return {
204
+ officer,
205
+ result: { status: "transport_failure", stage: officer, reason: failureReason(error) },
206
+ };
169
207
  }
208
+ return {
209
+ officer,
210
+ result: projectOfficerTerminal(officer, summoned),
211
+ summoned,
212
+ };
170
213
  }
171
- /** Project GatekeeperResult onto a submit path: transport→failInfrastructure; bounce/no_receipt→typed throw; pass silent. */
172
- export async function requireGatekeeperPass(options) {
173
- const gatekeeper = await runGatekeeper({
174
- context: options.context,
175
- subject: options.subject,
176
- ...(options.signal === undefined ? {} : { signal: options.signal }),
177
- });
178
- if (gatekeeper.status === "pass")
179
- return;
180
- if (gatekeeper.status === "transport_failure") {
181
- // Typed stage/reason/submission ride failInfrastructure → durable tool_result (#475).
182
- const error = new Error(`交卷闸 transport_failure(${gatekeeper.stage}):${gatekeeper.reason}`);
183
- error.stage = gatekeeper.stage;
184
- error.reason = gatekeeper.reason;
185
- if (gatekeeper.submission !== undefined)
186
- error.submission = gatekeeper.submission;
187
- options.hostActions.failInfrastructure(error, options.context, options.toolCallId);
188
- }
189
- if (gatekeeper.status === "escalate") {
190
- throw new GatekeeperEscalationError(gatekeeper);
191
- }
192
- // Envelope owns the execute→tool_result bridge; this module only projects + throws.
193
- options.hostActions.bindSubmissionNonPass(options.toolCallId, gatekeeper);
194
- throw new GatekeeperDecisionError(gatekeeper);
214
+ /** Submission-gate summons: subject kind → officer; activation is public role path (#675). Projection only. */
215
+ export async function runGatekeeper(options) {
216
+ const { result } = await projectGatekeeperRun(options);
217
+ return result;
195
218
  }
@@ -0,0 +1,78 @@
1
+ /**
2
+ * Single authority for #617 DK-4 cross-host prior-native projection.
3
+ * Classifies the prior volume into the two record families that exist
4
+ * (Pi native session file / sitian run records), never by host name.
5
+ */
6
+ import { access, readdir } from "node:fs/promises";
7
+ import { dirname, join } from "node:path";
8
+ function isEnoent(error) {
9
+ return typeof error === "object" && error !== null && error.code === "ENOENT";
10
+ }
11
+ /** Present Pi native session path, or empty when ENOENT. */
12
+ async function listPiNativeRecordPaths(sessionFile) {
13
+ try {
14
+ await access(sessionFile);
15
+ return [sessionFile];
16
+ }
17
+ catch (error) {
18
+ if (isEnoent(error))
19
+ return [];
20
+ throw error;
21
+ }
22
+ }
23
+ /**
24
+ * Present sitian records.jsonl paths under sessionParent topology (#717).
25
+ * resolveSitianRecordPathInLedger writes dirname(sessionParent)/<category>/records.jsonl
26
+ * when sessionParent is inside ledger home — never session.jsonl itself.
27
+ */
28
+ async function listSitianRecordPaths(sessionParent) {
29
+ const sessionRoot = dirname(sessionParent);
30
+ let entries;
31
+ try {
32
+ entries = await readdir(sessionRoot, { withFileTypes: true });
33
+ }
34
+ catch (error) {
35
+ if (isEnoent(error))
36
+ return [];
37
+ throw error;
38
+ }
39
+ const recordPaths = [];
40
+ for (const entry of entries) {
41
+ if (!entry.isDirectory())
42
+ continue;
43
+ const recordFile = join(sessionRoot, entry.name, "records.jsonl");
44
+ try {
45
+ await access(recordFile);
46
+ recordPaths.push(recordFile);
47
+ }
48
+ catch (error) {
49
+ if (!isEnoent(error))
50
+ throw error;
51
+ }
52
+ }
53
+ recordPaths.sort();
54
+ return recordPaths;
55
+ }
56
+ /**
57
+ * Project one hostTransition only for a real host switch. Empty native volume
58
+ * still yields a typed switch (empty path list).
59
+ *
60
+ * Pi wrote its own session.jsonl; every other host's run volume is the sitian
61
+ * record set on the live run (ADR 0077 `record-scope-phase-two`, #717) — the CLI's
62
+ * own journals stay in the operator home and are never copied here.
63
+ */
64
+ export async function projectHostTransitionPriorNative(input) {
65
+ if (input.previousHost === input.liveHost)
66
+ return undefined;
67
+ if (input.previousHost === "pi") {
68
+ return {
69
+ priorNativeKind: "pi-native",
70
+ priorNativePaths: await listPiNativeRecordPaths(input.piSessionFile),
71
+ };
72
+ }
73
+ // Sitian path handoff only — do not read bytes.
74
+ return {
75
+ priorNativeKind: "sitian",
76
+ priorNativePaths: await listSitianRecordPaths(input.piSessionFile),
77
+ };
78
+ }
@@ -1,24 +1,4 @@
1
- import { readFile, writeFile } from "node:fs/promises";
2
- import { join } from "node:path";
3
1
  import { seatModelOnly } from "./public-cli/registry.js";
4
- const INSTITUTIONAL_RESOLUTION_FILE = "institutional-resolution.json";
5
- class InstitutionalResolutionError extends Error {
6
- reason;
7
- constructor(message, reason, options) {
8
- super(message, options);
9
- this.name = "InstitutionalResolutionError";
10
- this.reason = reason;
11
- }
12
- }
13
- function cleanSelection(model) {
14
- if (model === void 0) return void 0;
15
- if (typeof model.provider !== "string" || typeof model.model !== "string") return void 0;
16
- return {
17
- provider: model.provider,
18
- model: model.model,
19
- ...model.thinking === void 0 ? {} : { thinking: model.thinking }
20
- };
21
- }
22
2
  function resolveConfiguredProvinceOfficer(config, officer) {
23
3
  const ownModel = seatModelOnly(config.seats[officer]);
24
4
  if (ownModel !== void 0) {
@@ -33,80 +13,6 @@ function resolveConfiguredProvinceOfficer(config, officer) {
33
13
  }
34
14
  return { source: "unconfigured" };
35
15
  }
36
- function resolveInstitutionalSeatSelections(config, parentEffectiveModel) {
37
- const parentSelection = cleanSelection(parentEffectiveModel);
38
- const gatekeeper = resolveConfiguredProvinceOfficer(config, "gatekeeper").selection ?? parentSelection;
39
- const inspector = resolveConfiguredProvinceOfficer(config, "inspector").selection ?? parentSelection;
40
- const notary = resolveConfiguredProvinceOfficer(config, "notary").selection ?? parentSelection;
41
- const auditor = parentSelection;
42
- const evidenceChild = parentSelection;
43
- const navigator = cleanSelection(seatModelOnly(config.seats?.navigator));
44
- return {
45
- version: 1,
46
- seats: {
47
- ...gatekeeper === void 0 ? {} : { gatekeeper },
48
- ...inspector === void 0 ? {} : { inspector },
49
- ...notary === void 0 ? {} : { notary },
50
- ...auditor === void 0 ? {} : { auditor },
51
- ...evidenceChild === void 0 ? {} : { evidenceChild },
52
- ...navigator === void 0 ? {} : { navigator }
53
- }
54
- };
55
- }
56
- async function writeInstitutionalResolutionPage(runDirectory, page) {
57
- const filePath = join(runDirectory, INSTITUTIONAL_RESOLUTION_FILE);
58
- await writeFile(filePath, `${JSON.stringify(page, null, 2)}
59
- `, "utf8");
60
- }
61
- async function readInstitutionalSeatSelection(runDirectory, seat) {
62
- const filePath = join(runDirectory, INSTITUTIONAL_RESOLUTION_FILE);
63
- let raw;
64
- try {
65
- raw = await readFile(filePath, "utf8");
66
- } catch (error) {
67
- throw new InstitutionalResolutionError(
68
- `institutional resolution page is missing at ${filePath}`,
69
- "missing-page",
70
- { cause: error }
71
- );
72
- }
73
- let page;
74
- try {
75
- page = JSON.parse(raw);
76
- } catch (error) {
77
- throw new InstitutionalResolutionError(
78
- `institutional resolution page is corrupted at ${filePath}`,
79
- "corrupted",
80
- { cause: error }
81
- );
82
- }
83
- if (typeof page !== "object" || page === null || page.version !== 1) {
84
- throw new InstitutionalResolutionError(
85
- `institutional resolution page format is invalid at ${filePath}`,
86
- "invalid-format"
87
- );
88
- }
89
- const seats = page.seats;
90
- if (typeof seats !== "object" || seats === null) {
91
- throw new InstitutionalResolutionError(
92
- `institutional resolution page missing seats object at ${filePath}`,
93
- "invalid-format"
94
- );
95
- }
96
- const selection = cleanSelection(seats[seat]);
97
- if (selection === void 0) {
98
- throw new InstitutionalResolutionError(
99
- `institutional resolution page has no resolution for seat "${seat}" at ${filePath}`,
100
- "missing-seat"
101
- );
102
- }
103
- return selection;
104
- }
105
16
  export {
106
- INSTITUTIONAL_RESOLUTION_FILE,
107
- InstitutionalResolutionError,
108
- readInstitutionalSeatSelection,
109
- resolveConfiguredProvinceOfficer,
110
- resolveInstitutionalSeatSelections,
111
- writeInstitutionalResolutionPage
17
+ resolveConfiguredProvinceOfficer
112
18
  };
@@ -0,0 +1,26 @@
1
+ import { runComplianceAudit, } from "./compliance-transport.js";
2
+ import { auditorRunDirectory } from "./auditor-dossier-tool.js";
3
+ import { readJudgeAuditSubjects, requireAuditMaterials, resolveAuditDossier, } from "./dossier-resolution.js";
4
+ export const JUDGE_AUDIT_TOOL_NAME = "ak_soul_audit_decision";
5
+ export const SOUL_AUDIT_TOOL_NAME = JUDGE_AUDIT_TOOL_NAME;
6
+ /**
7
+ * Judge compliance via public auditor activation (#675 / ADR 0062 / owner r11).
8
+ * 审刑院 is an independent role; subject=judge selects souls/judge-auditor.md.
9
+ */
10
+ export function createPiJudgeAuditor() {
11
+ return async (options) => {
12
+ const dossier = resolveAuditDossier();
13
+ requireAuditMaterials(dossier);
14
+ const subjects = readJudgeAuditSubjects(options.context);
15
+ requireAuditMaterials(subjects);
16
+ return runComplianceAudit({
17
+ subject: "judge",
18
+ context: options.context,
19
+ ...(auditorRunDirectory(options.context) === undefined
20
+ ? {}
21
+ : { runDirectory: auditorRunDirectory(options.context) }),
22
+ ...(options.signal === undefined ? {} : { signal: options.signal }),
23
+ ...(options.summonAuditor === undefined ? {} : { summonAuditor: options.summonAuditor }),
24
+ });
25
+ };
26
+ }
@@ -0,0 +1,227 @@
1
+ /**
2
+ * Canonical ledger session JSONL read primitives (shared owner).
3
+ * Consumers (ticket-trajectory, analyst, …) must import here — no second parse kernel.
4
+ */
5
+ import { readFile } from "node:fs/promises";
6
+ /**
7
+ * Loud JSONL failure that still retains rows parsed before the bad line.
8
+ * Callers that only need the throw keep catching Error; owners that must
9
+ * surface partial typed facts (e.g. first-frame timestamp) read prefixRows.
10
+ */
11
+ export class LedgerSessionJsonlError extends Error {
12
+ path;
13
+ line;
14
+ prefixRows;
15
+ constructor(message, init) {
16
+ super(message);
17
+ this.name = "LedgerSessionJsonlError";
18
+ this.path = init.path;
19
+ this.line = init.line;
20
+ this.prefixRows = init.prefixRows;
21
+ }
22
+ }
23
+ function isRecord(value) {
24
+ return typeof value === "object" && value !== null && !Array.isArray(value);
25
+ }
26
+ /**
27
+ * Read session JSONL with honest live-tail semantics:
28
+ * a malformed line is tolerated only when it is an unfinished final
29
+ * fragment at EOF (no record terminator after it). Any malformed line
30
+ * completed by a line terminator must fail loudly with file and 1-based
31
+ * line context — even when no non-empty record follows — never silently
32
+ * under-count.
33
+ *
34
+ * Loud failures throw LedgerSessionJsonlError carrying prefixRows so the
35
+ * single parse kernel can still expose facts obtained before the bad line.
36
+ */
37
+ export async function readLedgerSessionJsonl(path) {
38
+ const text = await readFile(path, "utf8");
39
+ // split keeps a trailing empty segment iff text ends with "\n", so
40
+ // index < lines.length - 1 means this segment was terminated.
41
+ const lines = text.split("\n");
42
+ const rows = [];
43
+ for (let index = 0; index < lines.length; index += 1) {
44
+ const line = lines[index];
45
+ if (!line.trim())
46
+ continue;
47
+ let row;
48
+ try {
49
+ row = JSON.parse(line);
50
+ }
51
+ catch (error) {
52
+ if (!(error instanceof SyntaxError))
53
+ throw error;
54
+ const completedByTerminator = index < lines.length - 1;
55
+ if (completedByTerminator) {
56
+ throw new LedgerSessionJsonlError(`malformed JSONL record in ${path} at line ${index + 1}: ${error.message}`, { path, line: index + 1, prefixRows: rows });
57
+ }
58
+ // unfinished fragment at EOF — keep prior complete rows
59
+ break;
60
+ }
61
+ // Syntactically complete line: must be a session object. Silent omission
62
+ // would under-count ledger evidence (failure honesty).
63
+ if (!isRecord(row)) {
64
+ const kind = row === null ? "null" : Array.isArray(row) ? "array" : typeof row;
65
+ throw new LedgerSessionJsonlError(`complete non-object JSONL record in ${path} at line ${index + 1}: expected object, got ${kind}`, { path, line: index + 1, prefixRows: rows });
66
+ }
67
+ rows.push(row);
68
+ }
69
+ return rows;
70
+ }
71
+ /** First and last record timestamps in encounter order. */
72
+ export function extractSessionTimestampSpan(rows) {
73
+ let startedAt;
74
+ let endedAt;
75
+ for (const row of rows) {
76
+ if (typeof row.timestamp !== "string" || !row.timestamp)
77
+ continue;
78
+ if (startedAt === undefined)
79
+ startedAt = row.timestamp;
80
+ endedAt = row.timestamp;
81
+ }
82
+ return {
83
+ ...(startedAt !== undefined ? { startedAt } : {}),
84
+ ...(endedAt !== undefined ? { endedAt } : {}),
85
+ };
86
+ }
87
+ /**
88
+ * Ordered-unique model ids from session frames (first-seen order).
89
+ * Sources (same faces ticket-trajectory already reads — single parse kernel):
90
+ * - `model_change.modelId`
91
+ * - assistant `message.model`
92
+ * Blank / non-string values are skipped. Does not invent a default model.
93
+ */
94
+ export function extractSessionModelSequence(rows) {
95
+ const seen = new Set();
96
+ const ordered = [];
97
+ const push = (raw) => {
98
+ const model = raw.trim();
99
+ if (model === "" || seen.has(model))
100
+ return;
101
+ seen.add(model);
102
+ ordered.push(model);
103
+ };
104
+ for (const row of rows) {
105
+ if (row.type === "model_change" && typeof row.modelId === "string") {
106
+ push(row.modelId);
107
+ }
108
+ const message = isRecord(row.message) ? row.message : undefined;
109
+ if (message?.role === "assistant" && typeof message.model === "string") {
110
+ push(message.model);
111
+ }
112
+ }
113
+ return ordered;
114
+ }
115
+ /** First line of a bash `command` argument (sole owner of this summary). */
116
+ export function bashCommandFirstLine(command) {
117
+ const match = /^[^\r\n]*/.exec(command);
118
+ return match?.[0] ?? "";
119
+ }
120
+ /**
121
+ * Pair toolCall frames → toolResult frames by toolCallId.
122
+ * Throws when a tool-bearing frame is structurally unreadable for association
123
+ * (toolCall missing string id, toolResult missing string toolCallId).
124
+ * Unpaired open calls remain without endedAt — that is incomplete, not unreadable.
125
+ */
126
+ export function extractSessionToolIntervals(rows) {
127
+ const order = [];
128
+ const openById = new Map();
129
+ for (const row of rows) {
130
+ const rowTimestamp = typeof row.timestamp === "string" ? row.timestamp : undefined;
131
+ const message = isRecord(row.message) ? row.message : undefined;
132
+ if (message?.role === "assistant" && Array.isArray(message.content)) {
133
+ const callTimestamp = typeof message.timestamp === "string" && message.timestamp
134
+ ? message.timestamp
135
+ : rowTimestamp;
136
+ for (const part of message.content) {
137
+ if (!isRecord(part) || part.type !== "toolCall")
138
+ continue;
139
+ if (typeof part.id !== "string" || part.id.length === 0) {
140
+ throw new Error("toolCall frame missing string id");
141
+ }
142
+ if (typeof part.name !== "string" || part.name.length === 0) {
143
+ throw new Error(`toolCall ${part.id} missing string name`);
144
+ }
145
+ if (callTimestamp === undefined || callTimestamp.length === 0) {
146
+ throw new Error(`toolCall ${part.id} missing timestamp`);
147
+ }
148
+ if (openById.has(part.id)) {
149
+ throw new Error(`duplicate toolCall id ${part.id}`);
150
+ }
151
+ const args = isRecord(part.arguments) ? part.arguments : undefined;
152
+ // Ticket surface: only bash first-line summary is authorized here.
153
+ const command = part.name === "bash" &&
154
+ args !== undefined &&
155
+ typeof args.command === "string"
156
+ ? bashCommandFirstLine(args.command)
157
+ : undefined;
158
+ const interval = {
159
+ toolCallId: part.id,
160
+ toolName: part.name,
161
+ startedAt: callTimestamp,
162
+ ...(command !== undefined ? { command } : {}),
163
+ };
164
+ order.push(interval);
165
+ openById.set(part.id, interval);
166
+ }
167
+ }
168
+ if (message?.role === "toolResult") {
169
+ if (typeof message.toolCallId !== "string" || message.toolCallId.length === 0) {
170
+ throw new Error("toolResult frame missing string toolCallId");
171
+ }
172
+ const resultTimestamp = typeof message.timestamp === "string" && message.timestamp
173
+ ? message.timestamp
174
+ : rowTimestamp;
175
+ if (resultTimestamp === undefined || resultTimestamp.length === 0) {
176
+ throw new Error(`toolResult ${message.toolCallId} missing timestamp`);
177
+ }
178
+ const open = openById.get(message.toolCallId);
179
+ if (open === undefined) {
180
+ // Result without a prior call is still associable as a closed interval
181
+ // once a name is known; keep structural readability without inventing a call.
182
+ const toolName = typeof message.toolName === "string" && message.toolName.length > 0
183
+ ? message.toolName
184
+ : "unknown";
185
+ order.push({
186
+ toolCallId: message.toolCallId,
187
+ toolName,
188
+ startedAt: resultTimestamp,
189
+ endedAt: resultTimestamp,
190
+ });
191
+ continue;
192
+ }
193
+ if (open.endedAt !== undefined) {
194
+ throw new Error(`duplicate toolResult for toolCallId ${message.toolCallId}`);
195
+ }
196
+ open.endedAt = resultTimestamp;
197
+ }
198
+ }
199
+ return order.map((interval) => {
200
+ const base = {
201
+ toolCallId: interval.toolCallId,
202
+ toolName: interval.toolName,
203
+ startedAt: interval.startedAt,
204
+ ...(interval.command !== undefined ? { command: interval.command } : {}),
205
+ };
206
+ return interval.endedAt === undefined
207
+ ? base
208
+ : { ...base, endedAt: interval.endedAt };
209
+ });
210
+ }
211
+ export function intervalRowsAroundAnchor(rows, anchorIndex, isBindingRow) {
212
+ let start = 0;
213
+ for (let i = anchorIndex; i >= 0; i -= 1) {
214
+ if (isBindingRow(rows[i])) {
215
+ start = i;
216
+ break;
217
+ }
218
+ }
219
+ let end = rows.length;
220
+ for (let i = Math.max(anchorIndex, start) + 1; i < rows.length; i += 1) {
221
+ if (isBindingRow(rows[i])) {
222
+ end = i;
223
+ break;
224
+ }
225
+ }
226
+ return { rows: rows.slice(start, end), closed: end < rows.length };
227
+ }