@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,446 @@
1
+ /**
2
+ * Exact current-invocation principal for Navigator attendance correlation.
3
+ * Shared role lifecycle owns one opaque uuidv7 per Role invocation and persists
4
+ * it on the role session via Pi's guaranteed `pi.appendEntry` boundary.
5
+ *
6
+ * session_start is process/session activation, not a new Role invocation:
7
+ * unfinished exact-session resume reuses the latest valid marker only when its
8
+ * role/phase/subjectKey still match the expected identity; a packaged role
9
+ * terminal completing that marker starts the next invocation (fresh mint).
10
+ * Terminal settlement binds the nearest independent marker strictly before the
11
+ * current durable packaged role terminal and compares equality — never attendance
12
+ * self-shape, markers after the terminal, or stale older markers behind a
13
+ * malformed nearest. Non-UUIDv7 principals are never accepted.
14
+ *
15
+ * One shared typed terminal classifier owns durable completion for lifecycle,
16
+ * publicNavigatorSettlement, and every public CLI Receipt extractor:
17
+ * - accepted/human: isError exactly false and no infrastructure-failure fact
18
+ * - infrastructure: isError exactly true plus exact closed infrastructure fact
19
+ * - retryable/missing/nonboolean/contradictory/malformed: nonterminal
20
+ *
21
+ * One truth table also owns marker↔terminal cardinality:
22
+ * - a marker binds exactly one durable packaged role terminal
23
+ * - multiple durable terminals after the same marker → ambiguous / fail-closed
24
+ * - marker role/phase/subjectKey must match the terminal + independent expected
25
+ */
26
+
27
+ import { PACKAGED_ROLE_REGISTRY } from "./packaged-role-registry.ts";
28
+ import { isUuidV7, uuidv7 } from "./uuidv7.ts";
29
+ import { workSubjectKeysEqual } from "./work-subject-identity.ts";
30
+
31
+ export const NAVIGATOR_INVOCATION_ENTRY = "ak-navigator-invocation" as const;
32
+
33
+ /** Typed durable infrastructure-failure fact on a packaged role output toolResult. */
34
+ export const NAVIGATOR_INFRASTRUCTURE_FAILURE_KIND = "role_infrastructure_failure" as const;
35
+
36
+ /** Closed fact keys — extras/missing/wrong keys fail closed. */
37
+ const NAVIGATOR_INFRASTRUCTURE_FAILURE_KEYS = [
38
+ "kind",
39
+ "source",
40
+ "reasonCode",
41
+ ] as const;
42
+
43
+ export type NavigatorInfrastructureFailureFact = {
44
+ kind: typeof NAVIGATOR_INFRASTRUCTURE_FAILURE_KIND;
45
+ source: "shared-role-lifecycle";
46
+ reasonCode: "host_failure";
47
+ };
48
+
49
+ export function buildNavigatorInfrastructureFailureFact(): NavigatorInfrastructureFailureFact {
50
+ return {
51
+ kind: NAVIGATOR_INFRASTRUCTURE_FAILURE_KIND,
52
+ source: "shared-role-lifecycle",
53
+ reasonCode: "host_failure",
54
+ };
55
+ }
56
+
57
+ /**
58
+ * Exact closed infrastructure-failure fact.
59
+ * Rejects extras, missing keys, wrong values/types, and non-objects.
60
+ */
61
+ export function isNavigatorInfrastructureFailureFact(
62
+ value: unknown,
63
+ ): value is NavigatorInfrastructureFailureFact {
64
+ if (typeof value !== "object" || value === null || Array.isArray(value)) return false;
65
+ const record = value as Record<string, unknown>;
66
+ const keys = Object.keys(record);
67
+ if (keys.length !== NAVIGATOR_INFRASTRUCTURE_FAILURE_KEYS.length) return false;
68
+ for (const key of NAVIGATOR_INFRASTRUCTURE_FAILURE_KEYS) {
69
+ if (!Object.hasOwn(record, key)) return false;
70
+ }
71
+ return (
72
+ record.kind === NAVIGATOR_INFRASTRUCTURE_FAILURE_KIND &&
73
+ record.source === "shared-role-lifecycle" &&
74
+ record.reasonCode === "host_failure"
75
+ );
76
+ }
77
+
78
+ const PACKAGED_ROLE_OUTPUT_TOOLS: ReadonlyMap<string, string> = new Map(
79
+ PACKAGED_ROLE_REGISTRY.map((entry) => [entry.outputTool, entry.role]),
80
+ );
81
+
82
+ export type NavigatorInvocationEntryLike = {
83
+ readonly type?: string;
84
+ readonly customType?: string;
85
+ readonly data?: unknown;
86
+ readonly message?: {
87
+ readonly role?: unknown;
88
+ readonly toolName?: unknown;
89
+ readonly isError?: unknown;
90
+ readonly details?: unknown;
91
+ };
92
+ };
93
+
94
+ /** Mint one globally unique opaque invocation principal. */
95
+ export function mintNavigatorInvocationId(): string {
96
+ return uuidv7();
97
+ }
98
+
99
+ /** Phase carried on the durable invocation marker / expected identity. */
100
+ export type InvocationPhase = "plan" | "apply" | null;
101
+
102
+ /** Full exact-invocation marker identity persisted on the role session. */
103
+ export type InvocationMarkerIdentity = {
104
+ readonly invocationId: string;
105
+ readonly role: string;
106
+ readonly phase: InvocationPhase;
107
+ readonly subjectKey: string;
108
+ };
109
+
110
+ /** Independently admitted / registry / cwd expected identity for resume and correlation. */
111
+ export type ExpectedInvocationIdentity = {
112
+ readonly role: string;
113
+ readonly phase?: InvocationPhase;
114
+ readonly allowedPhases?: readonly InvocationPhase[];
115
+ readonly subjectKey?: string;
116
+ };
117
+
118
+ function invocationPhaseFromUnknown(value: unknown): InvocationPhase | undefined {
119
+ if (value === null || value === "plan" || value === "apply") return value;
120
+ return undefined;
121
+ }
122
+
123
+ /**
124
+ * Parse a full invocation marker payload. All identity fields are required;
125
+ * non-UUIDv7 principals and partial shapes fail closed.
126
+ */
127
+ export function parseInvocationMarkerIdentity(
128
+ data: unknown,
129
+ ): InvocationMarkerIdentity | undefined {
130
+ if (data === null || typeof data !== "object" || Array.isArray(data)) return undefined;
131
+ const record = data as Record<string, unknown>;
132
+ const invocationId = record.invocationId;
133
+ if (typeof invocationId !== "string") return undefined;
134
+ const trimmedId = invocationId.trim();
135
+ if (!isUuidV7(trimmedId)) return undefined;
136
+ if (typeof record.role !== "string" || record.role.trim() === "") return undefined;
137
+ const phase = invocationPhaseFromUnknown(record.phase);
138
+ if (phase === undefined) return undefined;
139
+ if (typeof record.subjectKey !== "string" || record.subjectKey.trim() === "") return undefined;
140
+ return {
141
+ invocationId: trimmedId,
142
+ role: record.role,
143
+ phase,
144
+ subjectKey: record.subjectKey,
145
+ };
146
+ }
147
+
148
+ /**
149
+ * Marker identity agrees with independently expected role/phase/subject.
150
+ * Role is mandatory. Phase and subject apply when the expected side knows them.
151
+ */
152
+ export function markerMatchesExpectedIdentity(
153
+ marker: InvocationMarkerIdentity,
154
+ expected: ExpectedInvocationIdentity,
155
+ ): boolean {
156
+ if (marker.role !== expected.role) return false;
157
+ if (expected.phase !== undefined) {
158
+ if (marker.phase !== expected.phase) return false;
159
+ } else if (expected.allowedPhases !== undefined) {
160
+ if (!expected.allowedPhases.includes(marker.phase)) return false;
161
+ }
162
+ if (expected.subjectKey !== undefined) {
163
+ if (!workSubjectKeysEqual(marker.subjectKey, expected.subjectKey)) return false;
164
+ }
165
+ return true;
166
+ }
167
+
168
+ /** Shared terminal discriminant owned by one classifier. */
169
+ export type PackagedRoleTerminalClassification =
170
+ | { readonly kind: "accepted" }
171
+ | {
172
+ readonly kind: "infrastructure";
173
+ readonly fact: NavigatorInfrastructureFailureFact;
174
+ }
175
+ | { readonly kind: "nonterminal" };
176
+
177
+ export type PackagedRoleTerminalMessage = {
178
+ readonly toolName?: unknown;
179
+ readonly isError?: unknown;
180
+ readonly details?: unknown;
181
+ };
182
+
183
+ /**
184
+ * One shared typed terminal classifier for packaged role output toolResults.
185
+ * Consumed by lifecycle principal completion, publicNavigatorSettlement, and
186
+ * every public CLI role Receipt extractor. No second classifier.
187
+ */
188
+ export function classifyPackagedRoleTerminalResult(
189
+ message: PackagedRoleTerminalMessage,
190
+ ): PackagedRoleTerminalClassification {
191
+ if (typeof message.toolName !== "string") return { kind: "nonterminal" };
192
+ if (!PACKAGED_ROLE_OUTPUT_TOOLS.has(message.toolName)) return { kind: "nonterminal" };
193
+
194
+ const infraFact = isNavigatorInfrastructureFailureFact(message.details)
195
+ ? message.details
196
+ : undefined;
197
+
198
+ // Infrastructure completion: exact isError === true + exact closed infra fact.
199
+ if (message.isError === true) {
200
+ if (infraFact === undefined) return { kind: "nonterminal" };
201
+ return { kind: "infrastructure", fact: infraFact };
202
+ }
203
+ // Accepted/human completion: exact isError === false and must not carry infra fact.
204
+ if (message.isError === false) {
205
+ if (infraFact !== undefined) return { kind: "nonterminal" };
206
+ return { kind: "accepted" };
207
+ }
208
+ // Missing, non-boolean, contradictory, or malformed shapes fail closed.
209
+ return { kind: "nonterminal" };
210
+ }
211
+
212
+ /**
213
+ * Durable-completion boolean over the shared classifier
214
+ * (accepted/human or infrastructure terminal).
215
+ */
216
+ export function isDurablePackagedRoleTerminalResult(
217
+ message: PackagedRoleTerminalMessage,
218
+ ): boolean {
219
+ const classification = classifyPackagedRoleTerminalResult(message);
220
+ return classification.kind === "accepted" || classification.kind === "infrastructure";
221
+ }
222
+
223
+ /** Receipt extractors admit only exact accepted/human terminals. */
224
+ export function isAcceptedPackagedRoleTerminalResult(
225
+ message: PackagedRoleTerminalMessage,
226
+ ): boolean {
227
+ return classifyPackagedRoleTerminalResult(message).kind === "accepted";
228
+ }
229
+
230
+ export type DurablePackagedRoleTerminalRef = {
231
+ readonly index: number;
232
+ readonly role: string;
233
+ readonly toolName: string;
234
+ readonly classification: "accepted" | "infrastructure";
235
+ readonly message: PackagedRoleTerminalMessage;
236
+ };
237
+
238
+ function durableTerminalAt(
239
+ entries: readonly NavigatorInvocationEntryLike[],
240
+ index: number,
241
+ ): DurablePackagedRoleTerminalRef | undefined {
242
+ const entry = entries[index];
243
+ if (entry?.type !== "message") return undefined;
244
+ const message = entry.message;
245
+ if (message?.role !== "toolResult") return undefined;
246
+ if (typeof message.toolName !== "string") return undefined;
247
+ const role = PACKAGED_ROLE_OUTPUT_TOOLS.get(message.toolName);
248
+ if (role === undefined) return undefined;
249
+ const classification = classifyPackagedRoleTerminalResult(message);
250
+ if (classification.kind !== "accepted" && classification.kind !== "infrastructure") {
251
+ return undefined;
252
+ }
253
+ return {
254
+ index,
255
+ role,
256
+ toolName: message.toolName,
257
+ classification: classification.kind,
258
+ message,
259
+ };
260
+ }
261
+
262
+ function isPackagedRoleTerminalEntry(entry: NavigatorInvocationEntryLike | undefined): boolean {
263
+ if (entry?.type !== "message") return false;
264
+ const message = entry.message;
265
+ if (message?.role !== "toolResult") return false;
266
+ return isDurablePackagedRoleTerminalResult(message);
267
+ }
268
+
269
+ function isInvocationMarkerEntry(entry: NavigatorInvocationEntryLike | undefined): boolean {
270
+ return entry?.type === "custom" && entry.customType === NAVIGATOR_INVOCATION_ENTRY;
271
+ }
272
+
273
+ function latestInvocationMarkerIndex(
274
+ entries: readonly NavigatorInvocationEntryLike[],
275
+ ): number {
276
+ for (let i = entries.length - 1; i >= 0; i -= 1) {
277
+ if (isInvocationMarkerEntry(entries[i])) return i;
278
+ }
279
+ return -1;
280
+ }
281
+
282
+ /** Latest durable packaged role terminal in session order. */
283
+ export function findLatestDurablePackagedRoleTerminal(
284
+ entries: readonly NavigatorInvocationEntryLike[],
285
+ ): DurablePackagedRoleTerminalRef | undefined {
286
+ for (let i = entries.length - 1; i >= 0; i -= 1) {
287
+ const terminal = durableTerminalAt(entries, i);
288
+ if (terminal !== undefined) return terminal;
289
+ }
290
+ return undefined;
291
+ }
292
+
293
+ /**
294
+ * Truth-table binding of the current durable packaged role terminal to its
295
+ * owning invocation marker. Singleton cardinality: multiple durable terminals
296
+ * after the same marker are ambiguous and fail closed.
297
+ */
298
+ export type DurableTerminalMarkerBinding =
299
+ | {
300
+ readonly kind: "bound";
301
+ readonly terminal: DurablePackagedRoleTerminalRef;
302
+ readonly marker: InvocationMarkerIdentity & { readonly index: number };
303
+ }
304
+ | {
305
+ readonly kind: "unbound";
306
+ readonly terminal: DurablePackagedRoleTerminalRef;
307
+ }
308
+ | { readonly kind: "absent" }
309
+ | { readonly kind: "ambiguous" };
310
+
311
+ export function bindCurrentDurableTerminalToMarker(
312
+ entries: readonly NavigatorInvocationEntryLike[],
313
+ ): DurableTerminalMarkerBinding {
314
+ const terminal = findLatestDurablePackagedRoleTerminal(entries);
315
+ if (terminal === undefined) return { kind: "absent" };
316
+
317
+ let markerIndex = -1;
318
+ for (let i = terminal.index - 1; i >= 0; i -= 1) {
319
+ if (isInvocationMarkerEntry(entries[i])) {
320
+ markerIndex = i;
321
+ break;
322
+ }
323
+ }
324
+ if (markerIndex < 0) {
325
+ return { kind: "unbound", terminal };
326
+ }
327
+
328
+ const marker = parseInvocationMarkerIdentity(entries[markerIndex]?.data);
329
+ if (marker === undefined) {
330
+ // Malformed nearest marker: no stale fallback; terminal exists but is unbound.
331
+ return { kind: "unbound", terminal };
332
+ }
333
+
334
+ let windowEnd = entries.length;
335
+ for (let i = markerIndex + 1; i < entries.length; i += 1) {
336
+ if (isInvocationMarkerEntry(entries[i])) {
337
+ windowEnd = i;
338
+ break;
339
+ }
340
+ }
341
+
342
+ let durableCount = 0;
343
+ for (let i = markerIndex + 1; i < windowEnd; i += 1) {
344
+ if (durableTerminalAt(entries, i) !== undefined) durableCount += 1;
345
+ }
346
+ if (durableCount !== 1) return { kind: "ambiguous" };
347
+ // Current terminal must be the singleton inside this marker window.
348
+ if (terminal.index <= markerIndex || terminal.index >= windowEnd) {
349
+ return { kind: "ambiguous" };
350
+ }
351
+
352
+ return {
353
+ kind: "bound",
354
+ terminal,
355
+ marker: { ...marker, index: markerIndex },
356
+ };
357
+ }
358
+
359
+ /**
360
+ * Whether public Receipt settlement may proceed for the current durable terminal.
361
+ * Only ambiguous multi-terminal bindings fail closed; attendance mismatch does not
362
+ * pollute an otherwise legal single Receipt.
363
+ */
364
+ export function isReceiptSettlementBindingClear(
365
+ entries: readonly NavigatorInvocationEntryLike[],
366
+ ): boolean {
367
+ return bindCurrentDurableTerminalToMarker(entries).kind !== "ambiguous";
368
+ }
369
+
370
+ export type LifecycleInvocationPrincipal = {
371
+ readonly invocationId: string;
372
+ /** True when the principal is already on the admitted session (resume; do not re-append). */
373
+ readonly resume: boolean;
374
+ };
375
+
376
+ /**
377
+ * Resolve the exact-invocation principal at shared role lifecycle start from the
378
+ * admitted session's typed entries only.
379
+ *
380
+ * - Latest marker is valid full identity, matches expected (when supplied), and no
381
+ * packaged role terminal follows it → resume.
382
+ * - Contradictory marker role/phase/subjectKey vs expected → mint (do not resume).
383
+ * - Latest marker is valid and a packaged role terminal already completed it → mint.
384
+ * - Missing or malformed latest marker → mint; never fall back to a stale older marker.
385
+ */
386
+ export function resolveLifecycleInvocationPrincipal(
387
+ entries: readonly NavigatorInvocationEntryLike[],
388
+ expected?: ExpectedInvocationIdentity,
389
+ ): LifecycleInvocationPrincipal {
390
+ const markerIndex = latestInvocationMarkerIndex(entries);
391
+ if (markerIndex < 0) {
392
+ return { invocationId: mintNavigatorInvocationId(), resume: false };
393
+ }
394
+
395
+ const marker = parseInvocationMarkerIdentity(entries[markerIndex]?.data);
396
+ if (marker === undefined) {
397
+ // Malformed nearest marker: honest new principal, no stale fallback.
398
+ return { invocationId: mintNavigatorInvocationId(), resume: false };
399
+ }
400
+
401
+ if (expected !== undefined && !markerMatchesExpectedIdentity(marker, expected)) {
402
+ // Contradictory marker must not resume a different role/phase/subject invocation.
403
+ return { invocationId: mintNavigatorInvocationId(), resume: false };
404
+ }
405
+
406
+ for (let i = markerIndex + 1; i < entries.length; i += 1) {
407
+ if (isPackagedRoleTerminalEntry(entries[i])) {
408
+ return { invocationId: mintNavigatorInvocationId(), resume: false };
409
+ }
410
+ }
411
+
412
+ return { invocationId: marker.invocationId, resume: true };
413
+ }
414
+
415
+ /**
416
+ * Exact current invocation principal already present on the role session.
417
+ * `beforeIndex` is the current packaged role terminal index: only the nearest
418
+ * marker strictly before that bound is applicable. A malformed nearest marker
419
+ * fails closed (undefined) — never falls back to a stale older valid marker.
420
+ * Markers at/after the terminal (future prepare/event) are never considered.
421
+ * Non-UUIDv7 values are rejected.
422
+ */
423
+ export function currentInvocationPrincipalFromSession(
424
+ entries: readonly NavigatorInvocationEntryLike[],
425
+ beforeIndex: number = entries.length,
426
+ ): string | undefined {
427
+ return currentInvocationMarkerFromSession(entries, beforeIndex)?.invocationId;
428
+ }
429
+
430
+ /**
431
+ * Full marker identity nearest before `beforeIndex` (current durable terminal).
432
+ * Malformed nearest fails closed — no stale older fallback.
433
+ */
434
+ export function currentInvocationMarkerFromSession(
435
+ entries: readonly NavigatorInvocationEntryLike[],
436
+ beforeIndex: number = entries.length,
437
+ ): InvocationMarkerIdentity | undefined {
438
+ const limit = Math.min(Math.max(beforeIndex, 0), entries.length);
439
+ for (let i = limit - 1; i >= 0; i -= 1) {
440
+ const entry = entries[i];
441
+ if (!isInvocationMarkerEntry(entry)) continue;
442
+ // Nearest marker only — parse full identity or fail closed.
443
+ return parseInvocationMarkerIdentity(entry?.data);
444
+ }
445
+ return undefined;
446
+ }
@@ -0,0 +1,46 @@
1
+ import { Type, type TSchema } from "typebox";
2
+
3
+ type UnionSchema = TSchema & { anyOf: Array<TSchema & { properties?: Record<string, TSchema> }> };
4
+
5
+ function described(name: string, schema: TSchema): TSchema {
6
+ if (typeof (schema as { description?: unknown }).description === "string") return schema;
7
+ throw new Error(`Tool field ${name} has no semantic description at its schema owner`);
8
+ }
9
+
10
+ function declarationIdentity(schema: TSchema): string {
11
+ const { description: _description, ...semantic } = schema as TSchema & { description?: string };
12
+ return JSON.stringify(semantic);
13
+ }
14
+
15
+ /** Collapse transport variants into one provider-compatible, zero-required open object. */
16
+ export function openToolObjectFromUnion(schema: UnionSchema): TSchema {
17
+ const declarations = new Map<string, TSchema[]>();
18
+ for (const variant of schema.anyOf) {
19
+ for (const [name, declaration] of Object.entries(variant.properties ?? {})) {
20
+ const entries = declarations.get(name) ?? [];
21
+ const identity = declarationIdentity(declaration);
22
+ if (!entries.some((entry) => declarationIdentity(entry) === identity)) entries.push(declaration);
23
+ declarations.set(name, entries);
24
+ }
25
+ }
26
+ const properties = Object.fromEntries([...declarations].map(([name, entries]) => {
27
+ const descriptions = [...new Set(entries.map((entry) => (entry as { description?: unknown }).description).filter((value): value is string => typeof value === "string"))].join(" ");
28
+ const declaration = entries.length === 1
29
+ ? entries[0]!
30
+ : Type.Union(entries, descriptions === "" ? {} : { description: descriptions });
31
+ return [name, Type.Optional(described(name, declaration))];
32
+ }));
33
+ const object = Type.Object(properties, { additionalProperties: true });
34
+ (object as unknown as { required: string[] }).required = [];
35
+ return object;
36
+ }
37
+
38
+ /** Open a pre-existing object transport schema without changing declarations. */
39
+ export function openToolObject(schema: TSchema & { properties: Record<string, TSchema> }): TSchema {
40
+ const object = Type.Object(
41
+ Object.fromEntries(Object.entries(schema.properties).map(([name, declaration]) => [name, Type.Optional(described(name, declaration))])),
42
+ { additionalProperties: true },
43
+ );
44
+ (object as unknown as { required: string[] }).required = [];
45
+ return object;
46
+ }
@@ -0,0 +1,109 @@
1
+ /** Package-owned Collector receipt leaf — no role registration surface. */
2
+
3
+ export const COLLECTOR_OUTPUT_TOOL = "ak_collector_output";
4
+ export const COLLECTOR_ACCEPTED_TEXT = "Collector receipt accepted";
5
+ export const COLLECTOR_HOST = "github.com";
6
+
7
+ export type CollectorIdentityGroup = {
8
+ identity: Record<string, unknown> | null;
9
+ displayLogin?: string;
10
+ attendance: true;
11
+ materials: Array<Record<string, unknown>>;
12
+ findings: Array<Record<string, unknown>>;
13
+ };
14
+
15
+ export type CollectorRequestAttempt = {
16
+ attemptId: string;
17
+ requestId: string;
18
+ observedHead: string;
19
+ snapshotId: string;
20
+ marker: string;
21
+ body: string;
22
+ startedAt: string;
23
+ status: "started" | "succeeded" | "rejected" | "ambiguous_loss" | "recovered";
24
+ responseDiagnostics?: string;
25
+ commentEvidenceId?: string;
26
+ recoverySnapshotId?: string;
27
+ };
28
+
29
+ export type CollectorSnapshot = {
30
+ snapshotId: string;
31
+ observedAt: string;
32
+ completedAt: string;
33
+ completedMono: number;
34
+ host: "github.com";
35
+ repository: string;
36
+ prNumber: number;
37
+ prState: string;
38
+ headOid: string;
39
+ complete: boolean;
40
+ evidenceIds: string[];
41
+ pageDiagnostics: Array<Record<string, unknown>>;
42
+ normalizedByteLength: number;
43
+ };
44
+
45
+ export type CollectorEvidenceRecord = {
46
+ evidenceId: string;
47
+ kind: string;
48
+ versionId: string;
49
+ contentDigest: string;
50
+ firstObservedAt: string;
51
+ raw: unknown;
52
+ };
53
+
54
+ export type CollectorReceipt = {
55
+ host: "github.com";
56
+ repository: string;
57
+ prNumber: number;
58
+ manifestDigest: string;
59
+ activationTime: string;
60
+ deadlineTime: string;
61
+ finalObservationTime: string;
62
+ finalSnapshotId: string;
63
+ targetHead: string;
64
+ groups: CollectorIdentityGroup[];
65
+ requestAttempts: CollectorRequestAttempt[];
66
+ snapshots: CollectorSnapshot[];
67
+ evidenceRecords: CollectorEvidenceRecord[];
68
+ };
69
+
70
+ function safeGet(value: unknown, key: string): unknown {
71
+ if ((typeof value !== "object" && typeof value !== "function") || value === null) return undefined;
72
+ try { return (value as Record<string, unknown>)[key]; } catch { return undefined; }
73
+ }
74
+ function records(value: unknown): Record<string, unknown>[] {
75
+ return Array.isArray(value) ? value.filter((item): item is Record<string, unknown> => item !== null && typeof item === "object") : [];
76
+ }
77
+ function strings(value: unknown): string[] {
78
+ return Array.isArray(value) ? value.filter((item): item is string => typeof item === "string") : [];
79
+ }
80
+
81
+ /** Settlement projection. Presence of the canonical groups array is the one Collector terminal discriminator. */
82
+ export function validateAcceptedCollectorReceipt(value: unknown): CollectorReceipt {
83
+ const rawGroups = safeGet(value, "groups");
84
+ if (!Array.isArray(rawGroups)) throw new Error("Collector receipt has no typed groups terminal discriminator");
85
+ const groups = records(rawGroups).map((group) => ({
86
+ identity: (safeGet(group, "identity") ?? null) as Record<string, unknown> | null,
87
+ ...(typeof safeGet(group, "displayLogin") === "string" ? { displayLogin: safeGet(group, "displayLogin") as string } : {}),
88
+ attendance: true as const,
89
+ materials: records(safeGet(group, "materials")),
90
+ findings: records(safeGet(group, "findings")),
91
+ }));
92
+ return {
93
+ host: safeGet(value, "host") as "github.com",
94
+ repository: safeGet(value, "repository") as string,
95
+ prNumber: safeGet(value, "prNumber") as number,
96
+ manifestDigest: safeGet(value, "manifestDigest") as string,
97
+ activationTime: safeGet(value, "activationTime") as string,
98
+ deadlineTime: safeGet(value, "deadlineTime") as string,
99
+ finalObservationTime: safeGet(value, "finalObservationTime") as string,
100
+ finalSnapshotId: safeGet(value, "finalSnapshotId") as string,
101
+ targetHead: safeGet(value, "targetHead") as string,
102
+ groups,
103
+ requestAttempts: records(safeGet(value, "requestAttempts")) as unknown as CollectorRequestAttempt[],
104
+ snapshots: records(safeGet(value, "snapshots")).map((snapshot) => ({
105
+ snapshotId: safeGet(snapshot, "snapshotId"), observedAt: safeGet(snapshot, "observedAt"), completedAt: safeGet(snapshot, "completedAt"), completedMono: safeGet(snapshot, "completedMono"), host: safeGet(snapshot, "host"), repository: safeGet(snapshot, "repository"), prNumber: safeGet(snapshot, "prNumber"), prState: safeGet(snapshot, "prState"), headOid: safeGet(snapshot, "headOid"), complete: safeGet(snapshot, "complete"), evidenceIds: strings(safeGet(snapshot, "evidenceIds")), pageDiagnostics: records(safeGet(snapshot, "pageDiagnostics")), normalizedByteLength: safeGet(snapshot, "normalizedByteLength"),
106
+ } as CollectorSnapshot)),
107
+ evidenceRecords: records(safeGet(value, "evidenceRecords")).map((record) => ({ evidenceId: safeGet(record, "evidenceId"), kind: safeGet(record, "kind"), versionId: safeGet(record, "versionId"), contentDigest: safeGet(record, "contentDigest"), firstObservedAt: safeGet(record, "firstObservedAt"), raw: safeGet(record, "raw") } as CollectorEvidenceRecord)),
108
+ };
109
+ }
@@ -0,0 +1,81 @@
1
+ import { Type, type Static } from "typebox";
2
+ import { FIXER_PREREQUISITE_ID_PATTERN, type FixerInvocationInput } from "./fixer-packet.ts";
3
+ import { openToolObjectFromUnion } from "../open-tool-schema.ts";
4
+
5
+ export const FIXER_OUTPUT_TOOL_NAME = "ak_fixer_output";
6
+ export const FIXER_ACCEPTED_TEXT = "Fixer report accepted";
7
+
8
+ const nonblankTransportString = Type.String({ minLength: 1 });
9
+ const authorityBlockerSchema = Type.Object({ cause: Type.Literal("authority_violation"), evidence: nonblankTransportString });
10
+ const prerequisiteBlockerSchema = Type.Object({ cause: Type.Literal("prerequisite_unmet"), prerequisiteId: Type.String({ pattern: FIXER_PREREQUISITE_ID_PATTERN }), evidence: nonblankTransportString });
11
+ const blockerSchema = Type.Union([authorityBlockerSchema, prerequisiteBlockerSchema]);
12
+ const exceptionSchema = Type.Object({ where: nonblankTransportString, reason: nonblankTransportString });
13
+ /** ⑥ test evidence slip — require submit when diff has test changes; machine does not check existence/completeness/coverage. */
14
+ const testEvidenceSchema = Type.Object({
15
+ contract: Type.String({ minLength: 1, description: "Contract the test change proves." }),
16
+ minimumNecessaryCost: Type.String({ minLength: 1, description: "One-line minimum necessary cost of the test change." }),
17
+ measuredDuration: Type.String({ minLength: 1, description: "Measured duration of the focused verification run." }),
18
+ }, { description: "Test evidence slip (submit when diff includes test changes; machine does not verify)." });
19
+ const completedClassResultSchema = Type.Object({
20
+ name: nonblankTransportString,
21
+ disposition: Type.Literal("completed"),
22
+ searchScope: nonblankTransportString,
23
+ exceptions: Type.Array(exceptionSchema),
24
+ commitSha: nonblankTransportString,
25
+ });
26
+ const refusedClassResultSchema = Type.Object({
27
+ name: nonblankTransportString,
28
+ disposition: Type.Literal("refused"),
29
+ remainingScope: nonblankTransportString,
30
+ blocker: blockerSchema,
31
+ });
32
+ const classResultSchema = Type.Union([completedClassResultSchema, refusedClassResultSchema]);
33
+ const completedClassResultsSchema = Type.Array(completedClassResultSchema, { minItems: 1 });
34
+
35
+ const fixerOutputVariants = Type.Union([
36
+ Type.Object({ status: Type.Literal("planned", { description: "Plan-phase proposal outcome." }), report: Type.String({ minLength: 1, description: "Truthful Fixer outcome report." }) }),
37
+ Type.Object({ status: Type.Literal("refused", { description: "Lawfully refused outcome." }), report: Type.String({ minLength: 1, description: "Truthful Fixer outcome report." }), remainingScope: Type.String({ minLength: 1, description: "Work that cannot lawfully be performed." }), blocker: Type.Unsafe({ ...blockerSchema, description: "Lawful blocker preventing completion." }) }),
38
+ Type.Object({ status: Type.Literal("unfinished", { description: "Honest unfinished apply outcome." }), report: Type.String({ minLength: 1, description: "Truthful Fixer outcome report." }), remainingScope: Type.String({ minLength: 1, description: "Work remaining after this invocation." }), classResults: Type.Optional(Type.Unsafe({ ...completedClassResultsSchema, description: "Completed class settlements from this invocation." })), testEvidence: Type.Optional(testEvidenceSchema) }),
39
+ Type.Object({ status: Type.Literal("completed", { description: "All assigned classes completed." }), report: Type.String({ minLength: 1, description: "Truthful Fixer outcome report." }), classResults: Type.Array(classResultSchema, { minItems: 1, description: "Completed class settlements." }), testEvidence: Type.Optional(testEvidenceSchema) }),
40
+ Type.Object({ status: Type.Literal("refused", { description: "All assigned classes lawfully refused." }), report: Type.String({ minLength: 1, description: "Truthful Fixer outcome report." }), classResults: Type.Array(classResultSchema, { minItems: 1, description: "Per-class refusal settlements." }) }),
41
+ Type.Object({ status: Type.Literal("partially_completed", { description: "Assigned classes include completions and lawful refusals." }), report: Type.String({ minLength: 1, description: "Truthful Fixer outcome report." }), classResults: Type.Array(classResultSchema, { minItems: 1, description: "Per-class completion or refusal settlements." }), testEvidence: Type.Optional(testEvidenceSchema) }),
42
+ ]);
43
+ export const fixerOutputSchema = openToolObjectFromUnion(fixerOutputVariants);
44
+
45
+ export type FixerBlocker = Static<typeof blockerSchema>;
46
+ export type FixerClassResult = Static<typeof classResultSchema>;
47
+ export type FixerTestEvidence = Static<typeof testEvidenceSchema>;
48
+ export type FixerOutput = Static<typeof fixerOutputVariants>;
49
+ export type FixerPhase = "plan" | "apply";
50
+
51
+ export function validateFixerOutput(value: unknown, _phase?: FixerPhase): FixerOutput {
52
+ return value as FixerOutput;
53
+ }
54
+
55
+ function safeProperty(value: unknown, property: string): unknown {
56
+ if (value === null || typeof value !== "object") return undefined;
57
+ try {
58
+ return (value as Record<string, unknown>)[property];
59
+ } catch {
60
+ return undefined;
61
+ }
62
+ }
63
+
64
+ /** Bind recognizable prerequisite blockers to the packet declaration. */
65
+ export function validateFixerOutputForPacket(value: unknown, phase: FixerPhase, packet: FixerInvocationInput): FixerOutput {
66
+ const output = validateFixerOutput(value, phase);
67
+ const declaredIds = new Set(packet.prerequisites.map((entry) => entry.id));
68
+ const topLevelBlocker = safeProperty(output, "blocker");
69
+ const classResults = safeProperty(output, "classResults");
70
+ const blockers = topLevelBlocker === undefined
71
+ ? (Array.isArray(classResults) ? classResults.map((entry) => safeProperty(entry, "blocker")) : [])
72
+ : [topLevelBlocker];
73
+ for (const blocker of blockers) {
74
+ if (safeProperty(blocker, "cause") !== "prerequisite_unmet") continue;
75
+ const prerequisiteId = safeProperty(blocker, "prerequisiteId");
76
+ if (typeof prerequisiteId === "string" && !declaredIds.has(prerequisiteId)) {
77
+ throw new Error("Fixer output violates blocker.prerequisiteId declared-prerequisite constraint");
78
+ }
79
+ }
80
+ return output;
81
+ }