@akagilnc/pi-workflow-roles 0.1.1751

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 (192) hide show
  1. package/LICENSE +202 -0
  2. package/README.md +104 -0
  3. package/README.zh-CN.md +133 -0
  4. package/THIRD_PARTY_NOTICES.md +60 -0
  5. package/dist/activation-ledger-git.js +68 -0
  6. package/dist/activation-ledger-session.js +120 -0
  7. package/dist/activation-ledger-topology.js +239 -0
  8. package/dist/activation-reconciliation.js +61 -0
  9. package/dist/audit-escalation.js +108 -0
  10. package/dist/auditor-dossier-tool.js +35 -0
  11. package/dist/canonical-json.js +78 -0
  12. package/dist/compliance-transport.js +77 -0
  13. package/dist/doctor-contracts.js +172 -0
  14. package/dist/dossier-resolution.js +103 -0
  15. package/dist/evidence-child-executor.js +661 -0
  16. package/dist/exact-utf8.js +12 -0
  17. package/dist/git-object-id.js +7 -0
  18. package/dist/in-process-session.js +50 -0
  19. package/dist/merger-contracts.js +76 -0
  20. package/dist/navigator-attendance.js +995 -0
  21. package/dist/navigator-invocation-identity.js +220 -0
  22. package/dist/open-tool-schema.js +39 -0
  23. package/dist/package-contracts/collector-output.js +50 -0
  24. package/dist/package-contracts/fixer-output.js +72 -0
  25. package/dist/package-contracts/fixer-packet.js +77 -0
  26. package/dist/package-contracts/judge-output.js +17 -0
  27. package/dist/package-contracts/reviewer-output.js +82 -0
  28. package/dist/package-contracts/terminating-tools.js +173 -0
  29. package/dist/package-contracts/worker-output.js +13 -0
  30. package/dist/package-owned-tool-idle.js +104 -0
  31. package/dist/packaged-role-registry.js +34 -0
  32. package/dist/public-cli/main.js +23867 -0
  33. package/dist/public-command-renderer.js +20 -0
  34. package/dist/reviewer-agent.js +93 -0
  35. package/dist/reviewer-child-executor.js +23 -0
  36. package/dist/reviewer-construction.js +95 -0
  37. package/dist/reviewer-dispatch.js +77 -0
  38. package/dist/reviewer-execution-ledger.js +160 -0
  39. package/dist/reviewer-failure-diagnostic.js +17 -0
  40. package/dist/reviewer-git-snapshot.js +38 -0
  41. package/dist/reviewer-pinned-git.js +146 -0
  42. package/dist/reviewer-preflight-error.js +15 -0
  43. package/dist/reviewer-prompt-identity.js +10 -0
  44. package/dist/reviewer-scope-prompt.js +21 -0
  45. package/dist/reviewer-workspace.js +151 -0
  46. package/dist/sha256.js +5 -0
  47. package/dist/sitian-record-entry.js +33 -0
  48. package/dist/stderr-jsonl.js +26 -0
  49. package/dist/stream-idle-guard.js +75 -0
  50. package/dist/tool-execution-observation.js +141 -0
  51. package/dist/uuidv7.js +21 -0
  52. package/dist/work-subject-identity.js +53 -0
  53. package/extensions/role-runtime.ts +303 -0
  54. package/package.json +69 -0
  55. package/packets/fixer-prerequisites.json +6 -0
  56. package/packets/fixer-repair.md +5 -0
  57. package/packets/judge-apply.md +77 -0
  58. package/packets/judge-authority.md +64 -0
  59. package/packets/judge-plan.md +55 -0
  60. package/packets/judge-review.md +49 -0
  61. package/packets/judge-submission.md +34 -0
  62. package/resources/methods/code-review/SKILL.md +92 -0
  63. package/resources/methods/code-review/agents/openai.yaml +3 -0
  64. package/resources/methods/code-review/provenance.json +26 -0
  65. package/resources/methods/diagnosing-bugs/SKILL.md +134 -0
  66. package/resources/methods/diagnosing-bugs/agents/openai.yaml +3 -0
  67. package/resources/methods/diagnosing-bugs/provenance.json +31 -0
  68. package/resources/methods/diagnosing-bugs/scripts/hitl-loop.template.sh +41 -0
  69. package/resources/methods/resolving-merge-conflicts/SKILL.md +14 -0
  70. package/resources/methods/resolving-merge-conflicts/agents/openai.yaml +3 -0
  71. package/resources/methods/resolving-merge-conflicts/provenance.json +26 -0
  72. package/resources/methods/tdd/SKILL.md +38 -0
  73. package/resources/methods/tdd/agents/openai.yaml +3 -0
  74. package/resources/methods/tdd/mocking.md +59 -0
  75. package/resources/methods/tdd/provenance.json +36 -0
  76. package/resources/methods/tdd/tests.md +77 -0
  77. package/resources/navigator-route-playbook.md +32 -0
  78. package/schemas/tool-execution-observation.schema.json +107 -0
  79. package/scripts/build-package.mjs +65 -0
  80. package/scripts/generate-tool-execution-observation-schema.ts +7 -0
  81. package/souls/coder.md +10 -0
  82. package/souls/collector.md +11 -0
  83. package/souls/doctor-auditor.md +23 -0
  84. package/souls/doctor.md +8 -0
  85. package/souls/fixer-auditor.md +33 -0
  86. package/souls/fixer.md +13 -0
  87. package/souls/judge-auditor.md +33 -0
  88. package/souls/judge.md +74 -0
  89. package/souls/merger.md +5 -0
  90. package/souls/navigator.md +5 -0
  91. package/souls/reviewer-auditor.md +25 -0
  92. package/souls/reviewer.md +11 -0
  93. package/src/activation-ledger-git.ts +96 -0
  94. package/src/activation-ledger-session.ts +188 -0
  95. package/src/activation-ledger-topology.ts +301 -0
  96. package/src/activation-ledger.ts +240 -0
  97. package/src/activation-reconciliation.ts +163 -0
  98. package/src/activation-trace.ts +38 -0
  99. package/src/audit-escalation.ts +177 -0
  100. package/src/auditor-dossier-tool.ts +48 -0
  101. package/src/auditor-soul.ts +28 -0
  102. package/src/canonical-json.ts +74 -0
  103. package/src/canonical-skill-binding.ts +107 -0
  104. package/src/collector-config.ts +89 -0
  105. package/src/collector-evidence.ts +461 -0
  106. package/src/collector-github.ts +656 -0
  107. package/src/collector-identity.ts +161 -0
  108. package/src/collector-ledger.ts +827 -0
  109. package/src/collector-receipt.ts +87 -0
  110. package/src/collector-role.ts +592 -0
  111. package/src/collector-tool-schemas.ts +19 -0
  112. package/src/compliance-transport.ts +130 -0
  113. package/src/doctor-auditor.ts +53 -0
  114. package/src/doctor-contracts.ts +166 -0
  115. package/src/doctor-evidence.ts +47 -0
  116. package/src/doctor-role.ts +18 -0
  117. package/src/dossier-resolution.ts +137 -0
  118. package/src/evidence-child-executor.ts +775 -0
  119. package/src/exact-utf8.ts +9 -0
  120. package/src/factory-board.ts +1822 -0
  121. package/src/git-object-id.ts +11 -0
  122. package/src/human-format.ts +65 -0
  123. package/src/in-process-session.ts +78 -0
  124. package/src/judge-auditor.ts +55 -0
  125. package/src/judge-recording-anti-forge.ts +53 -0
  126. package/src/judge-role.ts +160 -0
  127. package/src/merger-contracts.ts +71 -0
  128. package/src/merger-git-state.ts +76 -0
  129. package/src/merger-role.ts +60 -0
  130. package/src/navigator-attendance.ts +1254 -0
  131. package/src/navigator-invocation-identity.ts +446 -0
  132. package/src/open-tool-schema.ts +46 -0
  133. package/src/package-contracts/collector-output.ts +109 -0
  134. package/src/package-contracts/fixer-output.ts +81 -0
  135. package/src/package-contracts/fixer-packet.ts +93 -0
  136. package/src/package-contracts/judge-output.ts +39 -0
  137. package/src/package-contracts/reviewer-output.ts +115 -0
  138. package/src/package-contracts/terminating-tools.ts +259 -0
  139. package/src/package-contracts/worker-output.ts +36 -0
  140. package/src/package-owned-tool-idle.ts +134 -0
  141. package/src/package-resources/method-skill-binding.ts +87 -0
  142. package/src/package-resources/method-skill.ts +358 -0
  143. package/src/packaged-role-registry.ts +36 -0
  144. package/src/public-cli/cli-errors.ts +11 -0
  145. package/src/public-cli/cli-io.ts +4 -0
  146. package/src/public-cli/cli.ts +912 -0
  147. package/src/public-cli/coder-run.ts +575 -0
  148. package/src/public-cli/collector-run.ts +375 -0
  149. package/src/public-cli/command-renderer.ts +8 -0
  150. package/src/public-cli/config.ts +346 -0
  151. package/src/public-cli/doctor-run.ts +355 -0
  152. package/src/public-cli/explicit-internal.ts +274 -0
  153. package/src/public-cli/fixer-run.ts +587 -0
  154. package/src/public-cli/host-pi-runtime.ts +112 -0
  155. package/src/public-cli/invocation.ts +1958 -0
  156. package/src/public-cli/judge-run.ts +507 -0
  157. package/src/public-cli/main.ts +15 -0
  158. package/src/public-cli/merger-run.ts +681 -0
  159. package/src/public-cli/public-run-credentials.ts +71 -0
  160. package/src/public-cli/registry.ts +153 -0
  161. package/src/public-cli/reviewer-run.ts +561 -0
  162. package/src/public-cli/run-lifecycle.ts +884 -0
  163. package/src/public-cli/settlement.ts +3765 -0
  164. package/src/public-cli/terminal.ts +325 -0
  165. package/src/public-command-renderer.ts +43 -0
  166. package/src/reviewer-agent.ts +94 -0
  167. package/src/reviewer-auditor.ts +53 -0
  168. package/src/reviewer-child-executor.ts +31 -0
  169. package/src/reviewer-construction.ts +137 -0
  170. package/src/reviewer-dispatch.ts +94 -0
  171. package/src/reviewer-execution-ledger.ts +206 -0
  172. package/src/reviewer-failure-diagnostic.ts +18 -0
  173. package/src/reviewer-git-snapshot.ts +53 -0
  174. package/src/reviewer-pinned-git.ts +144 -0
  175. package/src/reviewer-preflight-error.ts +14 -0
  176. package/src/reviewer-prompt-identity.ts +17 -0
  177. package/src/reviewer-role.ts +193 -0
  178. package/src/reviewer-scope-prompt.ts +24 -0
  179. package/src/reviewer-settlement.ts +63 -0
  180. package/src/reviewer-workspace.ts +111 -0
  181. package/src/role-runtime.ts +884 -0
  182. package/src/sha256.ts +6 -0
  183. package/src/sitian-record-entry.ts +57 -0
  184. package/src/stderr-jsonl.ts +28 -0
  185. package/src/stream-idle-guard.ts +98 -0
  186. package/src/ticket-snapshot.ts +662 -0
  187. package/src/ticket-trajectory.ts +1000 -0
  188. package/src/tool-execution-observation.ts +168 -0
  189. package/src/uuidv7.ts +1 -0
  190. package/src/work-subject-identity.ts +94 -0
  191. package/src/worker-role.ts +434 -0
  192. package/src/worker-submission-gates.ts +225 -0
@@ -0,0 +1,240 @@
1
+ import {
2
+ constants,
3
+ closeSync,
4
+ fstatSync,
5
+ openSync,
6
+ writeSync,
7
+ } from "node:fs";
8
+ import { dirname, isAbsolute, resolve } from "node:path";
9
+
10
+ import type { ActivationSessionPointer } from "./activation-ledger-session.ts";
11
+ import {
12
+ ActivationLedgerError,
13
+ activationWaitingLedgerPath,
14
+ assertLedgerFileInsideHome,
15
+ ensureRealDirectoryTree,
16
+ errorText,
17
+ } from "./activation-ledger-topology.ts";
18
+
19
+ /** Private open flags: O_NOFOLLOW closes the lstat→open TOCTOU window. */
20
+ const ACTIVATION_LEDGER_APPEND_OPEN_FLAGS =
21
+ constants.O_APPEND | constants.O_CREAT | constants.O_WRONLY | constants.O_NOFOLLOW;
22
+
23
+ export {
24
+ ActivationGitRepositoryRequiredError,
25
+ resolveBookKeyFromGit,
26
+ } from "./activation-ledger-git.ts";
27
+ export {
28
+ ActivationSessionFileMissingError,
29
+ durableSessionPointer,
30
+ type ActivationSessionManager,
31
+ type ActivationSessionPointer,
32
+ } from "./activation-ledger-session.ts";
33
+ export {
34
+ ActivationLedgerError,
35
+ activationBookDirectory,
36
+ activationWaitingLedgerPath,
37
+ resolveActivationLedgerHome,
38
+ } from "./activation-ledger-topology.ts";
39
+
40
+ /** Caller-preassigned correlation id, or an explicit absent identity (never empty string). */
41
+ export type ActivationCorrelationIdentity =
42
+ | { readonly kind: "caller"; readonly id: string }
43
+ | { readonly kind: "absent" };
44
+
45
+ export const ACCEPTED_ACTIVATION_EVENT = "accepted-activation" as const;
46
+
47
+ /**
48
+ * Package-owned index-only top-level keys for accepted-activation facts (ADR 0049).
49
+ * Sole machine key contract: projection and tests consume this descriptor — not prose markers.
50
+ */
51
+ export const ACCEPTED_ACTIVATION_FACT_KEYS = Object.freeze([
52
+ "event",
53
+ "role",
54
+ "observedAt",
55
+ "bookKey",
56
+ "session",
57
+ "correlation",
58
+ ] as const);
59
+
60
+ export type AcceptedActivationFactKey = (typeof ACCEPTED_ACTIVATION_FACT_KEYS)[number];
61
+
62
+ /**
63
+ * Closed activation fact: index fields only (ADR 0049).
64
+ * No prompt, transcript, argv, excerpt, or other content.
65
+ */
66
+ export type AcceptedActivationFact = {
67
+ readonly event: typeof ACCEPTED_ACTIVATION_EVENT;
68
+ readonly role: string;
69
+ readonly observedAt: string;
70
+ readonly bookKey: string;
71
+ readonly session: ActivationSessionPointer;
72
+ readonly correlation: ActivationCorrelationIdentity;
73
+ };
74
+
75
+ // Keys tuple ↔ fact type must stay exact (compile fail on drift; no second field list).
76
+ type ExactKeyMatch<T, K extends PropertyKey> =
77
+ Exclude<keyof T, K> | Exclude<K, keyof T> extends never ? true : never;
78
+ const _acceptedActivationFactKeysMatch: ExactKeyMatch<
79
+ AcceptedActivationFact,
80
+ AcceptedActivationFactKey
81
+ > = true;
82
+ void _acceptedActivationFactKeysMatch;
83
+
84
+ /** Trusted typed inputs for building the closed fact (canonical fact minus event discriminant). */
85
+ export type AcceptedActivationFactInput = Omit<AcceptedActivationFact, "event">;
86
+
87
+ /**
88
+ * Host correlation channel (not a CLI flag): a non-blank AK_CORRELATION_ID carries
89
+ * the caller id verbatim; missing/blank/whitespace-only yields the typed absent identity.
90
+ */
91
+ export function correlationIdentityFromEnv(
92
+ env: NodeJS.ProcessEnv = process.env,
93
+ ): ActivationCorrelationIdentity {
94
+ const raw = env.AK_CORRELATION_ID;
95
+ if (typeof raw === "string" && raw.trim().length > 0) {
96
+ return { kind: "caller", id: raw };
97
+ }
98
+ return { kind: "absent" };
99
+ }
100
+
101
+ /**
102
+ * Descriptor-driven top-level pick: only ACCEPTED_ACTIVATION_FACT_KEYS leave this boundary.
103
+ * Nested session/correlation are rebuilt closed before the pick (ADR 0049 zero-content by construction).
104
+ */
105
+ function projectAcceptedActivationFact(
106
+ input: AcceptedActivationFactInput,
107
+ ): AcceptedActivationFact {
108
+ const closed: AcceptedActivationFact = {
109
+ event: ACCEPTED_ACTIVATION_EVENT,
110
+ role: input.role,
111
+ observedAt: input.observedAt,
112
+ bookKey: input.bookKey,
113
+ session: { kind: "session-file", path: input.session.path },
114
+ correlation: input.correlation.kind === "caller"
115
+ ? { kind: "caller", id: input.correlation.id }
116
+ : { kind: "absent" },
117
+ };
118
+ // Descriptor is the sole top-level emission contract (typed pick; no content keys).
119
+ return Object.fromEntries(
120
+ ACCEPTED_ACTIVATION_FACT_KEYS.map((key) => [key, closed[key]]),
121
+ ) as AcceptedActivationFact;
122
+ }
123
+
124
+ /** Construct the closed fact from trusted typed inputs only (descriptor projection — no content keys). */
125
+ export function buildAcceptedActivationFact(input: AcceptedActivationFactInput): AcceptedActivationFact {
126
+ return projectAcceptedActivationFact(input);
127
+ }
128
+
129
+ /** Serialize only the closed index fields (descriptor projection — no content keys). */
130
+ export function serializeAcceptedActivationFact(fact: AcceptedActivationFact): string {
131
+ return `${JSON.stringify(projectAcceptedActivationFact(fact))}\n`;
132
+ }
133
+
134
+ /**
135
+ * Bare O_APPEND write of one complete line under an absolute ledger home.
136
+ * Private helper: production-bound to writeSync; owns open/write/close honesty only.
137
+ * Close failure cannot mask open/write; simultaneous close is nested evidence.
138
+ */
139
+ function appendActivationLedgerLine(
140
+ ledgerPath: string,
141
+ line: Uint8Array,
142
+ options: { ledgerHome: string },
143
+ ): void {
144
+ if (!isAbsolute(options.ledgerHome)) {
145
+ throw new ActivationLedgerError(
146
+ `activation ledger home must be absolute: ${options.ledgerHome}`,
147
+ );
148
+ }
149
+ const resolvedLedger = resolve(ledgerPath);
150
+ const resolvedHome = resolve(options.ledgerHome);
151
+ const parent = dirname(resolvedLedger);
152
+ ensureRealDirectoryTree(resolvedHome, parent);
153
+ assertLedgerFileInsideHome(resolvedLedger, resolvedHome);
154
+
155
+ const bytes = Buffer.isBuffer(line) ? line : Buffer.from(line);
156
+ let ledgerFd: number | undefined;
157
+ let primaryFailure: unknown;
158
+ try {
159
+ try {
160
+ ledgerFd = openSync(resolvedLedger, ACTIVATION_LEDGER_APPEND_OPEN_FLAGS, 0o644);
161
+ } catch (error) {
162
+ // Native ELOOP/filesystem cause retained; no errno dispatch.
163
+ throw new ActivationLedgerError(
164
+ `activation ledger failed to open ledger file (${resolvedLedger}): ${errorText(error)}`,
165
+ { cause: error },
166
+ );
167
+ }
168
+
169
+ let opened: ReturnType<typeof fstatSync>;
170
+ try {
171
+ opened = fstatSync(ledgerFd);
172
+ } catch (error) {
173
+ throw new ActivationLedgerError(
174
+ `activation ledger failed to fstat ledger file (${resolvedLedger}): ${errorText(error)}`,
175
+ { cause: error },
176
+ );
177
+ }
178
+ if (!opened.isFile()) {
179
+ throw new ActivationLedgerError(
180
+ `activation ledger is not a regular file: ${resolvedLedger}`,
181
+ );
182
+ }
183
+
184
+ const written = writeSync(ledgerFd, bytes, 0, bytes.length, null);
185
+ if (written !== bytes.length) {
186
+ throw new ActivationLedgerError(
187
+ `activation ledger short write: wrote ${written} of ${bytes.length} bytes to ${resolvedLedger}`,
188
+ );
189
+ }
190
+ } catch (error) {
191
+ primaryFailure = error;
192
+ }
193
+
194
+ if (ledgerFd !== undefined) {
195
+ try {
196
+ closeSync(ledgerFd);
197
+ } catch (closeFailure) {
198
+ if (primaryFailure !== undefined) {
199
+ throw new AggregateError(
200
+ [primaryFailure, closeFailure],
201
+ "activation ledger operation and close failed",
202
+ { cause: primaryFailure },
203
+ );
204
+ }
205
+ throw closeFailure;
206
+ }
207
+ }
208
+
209
+ if (primaryFailure !== undefined) throw primaryFailure;
210
+ }
211
+
212
+ /**
213
+ * Append one complete JSONL record with one O_APPEND write of the full record.
214
+ * Shared-ledger contract: concurrent successful append-only producers cannot
215
+ * overwrite one another. A short write is an honest infrastructure failure
216
+ * (ADR 0049) — no non-append rollback/truncate. Close failure cannot mask the
217
+ * primary write cause; simultaneous close evidence is retained.
218
+ */
219
+ export function appendAcceptedActivationFact(
220
+ ledgerPath: string,
221
+ fact: AcceptedActivationFact,
222
+ options: { ledgerHome: string },
223
+ ): void {
224
+ appendActivationLedgerLine(
225
+ ledgerPath,
226
+ Buffer.from(serializeAcceptedActivationFact(fact), "utf8"),
227
+ { ledgerHome: options.ledgerHome },
228
+ );
229
+ }
230
+
231
+ export function appendAcceptedActivationToBook(options: {
232
+ ledgerHome: string;
233
+ fact: AcceptedActivationFact;
234
+ }): void {
235
+ appendAcceptedActivationFact(
236
+ activationWaitingLedgerPath(options.ledgerHome, options.fact.bookKey),
237
+ options.fact,
238
+ { ledgerHome: options.ledgerHome },
239
+ );
240
+ }
@@ -0,0 +1,163 @@
1
+ import type { AcceptedActivationFact } from "./activation-ledger.ts";
2
+
3
+ /** Event category for pre-dispatch stub facts (#11 producer contract). */
4
+ export const DISPATCH_STUB_EVENT = "dispatch-stub" as const;
5
+
6
+ /**
7
+ * Non-content dispatch pointer. Carries process identity or an opaque ref —
8
+ * never prompt/argv/content bytes (ADR 0049).
9
+ */
10
+ export type DispatchPointer =
11
+ | { readonly kind: "process"; readonly pid: number }
12
+ | { readonly kind: "opaque"; readonly ref: string };
13
+
14
+ /**
15
+ * Minimum typed dispatch stub the #11 producer supplies before ignition.
16
+ * Index-only: correlation, book, time, event category, non-content pointer.
17
+ */
18
+ export type DispatchStubFact = {
19
+ readonly event: typeof DISPATCH_STUB_EVENT;
20
+ readonly observedAt: string;
21
+ readonly bookKey: string;
22
+ readonly dispatch: DispatchPointer;
23
+ readonly correlation: { readonly kind: "caller"; readonly id: string };
24
+ };
25
+
26
+ /** Trusted typed inputs for building the closed dispatch stub (fact minus event discriminant). */
27
+ export type DispatchStubFactInput = Omit<DispatchStubFact, "event">;
28
+
29
+ /**
30
+ * Construct the closed dispatch stub from trusted typed inputs only.
31
+ * Nested dispatch/correlation are rebuilt closed (ADR 0049 zero-content by construction).
32
+ */
33
+ export function buildDispatchStubFact(input: DispatchStubFactInput): DispatchStubFact {
34
+ return {
35
+ event: DISPATCH_STUB_EVENT,
36
+ observedAt: input.observedAt,
37
+ bookKey: input.bookKey,
38
+ dispatch: input.dispatch.kind === "process"
39
+ ? { kind: "process", pid: input.dispatch.pid }
40
+ : { kind: "opaque", ref: input.dispatch.ref },
41
+ correlation: { kind: "caller", id: input.correlation.id },
42
+ };
43
+ }
44
+
45
+ /**
46
+ * Truthful process liveness supplied by the consumer.
47
+ * Reconciliation never probes, kills, or retries processes (D1a / #78).
48
+ */
49
+ export type ProcessLivenessFact =
50
+ | { readonly state: "alive" }
51
+ | { readonly state: "terminated" };
52
+
53
+ /**
54
+ * One reconciliation subject as an exclusive complete union (four outcomes only):
55
+ * - activation present → activation required; dispatch/process optional
56
+ * - activation absent → dispatch + truthful process required; activation prohibited
57
+ * No session-file existence oracle (ADR 0047). Untyped JS still hits runtime TypeError.
58
+ */
59
+ export type ReconciliationSubject =
60
+ | {
61
+ readonly activation: AcceptedActivationFact;
62
+ readonly dispatch?: DispatchStubFact;
63
+ readonly process?: ProcessLivenessFact;
64
+ }
65
+ | {
66
+ readonly activation?: undefined;
67
+ readonly dispatch: DispatchStubFact;
68
+ readonly process: ProcessLivenessFact;
69
+ };
70
+
71
+ /**
72
+ * Exactly four typed outcomes (D1a):
73
+ * - matched — dispatch stub and matching accepted-activation fact
74
+ * - pending — dispatch present, no activation, process still alive
75
+ * - ghost — dispatch present, no activation, process terminated
76
+ * - activation-without-dispatch — activation fact with no matching stub
77
+ */
78
+ export type ReconciliationOutcome =
79
+ | {
80
+ readonly kind: "matched";
81
+ readonly correlationId: string;
82
+ readonly bookKey: string;
83
+ }
84
+ | {
85
+ readonly kind: "pending";
86
+ readonly correlationId: string;
87
+ readonly bookKey: string;
88
+ }
89
+ | {
90
+ readonly kind: "ghost";
91
+ readonly correlationId: string;
92
+ readonly bookKey: string;
93
+ }
94
+ | {
95
+ readonly kind: "activation-without-dispatch";
96
+ readonly correlationId: string | undefined;
97
+ readonly bookKey: string;
98
+ };
99
+
100
+ function callerCorrelationId(
101
+ correlation: AcceptedActivationFact["correlation"] | DispatchStubFact["correlation"],
102
+ ): string | undefined {
103
+ return correlation.kind === "caller" ? correlation.id : undefined;
104
+ }
105
+
106
+ function dispatchMatchesActivation(
107
+ dispatch: DispatchStubFact,
108
+ activation: AcceptedActivationFact,
109
+ ): boolean {
110
+ const activationId = callerCorrelationId(activation.correlation);
111
+ return (
112
+ activationId !== undefined
113
+ && activationId === dispatch.correlation.id
114
+ && activation.bookKey === dispatch.bookKey
115
+ );
116
+ }
117
+
118
+ /**
119
+ * Reconcile one invocation's typed dispatch/activation facts against truthful
120
+ * process liveness. Pure: never kills, never retries, never reads session files.
121
+ */
122
+ export function reconcileInvocation(subject: ReconciliationSubject): ReconciliationOutcome {
123
+ const { dispatch, activation, process } = subject;
124
+
125
+ if (activation !== undefined) {
126
+ if (dispatch !== undefined && dispatchMatchesActivation(dispatch, activation)) {
127
+ return {
128
+ kind: "matched",
129
+ correlationId: dispatch.correlation.id,
130
+ bookKey: dispatch.bookKey,
131
+ };
132
+ }
133
+ return {
134
+ kind: "activation-without-dispatch",
135
+ correlationId: callerCorrelationId(activation.correlation),
136
+ bookKey: activation.bookKey,
137
+ };
138
+ }
139
+
140
+ if (dispatch !== undefined) {
141
+ if (process === undefined) {
142
+ throw new TypeError(
143
+ "reconcileInvocation requires process liveness when activation is absent",
144
+ );
145
+ }
146
+ if (process.state === "alive") {
147
+ return {
148
+ kind: "pending",
149
+ correlationId: dispatch.correlation.id,
150
+ bookKey: dispatch.bookKey,
151
+ };
152
+ }
153
+ return {
154
+ kind: "ghost",
155
+ correlationId: dispatch.correlation.id,
156
+ bookKey: dispatch.bookKey,
157
+ };
158
+ }
159
+
160
+ throw new TypeError(
161
+ "reconcileInvocation requires a dispatch stub and/or an accepted-activation fact",
162
+ );
163
+ }
@@ -0,0 +1,38 @@
1
+ import { Type, type Static } from "typebox";
2
+
3
+ const causeSchema = Type.Object({
4
+ identity: Type.String({ minLength: 1 }),
5
+ name: Type.String({ minLength: 1 }),
6
+ message: Type.String(),
7
+ evidenceId: Type.Optional(Type.String({ minLength: 1 })),
8
+ }, { additionalProperties: false });
9
+
10
+ export const activationTraceRecordSchema = Type.Object({
11
+ role: Type.String({ minLength: 1 }),
12
+ stageId: Type.String({ pattern: "^[a-z][a-z0-9-]*$" }),
13
+ status: Type.Literal("failed"),
14
+ timestamp: Type.String({ format: "date-time" }),
15
+ cause: causeSchema,
16
+ }, { additionalProperties: false });
17
+
18
+ export type ActivationTraceRecord = Static<typeof activationTraceRecordSchema>;
19
+ export type ActivationTraceWriter = (record: ActivationTraceRecord) => void;
20
+
21
+ let activationCauseEvidence = 0;
22
+ const retainedActivationCauses = new Map<string, unknown>();
23
+ export function namedActivationCause(error: unknown): { identity: string; name: string; message: string; evidenceId: string } {
24
+ const evidenceId = `activation-cause-${++activationCauseEvidence}`;
25
+ retainedActivationCauses.set(evidenceId, error);
26
+ if (error instanceof Error) {
27
+ const code = (error as Error & { code?: unknown }).code;
28
+ const name = error.name || "Error";
29
+ return { identity: typeof code === "string" && code.length > 0 ? code : name, name, message: error.message, evidenceId };
30
+ }
31
+ let message: string;
32
+ try {
33
+ message = typeof error === "string" ? error : JSON.stringify(error) ?? String(error);
34
+ return { identity: "UnknownThrownCause", name: "UnknownThrownCause", message, evidenceId };
35
+ } catch {
36
+ return { identity: "UnknownThrownCause", name: "UnknownThrownCause", message: String(error), evidenceId };
37
+ }
38
+ }
@@ -0,0 +1,177 @@
1
+ import type { Usage } from "@earendil-works/pi-ai";
2
+
3
+ import type {
4
+ ComplianceAuditIncomplete,
5
+ ComplianceDecision,
6
+ } from "./compliance-transport.ts";
7
+
8
+ export const AUDIT_ESCALATION_KIND = "audit_escalation" as const;
9
+
10
+ // Live Navigator settlement may consume only the projection produced by this
11
+ // owner. Its private WeakSet cannot be authored by role output; persisted/
12
+ // replayed records are re-authenticated by the retained audit evidence binder.
13
+ const AUDIT_ESCALATION_LIVE_REGISTRY = new WeakSet<object>();
14
+
15
+ /**
16
+ * Escalation delivery face.
17
+ * `kind` / `conflicts` / `auditDecisionGate` are audit-owned fields.
18
+ * Role-delivered fields ride beside them as open content (ADR 0055) — including
19
+ * a role `decisionGate` when present. The index signature tells that truth so
20
+ * callers never need a cast to retain role output.
21
+ */
22
+ export type AuditEscalationResult = {
23
+ readonly kind: typeof AUDIT_ESCALATION_KIND;
24
+ /** Raw audit-owned conflicts field, when the auditor supplied one. */
25
+ readonly conflicts?: unknown;
26
+ /** Raw audit-owned gate field, when the auditor supplied one. */
27
+ readonly auditDecisionGate?: unknown;
28
+ readonly [key: string]: unknown;
29
+ };
30
+
31
+ export type AuditEscalationToolResult = {
32
+ content: [{ type: "text"; text: string }];
33
+ details: AuditEscalationResult;
34
+ terminate: true;
35
+ usage?: Usage;
36
+ };
37
+
38
+ export type AuditIncompleteToolResult = {
39
+ content: [{ type: "text"; text: string }];
40
+ details: ComplianceAuditIncomplete;
41
+ terminate: true;
42
+ usage?: Usage;
43
+ };
44
+
45
+ /**
46
+ * Build the escalation delivery face.
47
+ * Role-delivered fields ride under the escalation discriminator (ADR 0055).
48
+ * `kind` always wins so the discriminator cannot be laundered.
49
+ * `conflicts` and `auditDecisionGate`, when present, always come from the
50
+ * audit (why we escalated and its gate). Raw ancillary values are not repaired.
51
+ * A role `decisionGate`, when present, stays at its own key via spread and is
52
+ * never overwritten (not folded, not dropped, not swapped into the audit home).
53
+ */
54
+ export function buildAuditEscalationResult(
55
+ decision: Extract<ComplianceDecision, { status: "escalate" }>,
56
+ deliveredOutput?: unknown,
57
+ ): AuditEscalationResult {
58
+ const auditOwned: Record<string, unknown> = {
59
+ kind: AUDIT_ESCALATION_KIND,
60
+ };
61
+ if (Object.hasOwn(decision, "conflicts")) {
62
+ auditOwned.conflicts = decision.conflicts;
63
+ }
64
+ if (Object.hasOwn(decision, "decisionGate")) {
65
+ auditOwned.auditDecisionGate = decision.decisionGate;
66
+ }
67
+ const deliveredFields =
68
+ deliveredOutput !== undefined &&
69
+ deliveredOutput !== null &&
70
+ typeof deliveredOutput === "object" &&
71
+ !Array.isArray(deliveredOutput)
72
+ ? { ...(deliveredOutput as Record<string, unknown>) }
73
+ : {};
74
+ // Role output cannot fill an absent audit-owned field.
75
+ delete deliveredFields.conflicts;
76
+ delete deliveredFields.auditDecisionGate;
77
+ const result = {
78
+ ...deliveredFields,
79
+ ...auditOwned,
80
+ } as AuditEscalationResult;
81
+ AUDIT_ESCALATION_LIVE_REGISTRY.add(result);
82
+ return result;
83
+ }
84
+
85
+ /** True only for the audit-owned live projection, never for role-shaped data. */
86
+ export function isAuditEscalationProjection(
87
+ value: unknown,
88
+ ): value is AuditEscalationResult {
89
+ if (!isAuditEscalationResult(value)) return false;
90
+ return AUDIT_ESCALATION_LIVE_REGISTRY.has(value);
91
+ }
92
+
93
+ function humanDecisionText(result: AuditEscalationResult): string {
94
+ const lines = ["Human decision required: compliance audit escalation."];
95
+ if (Array.isArray(result.conflicts)) {
96
+ lines.push("Conflicts:", ...result.conflicts.map((conflict) => `- ${conflict}`));
97
+ }
98
+ const gate = result.auditDecisionGate;
99
+ if (gate !== null && typeof gate === "object" && !Array.isArray(gate)) {
100
+ const record = gate as Record<string, unknown>;
101
+ if (typeof record.question === "string") lines.push(`Question: ${record.question}`);
102
+ if (Array.isArray(record.options)) {
103
+ lines.push("Options:", ...record.options.map((option) => `- ${option}`));
104
+ }
105
+ }
106
+ return lines.join("\n");
107
+ }
108
+
109
+ export function projectAuditEscalation(
110
+ decision: Extract<ComplianceDecision, { status: "escalate" }>,
111
+ deliveredOutput?: unknown,
112
+ ): AuditEscalationToolResult {
113
+ const details = buildAuditEscalationResult(decision, deliveredOutput);
114
+ return {
115
+ content: [{ type: "text", text: humanDecisionText(details) }],
116
+ details,
117
+ terminate: true,
118
+ ...(decision.usage === undefined ? {} : { usage: decision.usage }),
119
+ };
120
+ }
121
+
122
+ export function projectAuditIncomplete(
123
+ decision: ComplianceAuditIncomplete,
124
+ ): AuditIncompleteToolResult {
125
+ return {
126
+ content: [{ type: "text", text: "Compliance audit incomplete; no role receipt was formed." }],
127
+ details: decision,
128
+ terminate: true,
129
+ ...(decision.usage === undefined ? {} : { usage: decision.usage }),
130
+ };
131
+ }
132
+
133
+ /**
134
+ * Discriminator-only recognition (ADR 0040). Shape of conflicts/options/gate
135
+ * is not a reject gate — element types and cardinality are delivery content.
136
+ */
137
+ export function isAuditEscalationResult(
138
+ value: unknown,
139
+ ): value is AuditEscalationResult {
140
+ if (typeof value !== "object" || value === null || Array.isArray(value)) {
141
+ return false;
142
+ }
143
+ return (value as Record<string, unknown>).kind === AUDIT_ESCALATION_KIND;
144
+ }
145
+
146
+ export type ComplianceDecisionHandlers<T> = {
147
+ pass: (usage: Usage | undefined) => T | PromiseLike<T>;
148
+ revise: (violations: readonly unknown[]) => T | PromiseLike<T>;
149
+ escalate: (result: AuditEscalationToolResult) => T | PromiseLike<T>;
150
+ auditIncomplete?: (result: AuditIncompleteToolResult) => T | PromiseLike<T>;
151
+ };
152
+
153
+ /**
154
+ * Dispose a parsed audit decision without repeating status handling in roles.
155
+ * Role output already delivered is preserved on the escalate face (ADR 0055).
156
+ */
157
+ export async function disposeComplianceDecision<T>(
158
+ decision: ComplianceDecision,
159
+ handlers: ComplianceDecisionHandlers<T>,
160
+ deliveredOutput?: unknown,
161
+ ): Promise<Awaited<T>> {
162
+ switch (decision.status) {
163
+ case "pass":
164
+ return await handlers.pass(decision.usage);
165
+ case "revise":
166
+ return await handlers.revise(decision.violations);
167
+ case "escalate":
168
+ return await handlers.escalate(
169
+ projectAuditEscalation(decision, deliveredOutput),
170
+ );
171
+ case "audit-incomplete":
172
+ if (handlers.auditIncomplete === undefined) {
173
+ throw new Error("Compliance audit-incomplete handler is unavailable");
174
+ }
175
+ return await handlers.auditIncomplete(projectAuditIncomplete(decision));
176
+ }
177
+ }
@@ -0,0 +1,48 @@
1
+ import { dirname, join, resolve } from "node:path";
2
+
3
+ import type { AgentToolResult, ExtensionContext } from "@earendil-works/pi-coding-agent";
4
+ import { Type } from "typebox";
5
+
6
+ export const AUDITOR_DOSSIER_TOOL_NAME = "ak_get_run_dossier" as const;
7
+
8
+ export type AuditorDossierLocation = {
9
+ readonly runDirectory: string;
10
+ readonly admittedRequest: string;
11
+ readonly parentSessionCandidate: string;
12
+ readonly attachments: string;
13
+ readonly artifacts: string;
14
+ };
15
+
16
+ /** Resolve the exact run binding already carried by the parent record session. */
17
+ export function auditorRunDirectory(context: ExtensionContext): string | undefined {
18
+ const sessionFile = context.sessionManager?.getSessionFile?.();
19
+ return sessionFile === undefined ? undefined : resolve(dirname(dirname(sessionFile)));
20
+ }
21
+
22
+ /** The one shared, run-bound dossier locator exposed to every auditor seat. */
23
+ export function createAuditorDossierTool(runDirectory: string | undefined) {
24
+ return {
25
+ name: AUDITOR_DOSSIER_TOOL_NAME,
26
+ description: "Locate this auditor's bound run dossier and its evidence entry points.",
27
+ parameters: Type.Object({}, { additionalProperties: false }),
28
+ async execute(_id: string, _params: unknown): Promise<AgentToolResult<AuditorDossierLocation | undefined>> {
29
+ if (runDirectory === undefined) {
30
+ return {
31
+ content: [{ type: "text", text: "This auditor has no run-bound dossier record." }],
32
+ details: undefined,
33
+ };
34
+ }
35
+ const details: AuditorDossierLocation = {
36
+ runDirectory,
37
+ admittedRequest: join(runDirectory, "admitted-request.json"),
38
+ parentSessionCandidate: join(runDirectory, "session", "session.jsonl"),
39
+ attachments: join(runDirectory, "attachments"),
40
+ artifacts: join(runDirectory, "artifacts"),
41
+ };
42
+ return {
43
+ content: [{ type: "text", text: JSON.stringify(details) }],
44
+ details,
45
+ };
46
+ },
47
+ };
48
+ }
@@ -0,0 +1,28 @@
1
+ import { readFile } from "node:fs/promises";
2
+ import { fileURLToPath } from "node:url";
3
+
4
+ /** Active auditor seats. Fixer LLM auditor retired by #242; souls/fixer-auditor.md retained on disk for possible re-enable. */
5
+ export const AUDITOR_SOUL_ROLES = [
6
+ "judge",
7
+ "reviewer",
8
+ "doctor",
9
+ ] as const;
10
+
11
+ export type AuditorSoulRole = (typeof AUDITOR_SOUL_ROLES)[number];
12
+
13
+ const auditorSoulPaths: Readonly<Record<AuditorSoulRole, string>> = Object.freeze({
14
+ judge: fileURLToPath(new URL("../souls/judge-auditor.md", import.meta.url)),
15
+ reviewer: fileURLToPath(
16
+ new URL("../souls/reviewer-auditor.md", import.meta.url),
17
+ ),
18
+ doctor: fileURLToPath(new URL("../souls/doctor-auditor.md", import.meta.url)),
19
+ });
20
+
21
+ /** Load one complete auditor Soul afresh for each audit invocation. */
22
+ export async function loadAuditorSoul(role: AuditorSoulRole): Promise<string> {
23
+ const soul = await readFile(auditorSoulPaths[role], "utf8");
24
+ if (soul.trim().length === 0) {
25
+ throw new Error(`The ${role} auditor Soul is blank`);
26
+ }
27
+ return soul;
28
+ }