@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,120 @@
1
+ import { lstatSync, realpathSync, statSync, writeFileSync, } from "node:fs";
2
+ import { dirname, isAbsolute, join, resolve } from "node:path";
3
+ import { SessionManager } from "@earendil-works/pi-coding-agent";
4
+ import { activationBookDirectory, ensureRealDirectoryTree, errnoCode, errorText, pathContainedIn, } from "./activation-ledger-topology.js";
5
+ export function childSessionManager(parent, cwd, childDirectory) {
6
+ const parentSession = parent?.getSessionFile();
7
+ return parentSession === undefined
8
+ ? SessionManager.inMemory(cwd)
9
+ : SessionManager.create(cwd, join(parent.getSessionDir(), childDirectory), { parentSession });
10
+ }
11
+ /**
12
+ * Typed missing durable session principal. Callers discriminate with instanceof/code;
13
+ * never by parsing message prose. Original filesystem causes are retained.
14
+ */
15
+ export class ActivationSessionFileMissingError extends Error {
16
+ code = "AK_ACTIVATION_SESSION_FILE_MISSING";
17
+ path;
18
+ constructor(path, options) {
19
+ super(`Workflow role activation durable session file does not exist: ${path}`, options?.cause === undefined ? undefined : { cause: options.cause });
20
+ this.name = "ActivationSessionFileMissingError";
21
+ this.path = path;
22
+ }
23
+ }
24
+ function materializeDeferredSessionFile(sessionManager, resolvedFile, ledgerHome) {
25
+ const header = sessionManager.getHeader?.();
26
+ if (header === null || header === undefined || header.type !== "session") {
27
+ throw new ActivationSessionFileMissingError(resolvedFile);
28
+ }
29
+ ensureRealDirectoryTree(ledgerHome, dirname(resolvedFile));
30
+ try {
31
+ writeFileSync(resolvedFile, `${JSON.stringify(header)}\n`, { flag: "wx" });
32
+ }
33
+ catch (error) {
34
+ if (errnoCode(error) !== "EEXIST") {
35
+ throw new Error(`Workflow role activation failed to materialize durable session file (${resolvedFile}): ${errorText(error)}`, { cause: error });
36
+ }
37
+ // Lost a create race — validate the winner below.
38
+ }
39
+ // Rebind full SessionManager so subsequent Pi appends use O_APPEND (flushed=true)
40
+ // instead of exclusive wx create against the file we just wrote.
41
+ if (typeof sessionManager.setSessionFile === "function") {
42
+ sessionManager.setSessionFile(resolvedFile);
43
+ }
44
+ }
45
+ /**
46
+ * Admit only a durable Pi session file principal under the resolved machine ledger
47
+ * book (ADR 0048). Requires an existing regular file at admission: resolve real paths
48
+ * and prove containment under the real book. Reject relative paths, outside-book paths,
49
+ * directories, symlink escapes, and nonexistent paths that cannot be materialized from
50
+ * the live SessionManager header. Original filesystem causes are retained.
51
+ *
52
+ * Upstream Pi defers exclusive create until the first assistant message. When the path
53
+ * is the live SessionManager principal under the book and only the header is in memory,
54
+ * admission materializes that header onto the same path before the fact is written so
55
+ * the role fact never points at a session that may be created later.
56
+ */
57
+ export function durableSessionPointer(sessionManager, options) {
58
+ const file = sessionManager.getSessionFile?.();
59
+ if (typeof file !== "string" || file.length === 0) {
60
+ throw new Error("Workflow role activation requires a durable Pi session file principal (getSessionFile); directory-only or --no-session invocations are rejected");
61
+ }
62
+ if (!isAbsolute(file)) {
63
+ throw new Error(`Workflow role activation requires an absolute durable session file path under the machine ledger book; got relative path: ${file}`);
64
+ }
65
+ const resolvedFile = resolve(file);
66
+ const bookRoot = resolve(activationBookDirectory(options.ledgerHome, options.bookKey));
67
+ if (!pathContainedIn(bookRoot, resolvedFile)) {
68
+ throw new Error(`Workflow role activation requires the durable session file principal under the machine ledger book (${bookRoot}); got: ${resolvedFile}`);
69
+ }
70
+ try {
71
+ lstatSync(resolvedFile);
72
+ }
73
+ catch (error) {
74
+ if (errnoCode(error) !== "ENOENT") {
75
+ throw new Error(`Workflow role activation failed to stat durable session file (${resolvedFile}): ${errorText(error)}`, { cause: error });
76
+ }
77
+ try {
78
+ materializeDeferredSessionFile(sessionManager, resolvedFile, options.ledgerHome);
79
+ }
80
+ catch (materializeError) {
81
+ // Missing principal: rethrow typed missing-file identity with the original ENOENT cause.
82
+ // Other materialize failures keep their own typed/native identity.
83
+ if (materializeError instanceof ActivationSessionFileMissingError) {
84
+ throw new ActivationSessionFileMissingError(resolvedFile, { cause: error });
85
+ }
86
+ throw materializeError;
87
+ }
88
+ }
89
+ let realBook;
90
+ try {
91
+ realBook = realpathSync(bookRoot);
92
+ }
93
+ catch (error) {
94
+ throw new Error(`Workflow role activation machine ledger book is not resolvable (${bookRoot}): ${errorText(error)}`, { cause: error });
95
+ }
96
+ let realFile;
97
+ try {
98
+ realFile = realpathSync(resolvedFile);
99
+ }
100
+ catch (error) {
101
+ throw new ActivationSessionFileMissingError(resolvedFile, { cause: error });
102
+ }
103
+ if (!pathContainedIn(realBook, realFile)) {
104
+ throw new Error(`Workflow role activation requires the durable session file principal under the machine ledger book (${realBook}); got: ${realFile}`);
105
+ }
106
+ let info;
107
+ try {
108
+ info = statSync(realFile);
109
+ }
110
+ catch (error) {
111
+ throw new Error(`Workflow role activation failed to stat durable session file (${realFile}): ${errorText(error)}`, { cause: error });
112
+ }
113
+ if (info.isDirectory()) {
114
+ throw new Error(`Workflow role activation durable session principal must be a file, not a directory: ${realFile}`);
115
+ }
116
+ if (!info.isFile()) {
117
+ throw new Error(`Workflow role activation durable session principal is not a regular file: ${realFile}`);
118
+ }
119
+ return { kind: "session-file", path: realFile };
120
+ }
@@ -0,0 +1,239 @@
1
+ import {
2
+ lstatSync,
3
+ mkdirSync,
4
+ realpathSync,
5
+ statSync
6
+ } from "node:fs";
7
+ import { homedir } from "node:os";
8
+ import { basename, dirname, isAbsolute, join, relative, resolve, sep } from "node:path";
9
+ class ActivationLedgerError extends Error {
10
+ code = "AK_ACTIVATION_LEDGER";
11
+ constructor(message, options) {
12
+ super(
13
+ message,
14
+ options?.cause === void 0 ? void 0 : { cause: options.cause }
15
+ );
16
+ this.name = "ActivationLedgerError";
17
+ }
18
+ }
19
+ function resolveActivationLedgerHome(home = homedir) {
20
+ const processHome = home();
21
+ if (typeof processHome !== "string" || processHome.length === 0 || !isAbsolute(processHome)) {
22
+ throw new ActivationLedgerError(
23
+ `activation ledger process home must be absolute, got ${JSON.stringify(processHome)}`
24
+ );
25
+ }
26
+ return resolve(processHome, ".ak-roles");
27
+ }
28
+ function activationBookDirectory(ledgerHome, bookKey) {
29
+ return join(ledgerHome, "books", bookKey);
30
+ }
31
+ function activationWaitingLedgerPath(ledgerHome, bookKey) {
32
+ return join(activationBookDirectory(ledgerHome, bookKey), "waiting.jsonl");
33
+ }
34
+ function pathContainedIn(root, candidate) {
35
+ const rel = relative(root, candidate);
36
+ return rel !== "" && rel !== ".." && !rel.startsWith(`..${sep}`) && !isAbsolute(rel);
37
+ }
38
+ function physicalPathIdentity(path) {
39
+ const absolute = resolve(path);
40
+ const missing = [];
41
+ let cursor = absolute;
42
+ while (true) {
43
+ try {
44
+ const real = realpathSync(cursor);
45
+ return missing.length === 0 ? real : join(real, ...missing);
46
+ } catch (error) {
47
+ if (errnoCode(error) !== "ENOENT") {
48
+ return absolute;
49
+ }
50
+ const parent = dirname(cursor);
51
+ if (parent === cursor) return absolute;
52
+ missing.unshift(basename(cursor));
53
+ cursor = parent;
54
+ }
55
+ }
56
+ }
57
+ function physicallyContainedIn(root, candidate) {
58
+ return pathContainedIn(physicalPathIdentity(root), physicalPathIdentity(candidate));
59
+ }
60
+ function errnoCode(error) {
61
+ return error !== null && typeof error === "object" && "code" in error && typeof error.code === "string" ? error.code : void 0;
62
+ }
63
+ function errorText(error) {
64
+ if (!(error instanceof Error)) return String(error);
65
+ return error.message;
66
+ }
67
+ function assertPhysicalLedgerRoot(absoluteRoot) {
68
+ let st;
69
+ try {
70
+ st = lstatSync(absoluteRoot);
71
+ } catch (error) {
72
+ if (errnoCode(error) !== "ENOENT") {
73
+ throw new ActivationLedgerError(
74
+ `activation ledger failed to stat home (${absoluteRoot}): ${errorText(error)}`,
75
+ { cause: error }
76
+ );
77
+ }
78
+ }
79
+ if (st === void 0) {
80
+ try {
81
+ mkdirSync(absoluteRoot, { recursive: true });
82
+ } catch (error) {
83
+ if (errnoCode(error) !== "EEXIST") {
84
+ throw new ActivationLedgerError(
85
+ `activation ledger failed to create home (${absoluteRoot}): ${errorText(error)}`,
86
+ { cause: error }
87
+ );
88
+ }
89
+ }
90
+ try {
91
+ st = lstatSync(absoluteRoot);
92
+ } catch (error) {
93
+ throw new ActivationLedgerError(
94
+ `activation ledger failed to stat home (${absoluteRoot}): ${errorText(error)}`,
95
+ { cause: error }
96
+ );
97
+ }
98
+ }
99
+ if (st.isSymbolicLink()) {
100
+ throw new ActivationLedgerError(
101
+ `activation ledger home is a symbolic link: ${absoluteRoot}`
102
+ );
103
+ }
104
+ if (!st.isDirectory()) {
105
+ throw new ActivationLedgerError(`activation ledger home is not a directory: ${absoluteRoot}`);
106
+ }
107
+ }
108
+ function ensureRealDirectoryTree(root, targetDir) {
109
+ if (!isAbsolute(root)) {
110
+ throw new ActivationLedgerError(`activation ledger home must be absolute: ${root}`);
111
+ }
112
+ const absoluteRoot = resolve(root);
113
+ const absoluteTarget = resolve(targetDir);
114
+ if (absoluteTarget !== absoluteRoot && !pathContainedIn(absoluteRoot, absoluteTarget)) {
115
+ throw new ActivationLedgerError(
116
+ `activation ledger path escapes ledger home (${absoluteRoot}): ${absoluteTarget}`
117
+ );
118
+ }
119
+ assertPhysicalLedgerRoot(absoluteRoot);
120
+ let realRoot;
121
+ try {
122
+ realRoot = realpathSync(absoluteRoot);
123
+ } catch (error) {
124
+ throw new ActivationLedgerError(
125
+ `activation ledger home is not resolvable (${absoluteRoot}): ${errorText(error)}`,
126
+ { cause: error }
127
+ );
128
+ }
129
+ if (!statSync(realRoot).isDirectory()) {
130
+ throw new ActivationLedgerError(`activation ledger home is not a directory: ${realRoot}`);
131
+ }
132
+ const rel = absoluteTarget === absoluteRoot ? "" : relative(absoluteRoot, absoluteTarget);
133
+ if (rel === "") return realRoot;
134
+ if (isAbsolute(rel) || rel === ".." || rel.startsWith(`..${sep}`)) {
135
+ throw new ActivationLedgerError(
136
+ `activation ledger path escapes ledger home (${absoluteRoot}): ${absoluteTarget}`
137
+ );
138
+ }
139
+ let lexicalCursor = absoluteRoot;
140
+ for (const part of rel.split(sep)) {
141
+ if (part === "" || part === ".") continue;
142
+ if (part === "..") {
143
+ throw new ActivationLedgerError(`activation ledger path contains '..': ${absoluteTarget}`);
144
+ }
145
+ lexicalCursor = join(lexicalCursor, part);
146
+ let st;
147
+ try {
148
+ st = lstatSync(lexicalCursor);
149
+ } catch (error) {
150
+ if (errnoCode(error) !== "ENOENT") {
151
+ throw new ActivationLedgerError(
152
+ `activation ledger failed to stat path component (${lexicalCursor}): ${errorText(error)}`,
153
+ { cause: error }
154
+ );
155
+ }
156
+ try {
157
+ mkdirSync(lexicalCursor);
158
+ } catch (mkdirError) {
159
+ if (errnoCode(mkdirError) !== "EEXIST") {
160
+ throw new ActivationLedgerError(
161
+ `activation ledger failed to create directory (${lexicalCursor}): ${errorText(mkdirError)}`,
162
+ { cause: mkdirError }
163
+ );
164
+ }
165
+ }
166
+ try {
167
+ st = lstatSync(lexicalCursor);
168
+ } catch (statError) {
169
+ throw new ActivationLedgerError(
170
+ `activation ledger failed to stat path component (${lexicalCursor}): ${errorText(statError)}`,
171
+ { cause: statError }
172
+ );
173
+ }
174
+ }
175
+ if (st.isSymbolicLink()) {
176
+ throw new ActivationLedgerError(
177
+ `activation ledger path component is a symbolic link: ${lexicalCursor}`
178
+ );
179
+ }
180
+ if (!st.isDirectory()) {
181
+ throw new ActivationLedgerError(`activation ledger path component is not a directory: ${lexicalCursor}`);
182
+ }
183
+ let realCursor;
184
+ try {
185
+ realCursor = realpathSync(lexicalCursor);
186
+ } catch (error) {
187
+ throw new ActivationLedgerError(
188
+ `activation ledger path component is not resolvable (${lexicalCursor}): ${errorText(error)}`,
189
+ { cause: error }
190
+ );
191
+ }
192
+ if (realCursor !== realRoot && !pathContainedIn(realRoot, realCursor)) {
193
+ throw new ActivationLedgerError(
194
+ `activation ledger path component escapes ledger home (${lexicalCursor} -> ${realCursor})`
195
+ );
196
+ }
197
+ }
198
+ try {
199
+ return realpathSync(absoluteTarget);
200
+ } catch (error) {
201
+ throw new ActivationLedgerError(
202
+ `activation ledger directory is not resolvable (${absoluteTarget}): ${errorText(error)}`,
203
+ { cause: error }
204
+ );
205
+ }
206
+ }
207
+ function assertLedgerFileInsideHome(ledgerPath, ledgerHome) {
208
+ if (!isAbsolute(ledgerHome)) {
209
+ throw new ActivationLedgerError(`activation ledger home must be absolute: ${ledgerHome}`);
210
+ }
211
+ const resolvedLedger = resolve(ledgerPath);
212
+ try {
213
+ if (!lstatSync(resolvedLedger).isSymbolicLink()) return;
214
+ throw new ActivationLedgerError(
215
+ `activation ledger file is a symbolic link: ${resolvedLedger}`
216
+ );
217
+ } catch (error) {
218
+ if (errnoCode(error) !== "ENOENT") {
219
+ if (error instanceof ActivationLedgerError) throw error;
220
+ throw new ActivationLedgerError(
221
+ `activation ledger failed to stat ledger file (${resolvedLedger}): ${errorText(error)}`,
222
+ { cause: error }
223
+ );
224
+ }
225
+ }
226
+ }
227
+ export {
228
+ ActivationLedgerError,
229
+ activationBookDirectory,
230
+ activationWaitingLedgerPath,
231
+ assertLedgerFileInsideHome,
232
+ ensureRealDirectoryTree,
233
+ errnoCode,
234
+ errorText,
235
+ pathContainedIn,
236
+ physicalPathIdentity,
237
+ physicallyContainedIn,
238
+ resolveActivationLedgerHome
239
+ };
@@ -0,0 +1,61 @@
1
+ const DISPATCH_STUB_EVENT = "dispatch-stub";
2
+ function buildDispatchStubFact(input) {
3
+ return {
4
+ event: DISPATCH_STUB_EVENT,
5
+ observedAt: input.observedAt,
6
+ bookKey: input.bookKey,
7
+ dispatch: input.dispatch.kind === "process" ? { kind: "process", pid: input.dispatch.pid } : { kind: "opaque", ref: input.dispatch.ref },
8
+ correlation: { kind: "caller", id: input.correlation.id }
9
+ };
10
+ }
11
+ function callerCorrelationId(correlation) {
12
+ return correlation.kind === "caller" ? correlation.id : void 0;
13
+ }
14
+ function dispatchMatchesActivation(dispatch, activation) {
15
+ const activationId = callerCorrelationId(activation.correlation);
16
+ return activationId !== void 0 && activationId === dispatch.correlation.id && activation.bookKey === dispatch.bookKey;
17
+ }
18
+ function reconcileInvocation(subject) {
19
+ const { dispatch, activation, process } = subject;
20
+ if (activation !== void 0) {
21
+ if (dispatch !== void 0 && dispatchMatchesActivation(dispatch, activation)) {
22
+ return {
23
+ kind: "matched",
24
+ correlationId: dispatch.correlation.id,
25
+ bookKey: dispatch.bookKey
26
+ };
27
+ }
28
+ return {
29
+ kind: "activation-without-dispatch",
30
+ correlationId: callerCorrelationId(activation.correlation),
31
+ bookKey: activation.bookKey
32
+ };
33
+ }
34
+ if (dispatch !== void 0) {
35
+ if (process === void 0) {
36
+ throw new TypeError(
37
+ "reconcileInvocation requires process liveness when activation is absent"
38
+ );
39
+ }
40
+ if (process.state === "alive") {
41
+ return {
42
+ kind: "pending",
43
+ correlationId: dispatch.correlation.id,
44
+ bookKey: dispatch.bookKey
45
+ };
46
+ }
47
+ return {
48
+ kind: "ghost",
49
+ correlationId: dispatch.correlation.id,
50
+ bookKey: dispatch.bookKey
51
+ };
52
+ }
53
+ throw new TypeError(
54
+ "reconcileInvocation requires a dispatch stub and/or an accepted-activation fact"
55
+ );
56
+ }
57
+ export {
58
+ DISPATCH_STUB_EVENT,
59
+ buildDispatchStubFact,
60
+ reconcileInvocation
61
+ };
@@ -0,0 +1,108 @@
1
+ export const AUDIT_ESCALATION_KIND = "audit_escalation";
2
+ // Live Navigator settlement may consume only the projection produced by this
3
+ // owner. Its private WeakSet cannot be authored by role output; persisted/
4
+ // replayed records are re-authenticated by the retained audit evidence binder.
5
+ const AUDIT_ESCALATION_LIVE_REGISTRY = new WeakSet();
6
+ /**
7
+ * Build the escalation delivery face.
8
+ * Role-delivered fields ride under the escalation discriminator (ADR 0055).
9
+ * `kind` always wins so the discriminator cannot be laundered.
10
+ * `conflicts` and `auditDecisionGate`, when present, always come from the
11
+ * audit (why we escalated and its gate). Raw ancillary values are not repaired.
12
+ * A role `decisionGate`, when present, stays at its own key via spread and is
13
+ * never overwritten (not folded, not dropped, not swapped into the audit home).
14
+ */
15
+ export function buildAuditEscalationResult(decision, deliveredOutput) {
16
+ const auditOwned = {
17
+ kind: AUDIT_ESCALATION_KIND,
18
+ };
19
+ if (Object.hasOwn(decision, "conflicts")) {
20
+ auditOwned.conflicts = decision.conflicts;
21
+ }
22
+ if (Object.hasOwn(decision, "decisionGate")) {
23
+ auditOwned.auditDecisionGate = decision.decisionGate;
24
+ }
25
+ const deliveredFields = deliveredOutput !== undefined &&
26
+ deliveredOutput !== null &&
27
+ typeof deliveredOutput === "object" &&
28
+ !Array.isArray(deliveredOutput)
29
+ ? { ...deliveredOutput }
30
+ : {};
31
+ // Role output cannot fill an absent audit-owned field.
32
+ delete deliveredFields.conflicts;
33
+ delete deliveredFields.auditDecisionGate;
34
+ const result = {
35
+ ...deliveredFields,
36
+ ...auditOwned,
37
+ };
38
+ AUDIT_ESCALATION_LIVE_REGISTRY.add(result);
39
+ return result;
40
+ }
41
+ /** True only for the audit-owned live projection, never for role-shaped data. */
42
+ export function isAuditEscalationProjection(value) {
43
+ if (!isAuditEscalationResult(value))
44
+ return false;
45
+ return AUDIT_ESCALATION_LIVE_REGISTRY.has(value);
46
+ }
47
+ function humanDecisionText(result) {
48
+ const lines = ["Human decision required: compliance audit escalation."];
49
+ if (Array.isArray(result.conflicts)) {
50
+ lines.push("Conflicts:", ...result.conflicts.map((conflict) => `- ${conflict}`));
51
+ }
52
+ const gate = result.auditDecisionGate;
53
+ if (gate !== null && typeof gate === "object" && !Array.isArray(gate)) {
54
+ const record = gate;
55
+ if (typeof record.question === "string")
56
+ lines.push(`Question: ${record.question}`);
57
+ if (Array.isArray(record.options)) {
58
+ lines.push("Options:", ...record.options.map((option) => `- ${option}`));
59
+ }
60
+ }
61
+ return lines.join("\n");
62
+ }
63
+ export function projectAuditEscalation(decision, deliveredOutput) {
64
+ const details = buildAuditEscalationResult(decision, deliveredOutput);
65
+ return {
66
+ content: [{ type: "text", text: humanDecisionText(details) }],
67
+ details,
68
+ terminate: true,
69
+ ...(decision.usage === undefined ? {} : { usage: decision.usage }),
70
+ };
71
+ }
72
+ export function projectAuditIncomplete(decision) {
73
+ return {
74
+ content: [{ type: "text", text: "Compliance audit incomplete; no role receipt was formed." }],
75
+ details: decision,
76
+ terminate: true,
77
+ ...(decision.usage === undefined ? {} : { usage: decision.usage }),
78
+ };
79
+ }
80
+ /**
81
+ * Discriminator-only recognition (ADR 0040). Shape of conflicts/options/gate
82
+ * is not a reject gate — element types and cardinality are delivery content.
83
+ */
84
+ export function isAuditEscalationResult(value) {
85
+ if (typeof value !== "object" || value === null || Array.isArray(value)) {
86
+ return false;
87
+ }
88
+ return value.kind === AUDIT_ESCALATION_KIND;
89
+ }
90
+ /**
91
+ * Dispose a parsed audit decision without repeating status handling in roles.
92
+ * Role output already delivered is preserved on the escalate face (ADR 0055).
93
+ */
94
+ export async function disposeComplianceDecision(decision, handlers, deliveredOutput) {
95
+ switch (decision.status) {
96
+ case "pass":
97
+ return await handlers.pass(decision.usage);
98
+ case "revise":
99
+ return await handlers.revise(decision.violations);
100
+ case "escalate":
101
+ return await handlers.escalate(projectAuditEscalation(decision, deliveredOutput));
102
+ case "audit-incomplete":
103
+ if (handlers.auditIncomplete === undefined) {
104
+ throw new Error("Compliance audit-incomplete handler is unavailable");
105
+ }
106
+ return await handlers.auditIncomplete(projectAuditIncomplete(decision));
107
+ }
108
+ }
@@ -0,0 +1,35 @@
1
+ import { dirname, join, resolve } from "node:path";
2
+ import { Type } from "typebox";
3
+ export const AUDITOR_DOSSIER_TOOL_NAME = "ak_get_run_dossier";
4
+ /** Resolve the exact run binding already carried by the parent record session. */
5
+ export function auditorRunDirectory(context) {
6
+ const sessionFile = context.sessionManager?.getSessionFile?.();
7
+ return sessionFile === undefined ? undefined : resolve(dirname(dirname(sessionFile)));
8
+ }
9
+ /** The one shared, run-bound dossier locator exposed to every auditor seat. */
10
+ export function createAuditorDossierTool(runDirectory) {
11
+ return {
12
+ name: AUDITOR_DOSSIER_TOOL_NAME,
13
+ description: "Locate this auditor's bound run dossier and its evidence entry points.",
14
+ parameters: Type.Object({}, { additionalProperties: false }),
15
+ async execute(_id, _params) {
16
+ if (runDirectory === undefined) {
17
+ return {
18
+ content: [{ type: "text", text: "This auditor has no run-bound dossier record." }],
19
+ details: undefined,
20
+ };
21
+ }
22
+ const details = {
23
+ runDirectory,
24
+ admittedRequest: join(runDirectory, "admitted-request.json"),
25
+ parentSessionCandidate: join(runDirectory, "session", "session.jsonl"),
26
+ attachments: join(runDirectory, "attachments"),
27
+ artifacts: join(runDirectory, "artifacts"),
28
+ };
29
+ return {
30
+ content: [{ type: "text", text: JSON.stringify(details) }],
31
+ details,
32
+ };
33
+ },
34
+ };
35
+ }
@@ -0,0 +1,78 @@
1
+ export const CANONICAL_JSON_VALIDATION_ERROR_CODE = "canonical-json-invalid";
2
+ export class CanonicalJsonValidationError extends Error {
3
+ path;
4
+ reason;
5
+ code = CANONICAL_JSON_VALIDATION_ERROR_CODE;
6
+ constructor(path, reason) {
7
+ super(`Canonical JSON validation failed at ${path}: ${reason}`);
8
+ this.path = path;
9
+ this.reason = reason;
10
+ this.name = "CanonicalJsonValidationError";
11
+ }
12
+ }
13
+ function childPath(path, segment) {
14
+ return `${path}/${String(segment).replaceAll("~", "~0").replaceAll("/", "~1")}`;
15
+ }
16
+ function ownStringKeys(value, path) {
17
+ const keys = Reflect.ownKeys(value);
18
+ if (keys.some((key) => typeof key === "symbol")) {
19
+ throw new CanonicalJsonValidationError(childPath(path, "<symbol>"), "symbol-keyed member is not a JSON member");
20
+ }
21
+ return keys;
22
+ }
23
+ /** Deterministic serialization of the closed JSON value domain. */
24
+ export function canonicalJson(value) {
25
+ const ancestors = new WeakSet();
26
+ const serialize = (item, path) => {
27
+ if (item === null)
28
+ return "null";
29
+ switch (typeof item) {
30
+ case "boolean":
31
+ case "string":
32
+ return JSON.stringify(item);
33
+ case "number":
34
+ if (!Number.isFinite(item))
35
+ throw new CanonicalJsonValidationError(path, "number must be finite");
36
+ return JSON.stringify(item);
37
+ case "undefined":
38
+ case "function":
39
+ case "symbol":
40
+ case "bigint":
41
+ throw new CanonicalJsonValidationError(path, `${typeof item} is not a JSON value`);
42
+ case "object": {
43
+ if (ancestors.has(item))
44
+ throw new CanonicalJsonValidationError(path, "cycle is not a JSON value");
45
+ ancestors.add(item);
46
+ try {
47
+ if (Array.isArray(item)) {
48
+ if (Object.getPrototypeOf(item) !== Array.prototype)
49
+ throw new CanonicalJsonValidationError(path, "array must not be a custom object");
50
+ const keys = ownStringKeys(item, path);
51
+ const extraKey = keys.find((key) => key !== "length" && (!/^(0|[1-9][0-9]*)$/.test(key) || Number(key) >= item.length));
52
+ if (extraKey !== undefined)
53
+ throw new CanonicalJsonValidationError(childPath(path, extraKey), "non-index array member is not a JSON member");
54
+ const values = [];
55
+ for (let index = 0; index < item.length; index += 1) {
56
+ if (!Object.hasOwn(item, index))
57
+ throw new CanonicalJsonValidationError(childPath(path, index), "sparse array slot is not a JSON value");
58
+ values.push(serialize(item[index], childPath(path, index)));
59
+ }
60
+ return `[${values.join(",")}]`;
61
+ }
62
+ const prototype = Object.getPrototypeOf(item);
63
+ if (prototype !== Object.prototype && prototype !== null)
64
+ throw new CanonicalJsonValidationError(path, "object must be a plain record");
65
+ return `{${ownStringKeys(item, path).sort().map((key) => `${JSON.stringify(key)}:${serialize(item[key], childPath(path, key))}`).join(",")}}`;
66
+ }
67
+ finally {
68
+ ancestors.delete(item);
69
+ }
70
+ }
71
+ }
72
+ throw new Error("Unreachable canonical JSON value type");
73
+ };
74
+ return serialize(value, "$");
75
+ }
76
+ export function canonicalJsonBytes(value) {
77
+ return new TextEncoder().encode(canonicalJson(value));
78
+ }