approval-md 0.2.0 → 0.3.0

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 (235) hide show
  1. package/README.md +63 -24
  2. package/SPEC.md +57 -11
  3. package/dist/src/channels/contract.d.ts +34 -1
  4. package/dist/src/channels/contract.js +200 -7
  5. package/dist/src/channels/contract.js.map +1 -1
  6. package/dist/src/channels/telegram.d.ts +123 -11
  7. package/dist/src/channels/telegram.js +218 -23
  8. package/dist/src/channels/telegram.js.map +1 -1
  9. package/dist/src/channels/web.d.ts +9 -0
  10. package/dist/src/channels/web.js +17 -0
  11. package/dist/src/channels/web.js.map +1 -1
  12. package/dist/src/cli/amend.js +214 -30
  13. package/dist/src/cli/amend.js.map +1 -1
  14. package/dist/src/cli/attest.d.ts +9 -0
  15. package/dist/src/cli/attest.js +134 -7
  16. package/dist/src/cli/attest.js.map +1 -1
  17. package/dist/src/cli/channel-telegram.d.ts +99 -26
  18. package/dist/src/cli/channel-telegram.js +311 -13
  19. package/dist/src/cli/channel-telegram.js.map +1 -1
  20. package/dist/src/cli/channel.d.ts +9 -0
  21. package/dist/src/cli/channel.js +9 -0
  22. package/dist/src/cli/channel.js.map +1 -1
  23. package/dist/src/cli/codex-bridge.d.ts +819 -0
  24. package/dist/src/cli/codex-bridge.js +1607 -0
  25. package/dist/src/cli/codex-bridge.js.map +1 -0
  26. package/dist/src/cli/codex.d.ts +1 -1
  27. package/dist/src/cli/codex.js +304 -7
  28. package/dist/src/cli/codex.js.map +1 -1
  29. package/dist/src/cli/daemon.js +4 -1
  30. package/dist/src/cli/daemon.js.map +1 -1
  31. package/dist/src/cli/doctor.js +467 -12
  32. package/dist/src/cli/doctor.js.map +1 -1
  33. package/dist/src/cli/execute.js +25 -2
  34. package/dist/src/cli/execute.js.map +1 -1
  35. package/dist/src/cli/help.d.ts +6 -2
  36. package/dist/src/cli/help.js +165 -60
  37. package/dist/src/cli/help.js.map +1 -1
  38. package/dist/src/cli/hook-codex.d.ts +49 -1
  39. package/dist/src/cli/hook-codex.js +60 -1
  40. package/dist/src/cli/hook-codex.js.map +1 -1
  41. package/dist/src/cli/hook.d.ts +459 -3
  42. package/dist/src/cli/hook.js +1062 -114
  43. package/dist/src/cli/hook.js.map +1 -1
  44. package/dist/src/cli/import.js +1 -1
  45. package/dist/src/cli/import.js.map +1 -1
  46. package/dist/src/cli/main.js +5 -3
  47. package/dist/src/cli/main.js.map +1 -1
  48. package/dist/src/cli/policy-apply.d.ts +195 -0
  49. package/dist/src/cli/policy-apply.js +573 -0
  50. package/dist/src/cli/policy-apply.js.map +1 -0
  51. package/dist/src/cli/policy.js +14 -1
  52. package/dist/src/cli/policy.js.map +1 -1
  53. package/dist/src/cli/preflight.d.ts +151 -13
  54. package/dist/src/cli/preflight.js +398 -41
  55. package/dist/src/cli/preflight.js.map +1 -1
  56. package/dist/src/cli/sandbox.js +17 -1
  57. package/dist/src/cli/sandbox.js.map +1 -1
  58. package/dist/src/cli/scaffold.d.ts +1 -1
  59. package/dist/src/cli/scaffold.js +1 -1
  60. package/dist/src/cli/setup-channel.d.ts +9 -0
  61. package/dist/src/cli/setup-channel.js +28 -1
  62. package/dist/src/cli/setup-channel.js.map +1 -1
  63. package/dist/src/cli/setup-common.d.ts +3 -1
  64. package/dist/src/cli/setup-common.js +3 -2
  65. package/dist/src/cli/setup-common.js.map +1 -1
  66. package/dist/src/cli/setup.d.ts +2 -0
  67. package/dist/src/cli/setup.js +94 -2
  68. package/dist/src/cli/setup.js.map +1 -1
  69. package/dist/src/cli/up.js +115 -51
  70. package/dist/src/cli/up.js.map +1 -1
  71. package/dist/src/cli/values.js +3 -4
  72. package/dist/src/cli/values.js.map +1 -1
  73. package/dist/src/cli/verb-registry.js +174 -9
  74. package/dist/src/cli/verb-registry.js.map +1 -1
  75. package/dist/src/cli/wordmark.d.ts +2 -2
  76. package/dist/src/cli/wordmark.js +2 -2
  77. package/dist/src/codex/broker.d.ts +229 -0
  78. package/dist/src/codex/broker.js +548 -0
  79. package/dist/src/codex/broker.js.map +1 -0
  80. package/dist/src/codex/runner.d.ts +178 -0
  81. package/dist/src/codex/runner.js +231 -0
  82. package/dist/src/codex/runner.js.map +1 -0
  83. package/dist/src/codex/serve.d.ts +56 -0
  84. package/dist/src/codex/serve.js +98 -0
  85. package/dist/src/codex/serve.js.map +1 -0
  86. package/dist/src/codex/workspace-commit.d.ts +219 -0
  87. package/dist/src/codex/workspace-commit.js +549 -0
  88. package/dist/src/codex/workspace-commit.js.map +1 -0
  89. package/dist/src/core/advance-cycle.d.ts +51 -0
  90. package/dist/src/core/advance-cycle.js +66 -2
  91. package/dist/src/core/advance-cycle.js.map +1 -1
  92. package/dist/src/core/agents-md.d.ts +20 -18
  93. package/dist/src/core/agents-md.js +33 -31
  94. package/dist/src/core/agents-md.js.map +1 -1
  95. package/dist/src/core/attest.d.ts +215 -0
  96. package/dist/src/core/attest.js +317 -7
  97. package/dist/src/core/attest.js.map +1 -1
  98. package/dist/src/core/audit.d.ts +18 -0
  99. package/dist/src/core/audit.js +13 -0
  100. package/dist/src/core/audit.js.map +1 -1
  101. package/dist/src/core/channel-owner.d.ts +213 -0
  102. package/dist/src/core/channel-owner.js +358 -0
  103. package/dist/src/core/channel-owner.js.map +1 -0
  104. package/dist/src/core/command-class.d.ts +154 -0
  105. package/dist/src/core/command-class.js +673 -20
  106. package/dist/src/core/command-class.js.map +1 -1
  107. package/dist/src/core/commit-guard.d.ts +272 -0
  108. package/dist/src/core/commit-guard.js +424 -0
  109. package/dist/src/core/commit-guard.js.map +1 -0
  110. package/dist/src/core/daemon-actor.d.ts +45 -0
  111. package/dist/src/core/daemon-actor.js +54 -0
  112. package/dist/src/core/daemon-actor.js.map +1 -0
  113. package/dist/src/core/dark-session.d.ts +109 -8
  114. package/dist/src/core/dark-session.js +266 -82
  115. package/dist/src/core/dark-session.js.map +1 -1
  116. package/dist/src/core/decision-refusal.d.ts +23 -2
  117. package/dist/src/core/decision-refusal.js +24 -2
  118. package/dist/src/core/decision-refusal.js.map +1 -1
  119. package/dist/src/core/env-file.d.ts +5 -0
  120. package/dist/src/core/env-file.js +60 -1
  121. package/dist/src/core/env-file.js.map +1 -1
  122. package/dist/src/core/execute.d.ts +15 -2
  123. package/dist/src/core/execute.js +15 -2
  124. package/dist/src/core/execute.js.map +1 -1
  125. package/dist/src/core/gate.d.ts +86 -1
  126. package/dist/src/core/gate.js +81 -1
  127. package/dist/src/core/gate.js.map +1 -1
  128. package/dist/src/core/gesture-refusal.d.ts +166 -0
  129. package/dist/src/core/gesture-refusal.js +188 -0
  130. package/dist/src/core/gesture-refusal.js.map +1 -0
  131. package/dist/src/core/harness-version.d.ts +1 -1
  132. package/dist/src/core/harness-version.js +3 -1
  133. package/dist/src/core/harness-version.js.map +1 -1
  134. package/dist/src/core/instance.d.ts +59 -2
  135. package/dist/src/core/instance.js +113 -0
  136. package/dist/src/core/instance.js.map +1 -1
  137. package/dist/src/core/log.d.ts +39 -1
  138. package/dist/src/core/log.js.map +1 -1
  139. package/dist/src/core/policy-explain.d.ts +10 -0
  140. package/dist/src/core/policy-explain.js +32 -0
  141. package/dist/src/core/policy-explain.js.map +1 -1
  142. package/dist/src/core/policy-load.d.ts +41 -1
  143. package/dist/src/core/policy-load.js +21 -3
  144. package/dist/src/core/policy-load.js.map +1 -1
  145. package/dist/src/core/policy-match.d.ts +43 -0
  146. package/dist/src/core/policy-match.js +52 -0
  147. package/dist/src/core/policy-match.js.map +1 -1
  148. package/dist/src/core/policy-proposal.d.ts +52 -0
  149. package/dist/src/core/policy-proposal.js +102 -2
  150. package/dist/src/core/policy-proposal.js.map +1 -1
  151. package/dist/src/core/protected-path-guard.d.ts +117 -4
  152. package/dist/src/core/protected-path-guard.js +362 -48
  153. package/dist/src/core/protected-path-guard.js.map +1 -1
  154. package/dist/src/core/question-preempted.d.ts +141 -0
  155. package/dist/src/core/question-preempted.js +152 -0
  156. package/dist/src/core/question-preempted.js.map +1 -0
  157. package/dist/src/core/read-scope.d.ts +172 -0
  158. package/dist/src/core/read-scope.js +252 -0
  159. package/dist/src/core/read-scope.js.map +1 -0
  160. package/dist/src/core/sandbox.d.ts +81 -0
  161. package/dist/src/core/sandbox.js +190 -1
  162. package/dist/src/core/sandbox.js.map +1 -1
  163. package/dist/src/core/sender-identity.d.ts +476 -0
  164. package/dist/src/core/sender-identity.js +572 -0
  165. package/dist/src/core/sender-identity.js.map +1 -0
  166. package/dist/src/core/shlex.d.ts +102 -0
  167. package/dist/src/core/shlex.js +159 -0
  168. package/dist/src/core/shlex.js.map +1 -0
  169. package/dist/src/core/values.d.ts +18 -8
  170. package/dist/src/core/values.js +36 -1
  171. package/dist/src/core/values.js.map +1 -1
  172. package/dist/src/daemon/advance.d.ts +10 -0
  173. package/dist/src/daemon/advance.js +25 -4
  174. package/dist/src/daemon/advance.js.map +1 -1
  175. package/dist/src/daemon/daemon.js +9 -0
  176. package/dist/src/daemon/daemon.js.map +1 -1
  177. package/dist/src/daemon/git-evidence.d.ts +2 -2
  178. package/dist/src/daemon/git-evidence.js +1 -1
  179. package/dist/src/mcp/server.js +8 -0
  180. package/dist/src/mcp/server.js.map +1 -1
  181. package/docs/cli-reference.md +932 -32
  182. package/docs/codex-enforced-session.md +75 -2
  183. package/docs/codex-workspace-broker.md +118 -0
  184. package/package.json +3 -1
  185. package/schema/event.schema.json +538 -9
  186. package/schema/fixtures/event/invalid/approval-granted-sender-hashed-false.json +20 -0
  187. package/schema/fixtures/event/invalid/approval-granted-sender-hashed-raw-id.json +20 -0
  188. package/schema/fixtures/event/invalid/audit-gesture-refused-human-actor.json +16 -0
  189. package/schema/fixtures/event/invalid/audit-gesture-refused-no-actor-no-sender.json +15 -0
  190. package/schema/fixtures/event/invalid/audit-gesture-refused-unknown-gesture.json +16 -0
  191. package/schema/fixtures/event/invalid/audit-question-preempted-agent-actor.json +16 -0
  192. package/schema/fixtures/event/invalid/audit-question-preempted-no-question-id.json +16 -0
  193. package/schema/fixtures/event/invalid/audit-question-preempted-unknown-source.json +15 -0
  194. package/schema/fixtures/event/invalid/gate-path-signed-off-absolute-path.json +14 -0
  195. package/schema/fixtures/event/invalid/gate-path-signed-off-agent-actor.json +14 -0
  196. package/schema/fixtures/event/invalid/gate-path-signed-off-missing-path.json +13 -0
  197. package/schema/fixtures/event/valid/approval-granted-sender-hashed.json +20 -0
  198. package/schema/fixtures/event/valid/audit-gesture-refused-review-note.json +21 -0
  199. package/schema/fixtures/event/valid/audit-gesture-refused-sender-key-unavailable.json +19 -0
  200. package/schema/fixtures/event/valid/audit-gesture-refused.json +19 -0
  201. package/schema/fixtures/event/valid/audit-question-preempted-no-verdict.json +16 -0
  202. package/schema/fixtures/event/valid/audit-question-preempted.json +20 -0
  203. package/schema/fixtures/event/valid/gate-path-signed-off.json +14 -0
  204. package/schema/fixtures/event/valid/harness-kind-claude-code.json +23 -0
  205. package/schema/fixtures/event/valid/harness-kind-codex.json +23 -0
  206. package/schema/fixtures/event/valid/harness-kind-cursor.json +23 -0
  207. package/schema/fixtures/event/valid/harness-kind-grok.json +23 -0
  208. package/schema/fixtures/event/valid/harness-kind-muse.json +23 -0
  209. package/schema/fixtures/policy/invalid/senders-half-keyed.json +20 -0
  210. package/schema/fixtures/policy/valid/canonical.json +1 -1
  211. package/schema/fixtures/policy/valid/senders-keyed.json +24 -0
  212. package/schema/fixtures/policy-md/valid/canonical.md +1 -1
  213. package/schema/fixtures/policy-md/valid/with-values.md +5 -7
  214. package/schema/fixtures/values/invalid/class-shaped.json +1 -1
  215. package/schema/fixtures/values/invalid/duplicate-entry.json +1 -1
  216. package/schema/fixtures/values/invalid/non-string-item.json +1 -1
  217. package/schema/fixtures/values/invalid/over-cap.json +1 -1
  218. package/schema/fixtures/values/invalid/unknown-key.json +1 -1
  219. package/schema/fixtures/values/invalid/version-float.json +1 -0
  220. package/schema/fixtures/values/invalid/version-integer.json +1 -0
  221. package/schema/fixtures/values/invalid/version-wrong-string.json +1 -0
  222. package/schema/fixtures/values/valid/empty-lists.json +2 -3
  223. package/schema/fixtures/values/valid/full.json +5 -7
  224. package/schema/fixtures/values/valid/minimal.json +1 -1
  225. package/schema/fixtures/values-md/invalid/schema-invalid.md +5 -3
  226. package/schema/fixtures/values-md/invalid/two-blocks.md +3 -3
  227. package/schema/fixtures/values-md/invalid/unterminated.md +2 -2
  228. package/schema/fixtures/values-md/invalid/version-1.md +69 -0
  229. package/schema/fixtures/values-md/invalid/version-unquoted.md +64 -0
  230. package/schema/fixtures/values-md/invalid/yaml-error.md +2 -2
  231. package/schema/fixtures/values-md/valid/absent.md +1 -1
  232. package/schema/fixtures/values-md/valid/with-values.md +5 -7
  233. package/schema/policy.schema.json +54 -2
  234. package/schema/values.schema.json +7 -11
  235. package/schema/fixtures/values/invalid/version-string.json +0 -1
@@ -0,0 +1,549 @@
1
+ /**
2
+ * The durable half of the Codex workspace broker (APRV-325.2).
3
+ *
4
+ * `workspace-plan.ts` decides what a change IS; this module is the only place
5
+ * that makes one happen. It exists because POSIX gives no atomic multi-file
6
+ * rename: four `rename(2)` calls are four separate instants, and a crash
7
+ * between the second and the third leaves a workspace that is neither the state
8
+ * the human approved nor the state they started from.
9
+ *
10
+ * ## The three states, and the one this module refuses to invent
11
+ *
12
+ * A committed transaction is `after`. An untouched one is `before`. Anything
13
+ * else is `mixed`, and mixed is reported as mixed. The classification is made
14
+ * by READING the filesystem back against the journal's recorded digests, never
15
+ * from what the applying code believes it did: a `rename` that returned zero
16
+ * and a `rename` whose effect a crash lost look identical from inside the
17
+ * process that called it, and only the bytes on disk can tell them apart. That
18
+ * is why {@link inspectWorkspaceState} runs after a successful apply as well as
19
+ * after a failed one, and why a mixed result reaches the log as
20
+ * `execution.indeterminate` with reason `workspace-commit-unknown` rather than
21
+ * as a failure (SPEC.md §8's closed reason set, extended by this task).
22
+ *
23
+ * ## Ordering, and why the journal is written before anything moves
24
+ *
25
+ * 1. Stage every new byte and every preimage into a directory on the SAME
26
+ * filesystem as the workspace, and `fsync` each file and then the directory.
27
+ * Nothing in the workspace has changed yet, so a crash here is `before`.
28
+ * 2. Write the journal — the plan's payload hash, every endpoint, and the
29
+ * before/after digest of each — and `fsync` it and its directory. This is
30
+ * the instant after which a crash is RECOVERABLE rather than merely
31
+ * undecidable: a journal on disk names what the workspace was and what it
32
+ * was becoming, so a later reader can prove which of the two it is in.
33
+ * 3. Apply, in a fixed order, then `fsync` every touched parent directory.
34
+ * 4. Read the endpoints back. Only an all-`after` reading removes the journal
35
+ * as a success; an all-`before` reading removes it as an untaken
36
+ * transaction; a mixed reading LEAVES IT for a person.
37
+ *
38
+ * ## Custody is claimed only as far as the platform proves it
39
+ *
40
+ * {@link acquireWorkspaceCustody} takes an `O_CREAT | O_EXCL` lock, which
41
+ * excludes other cooperating brokers and nothing else, and then asks POSIX
42
+ * ownership and mode whether any other principal can write the endpoints it is
43
+ * about to touch. It reports `os-exclusive` only when both hold, `advisory`
44
+ * otherwise, and it always reports `acl-unproven`, because ownership and mode
45
+ * do not speak about ACLs (the same honesty `codex/trust.ts` already keeps). A
46
+ * caller that needs the strong answer asks for it and is refused when the host
47
+ * cannot give it; nothing here silently downgrades.
48
+ */
49
+ import { createHash } from "node:crypto";
50
+ import { closeSync, constants, fstatSync, fsyncSync, mkdirSync, openSync, readFileSync, readSync, renameSync, rmSync, statSync, unlinkSync, writeSync, } from "node:fs";
51
+ import { dirname, join, resolve } from "node:path";
52
+ import { canonicalize } from "../core/jcs.js";
53
+ export const WORKSPACE_COMMIT_VERSION = "approval.codex.workspace-commit.v1";
54
+ /**
55
+ * The one directory name inside a workspace root that a proposal may never
56
+ * name (the broker refuses an endpoint under it before the planner ever reads
57
+ * a preimage). Staging has to share a filesystem with the workspace for
58
+ * `rename` to be atomic, so it lives inside it; reserving the name is what
59
+ * stops a proposal from staging over its own transaction.
60
+ */
61
+ export const WORKSPACE_TXN_DIR = ".approval-codex-txn";
62
+ /** The lockfile, beside the staging directory and under the same reservation. */
63
+ export const WORKSPACE_LOCK_FILE = ".approval-codex-lock";
64
+ const JOURNAL_FILE = "journal.json";
65
+ function fsyncDir(path) {
66
+ let fd = null;
67
+ try {
68
+ fd = openSync(path, constants.O_RDONLY);
69
+ fsyncSync(fd);
70
+ }
71
+ catch {
72
+ // A filesystem that refuses to fsync a directory handle (some network and
73
+ // virtual filesystems do) is reported by the readback, not guessed at here.
74
+ }
75
+ finally {
76
+ if (fd !== null)
77
+ closeSync(fd);
78
+ }
79
+ }
80
+ function writeDurable(path, bytes) {
81
+ const fd = openSync(path, constants.O_WRONLY | constants.O_CREAT | constants.O_EXCL, 0o600);
82
+ try {
83
+ let written = 0;
84
+ while (written < bytes.byteLength) {
85
+ written += writeSync(fd, bytes, written, bytes.byteLength - written);
86
+ }
87
+ fsyncSync(fd);
88
+ }
89
+ finally {
90
+ closeSync(fd);
91
+ }
92
+ }
93
+ /**
94
+ * Is `path` writable by a principal other than this process's effective user,
95
+ * as far as POSIX ownership and mode can say?
96
+ *
97
+ * `null` means the question could not be asked (the path could not be stated),
98
+ * which is not the same as "no" and is never read as one.
99
+ */
100
+ function foreignWritable(path) {
101
+ try {
102
+ const stats = statSync(path);
103
+ if ((stats.mode & 0o022) !== 0)
104
+ return true;
105
+ return stats.uid !== process.getuid?.();
106
+ }
107
+ catch {
108
+ return null;
109
+ }
110
+ }
111
+ /**
112
+ * Take the workspace lock and report how much exclusion it bought.
113
+ *
114
+ * The lock is `O_CREAT | O_EXCL` on a reserved name, which is the strongest
115
+ * primitive available to a process with no privilege: it excludes every other
116
+ * broker that respects it and no one else. Whether anything else CAN write is a
117
+ * separate question, asked of POSIX afterwards, and answered conservatively.
118
+ */
119
+ export function acquireWorkspaceCustody(root, touchedDirectories) {
120
+ const lockPath = join(root, WORKSPACE_LOCK_FILE);
121
+ const txnDir = join(root, WORKSPACE_TXN_DIR);
122
+ let lockFd;
123
+ try {
124
+ lockFd = openSync(lockPath, constants.O_WRONLY | constants.O_CREAT | constants.O_EXCL, 0o600);
125
+ }
126
+ catch (cause) {
127
+ const code = cause.code;
128
+ if (code === "EEXIST") {
129
+ return {
130
+ ok: false,
131
+ code: "custody-contended",
132
+ message: `another workspace transaction holds ${lockPath}; no operation was attempted`,
133
+ };
134
+ }
135
+ return {
136
+ ok: false,
137
+ code: "custody-unavailable",
138
+ message: `workspace lock ${lockPath} could not be created: ${String(code ?? cause)}`,
139
+ };
140
+ }
141
+ try {
142
+ closeSync(lockFd);
143
+ }
144
+ catch {
145
+ // The lock is the file's existence, not the descriptor.
146
+ }
147
+ const findings = ["acl-unproven"];
148
+ let exclusive = true;
149
+ for (const directory of [root, ...touchedDirectories]) {
150
+ const foreign = foreignWritable(directory);
151
+ if (foreign === null) {
152
+ findings.push(`custody-unreadable:${directory}`);
153
+ exclusive = false;
154
+ continue;
155
+ }
156
+ if (foreign) {
157
+ findings.push(`foreign-writable:${directory}`);
158
+ exclusive = false;
159
+ }
160
+ }
161
+ try {
162
+ mkdirSync(txnDir, { mode: 0o700 });
163
+ }
164
+ catch (cause) {
165
+ try {
166
+ unlinkSync(lockPath);
167
+ }
168
+ catch {
169
+ // Best effort: the failure below is the one that matters.
170
+ }
171
+ return {
172
+ ok: false,
173
+ code: "custody-unavailable",
174
+ message: `staging directory ${txnDir} could not be created: ${String(cause.code ?? cause)}`,
175
+ };
176
+ }
177
+ fsyncDir(root);
178
+ return {
179
+ ok: true,
180
+ custody: {
181
+ report: { kind: exclusive ? "os-exclusive" : "advisory", findings },
182
+ root,
183
+ txnDir,
184
+ release: () => {
185
+ try {
186
+ rmSync(txnDir, { recursive: true, force: true });
187
+ }
188
+ catch {
189
+ // Leaving staging behind is visible to recovery and to a person.
190
+ }
191
+ try {
192
+ unlinkSync(lockPath);
193
+ }
194
+ catch {
195
+ // Same.
196
+ }
197
+ fsyncDir(root);
198
+ },
199
+ },
200
+ };
201
+ }
202
+ function sha256(bytes) {
203
+ return createHash("sha256").update(bytes).digest("hex");
204
+ }
205
+ /**
206
+ * The endpoints one planned operation owns, in the shape recovery reads.
207
+ *
208
+ * A move owns two, and the second one's `before` is deliberately `null`: the
209
+ * planner refuses a move whose destination exists, so "absent" is the state the
210
+ * transaction started from and the state a rollback must restore.
211
+ */
212
+ export function journalEndpointsOf(operation) {
213
+ switch (operation.kind) {
214
+ case "create":
215
+ return [{ path: operation.path, before_sha256: null, after_sha256: operation.after.sha256 }];
216
+ case "replace":
217
+ return [{
218
+ path: operation.path,
219
+ before_sha256: operation.before.sha256,
220
+ after_sha256: operation.after.sha256,
221
+ }];
222
+ case "delete":
223
+ return [{ path: operation.path, before_sha256: operation.before.sha256, after_sha256: null }];
224
+ case "move":
225
+ return [
226
+ { path: operation.from, before_sha256: operation.before.sha256, after_sha256: null },
227
+ { path: operation.to, before_sha256: null, after_sha256: operation.before.sha256 },
228
+ ];
229
+ }
230
+ }
231
+ function newBytesOf(operation) {
232
+ if (operation.kind === "create" || operation.kind === "replace") {
233
+ return Buffer.from(operation.after.base64, "base64");
234
+ }
235
+ return null;
236
+ }
237
+ function preimageBytesOf(operation) {
238
+ if (operation.kind === "create")
239
+ return null;
240
+ return Buffer.from(operation.before.base64, "base64");
241
+ }
242
+ /** Every parent directory a transaction will write into, absolute and deduplicated. */
243
+ export function touchedDirectories(plan) {
244
+ const seen = new Set();
245
+ for (const operation of plan.payload.operations) {
246
+ for (const endpoint of journalEndpointsOf(operation)) {
247
+ seen.add(dirname(resolve(plan.payload.root, endpoint.path)));
248
+ }
249
+ }
250
+ return [...seen].sort();
251
+ }
252
+ const MAX_READBACK_BYTES = 1_048_576;
253
+ /**
254
+ * The digest of a regular single-link file, `null` when it is absent, and
255
+ * `undefined` when the question could not be answered.
256
+ *
257
+ * `O_NOFOLLOW` and the `nlink` test are the planner's rules, repeated here
258
+ * because a readback that followed a symlink planted between the apply and the
259
+ * inspection would report someone else's bytes as this transaction's outcome.
260
+ */
261
+ function readbackDigest(path) {
262
+ let fd = null;
263
+ try {
264
+ fd = openSync(path, constants.O_RDONLY | constants.O_NOFOLLOW | constants.O_NONBLOCK);
265
+ }
266
+ catch (cause) {
267
+ const code = cause.code;
268
+ if (code === "ENOENT")
269
+ return null;
270
+ return undefined;
271
+ }
272
+ try {
273
+ const stats = fstatSync(fd, { bigint: true });
274
+ if (!stats.isFile() || stats.nlink !== 1n)
275
+ return undefined;
276
+ if (stats.size > BigInt(MAX_READBACK_BYTES))
277
+ return undefined;
278
+ const buffer = Buffer.alloc(Number(stats.size));
279
+ let length = 0;
280
+ while (length < buffer.byteLength) {
281
+ const count = readSync(fd, buffer, length, buffer.byteLength - length, length);
282
+ if (count === 0)
283
+ break;
284
+ length += count;
285
+ }
286
+ if (length !== buffer.byteLength)
287
+ return undefined;
288
+ return sha256(buffer);
289
+ }
290
+ catch {
291
+ return undefined;
292
+ }
293
+ finally {
294
+ closeSync(fd);
295
+ }
296
+ }
297
+ function endpointState(root, endpoint) {
298
+ const digest = readbackDigest(resolve(root, endpoint.path));
299
+ if (digest === undefined)
300
+ return "unreadable";
301
+ const isAfter = digest === endpoint.after_sha256;
302
+ const isBefore = digest === endpoint.before_sha256;
303
+ // `before` wins a tie, and a tie is real: a replace whose new bytes equal its
304
+ // old bytes is both, and calling it `before` can only under-claim.
305
+ if (isBefore)
306
+ return "before";
307
+ if (isAfter)
308
+ return "after";
309
+ return "other";
310
+ }
311
+ /**
312
+ * Classify the workspace against a journal by reading it.
313
+ *
314
+ * `mixed` is the honest answer for anything that is not uniformly one state,
315
+ * INCLUDING a single unreadable endpoint: "I could not look" and "it is half
316
+ * done" are both "nobody knows", and collapsing the first into `before` would
317
+ * be the runtime deciding an effect did not happen.
318
+ */
319
+ export function inspectWorkspaceState(journal) {
320
+ const endpoints = [];
321
+ for (const operation of journal.operations) {
322
+ for (const endpoint of operation.endpoints) {
323
+ endpoints.push({ path: endpoint.path, state: endpointState(journal.root, endpoint) });
324
+ }
325
+ }
326
+ const every = (state) => endpoints.every((entry) => entry.state === state);
327
+ if (endpoints.length > 0 && every("after"))
328
+ return { state: "after", endpoints };
329
+ if (endpoints.length === 0 || every("before"))
330
+ return { state: "before", endpoints };
331
+ return { state: "mixed", endpoints };
332
+ }
333
+ function journalPath(txnDir) {
334
+ return join(txnDir, JOURNAL_FILE);
335
+ }
336
+ /**
337
+ * Stage, journal, apply, read back.
338
+ *
339
+ * The caller holds custody (and therefore the staging directory) and keeps it
340
+ * until this returns. A `stage-failed` result is the one result that guarantees
341
+ * the workspace is untouched by construction rather than by inspection: nothing
342
+ * visible has been attempted when it fires.
343
+ */
344
+ export function commitWorkspacePlan(plan, custody, options = {}) {
345
+ const { root, txnDir } = custody;
346
+ const operations = [];
347
+ // --- Phase 1: stage. Nothing in the workspace changes. ------------------
348
+ try {
349
+ for (let index = 0; index < plan.payload.operations.length; index += 1) {
350
+ const operation = plan.payload.operations[index];
351
+ const newBytes = newBytesOf(operation);
352
+ const preimage = preimageBytesOf(operation);
353
+ const staged = newBytes === null ? null : `new-${String(index)}`;
354
+ const saved = preimage === null ? null : `old-${String(index)}`;
355
+ if (staged !== null && newBytes !== null)
356
+ writeDurable(join(txnDir, staged), newBytes);
357
+ if (saved !== null && preimage !== null)
358
+ writeDurable(join(txnDir, saved), preimage);
359
+ operations.push({
360
+ index,
361
+ kind: operation.kind,
362
+ endpoints: journalEndpointsOf(operation),
363
+ staged,
364
+ preimage: saved,
365
+ });
366
+ }
367
+ fsyncDir(txnDir);
368
+ }
369
+ catch (cause) {
370
+ return {
371
+ ok: false,
372
+ code: "stage-failed",
373
+ message: `workspace staging failed before any mutation: ${cause instanceof Error ? cause.message : String(cause)}`,
374
+ };
375
+ }
376
+ const journal = {
377
+ version: WORKSPACE_COMMIT_VERSION,
378
+ root,
379
+ payload_hash: plan.payload_hash,
380
+ custody: custody.report.kind,
381
+ operations,
382
+ };
383
+ // --- Phase 2: the journal, durable, before anything moves. --------------
384
+ try {
385
+ writeDurable(journalPath(txnDir), Buffer.from(canonicalize(journal), "utf8"));
386
+ fsyncDir(txnDir);
387
+ }
388
+ catch (cause) {
389
+ return {
390
+ ok: false,
391
+ code: "stage-failed",
392
+ message: `workspace journal could not be made durable before any mutation: ${cause instanceof Error ? cause.message : String(cause)}`,
393
+ };
394
+ }
395
+ // --- Phase 3: apply. From here the answer comes from reading. -----------
396
+ let failure;
397
+ try {
398
+ options.onStep?.(0);
399
+ for (const entry of operations) {
400
+ const operation = plan.payload.operations[entry.index];
401
+ applyOne(root, txnDir, operation, entry);
402
+ options.onStep?.(entry.index + 1);
403
+ }
404
+ }
405
+ catch (cause) {
406
+ failure = cause instanceof Error ? cause.message : String(cause);
407
+ }
408
+ for (const directory of touchedDirectories(plan))
409
+ fsyncDir(directory);
410
+ // --- Phase 4: a failed apply tries to undo itself, and is then read. ----
411
+ if (failure !== undefined) {
412
+ try {
413
+ rollback(root, txnDir, operations);
414
+ }
415
+ catch (cause) {
416
+ failure = `${failure}; rollback also failed: ${cause instanceof Error ? cause.message : String(cause)}`;
417
+ }
418
+ for (const directory of touchedDirectories(plan))
419
+ fsyncDir(directory);
420
+ }
421
+ const inspection = inspectWorkspaceState(journal);
422
+ const retained = inspection.state === "mixed";
423
+ if (!retained) {
424
+ try {
425
+ unlinkSync(journalPath(txnDir));
426
+ fsyncDir(txnDir);
427
+ }
428
+ catch {
429
+ // Recovery reads a journal beside a settled workspace as settled.
430
+ }
431
+ }
432
+ return {
433
+ ok: true,
434
+ outcome: {
435
+ state: inspection.state,
436
+ inspection,
437
+ journal,
438
+ journalRetained: retained,
439
+ ...(failure === undefined ? {} : { failure }),
440
+ },
441
+ };
442
+ }
443
+ function applyOne(root, txnDir, operation, entry) {
444
+ switch (operation.kind) {
445
+ case "create":
446
+ case "replace":
447
+ renameSync(join(txnDir, entry.staged), resolve(root, operation.path));
448
+ return;
449
+ case "delete":
450
+ unlinkSync(resolve(root, operation.path));
451
+ return;
452
+ case "move":
453
+ renameSync(resolve(root, operation.from), resolve(root, operation.to));
454
+ return;
455
+ }
456
+ }
457
+ /**
458
+ * Put every endpoint back the way the journal says it was, from the staged
459
+ * preimages.
460
+ *
461
+ * Best effort by construction: it is attempted, and then the workspace is READ.
462
+ * A rollback that half worked produces `mixed` from the inspection, which is
463
+ * the same answer a rollback that was never attempted would produce, and that
464
+ * is deliberate — the classification must not depend on what this function
465
+ * thinks it achieved.
466
+ */
467
+ function rollback(root, txnDir, operations) {
468
+ const problems = [];
469
+ for (let index = operations.length - 1; index >= 0; index -= 1) {
470
+ const entry = operations[index];
471
+ try {
472
+ for (let slot = 0; slot < entry.endpoints.length; slot += 1) {
473
+ const endpoint = entry.endpoints[slot];
474
+ const target = resolve(root, endpoint.path);
475
+ if (endpoint.before_sha256 === null) {
476
+ try {
477
+ unlinkSync(target);
478
+ }
479
+ catch (cause) {
480
+ if (cause.code !== "ENOENT")
481
+ throw cause;
482
+ }
483
+ continue;
484
+ }
485
+ if (readbackDigest(target) === endpoint.before_sha256)
486
+ continue;
487
+ if (entry.preimage === null)
488
+ continue;
489
+ const restored = join(txnDir, `restore-${String(index)}-${String(slot)}`);
490
+ writeDurable(restored, readFileSync(join(txnDir, entry.preimage)));
491
+ renameSync(restored, target);
492
+ }
493
+ }
494
+ catch (cause) {
495
+ problems.push(cause instanceof Error ? cause.message : String(cause));
496
+ }
497
+ }
498
+ if (problems.length > 0)
499
+ throw new Error(problems.join("; "));
500
+ }
501
+ /**
502
+ * Read a workspace's retained journal and say which of the three states it is
503
+ * in. It CHANGES NOTHING in the workspace.
504
+ *
505
+ * That is the whole contract, and the restraint is the point: a recovery that
506
+ * rolled a mixed workspace forward would be guessing which half of it the
507
+ * human approved, and a recovery that rolled one back would be deleting the
508
+ * half that already committed. Both are the runtime inventing a fact. A proven
509
+ * `before` or `after` needs no repair by definition, so there is nothing left
510
+ * for this function to do but say so and let the operator clear the staging
511
+ * directory.
512
+ */
513
+ export function recoverWorkspaceCommit(root) {
514
+ const path = journalPath(join(root, WORKSPACE_TXN_DIR));
515
+ let raw;
516
+ try {
517
+ raw = readFileSync(path, "utf8");
518
+ }
519
+ catch (cause) {
520
+ if (cause.code === "ENOENT") {
521
+ return { ok: true, state: "none", message: `no workspace transaction journal at ${path}` };
522
+ }
523
+ return {
524
+ ok: false,
525
+ code: "journal-unreadable",
526
+ message: `workspace transaction journal ${path} could not be read: ${String(cause.code ?? cause)}`,
527
+ };
528
+ }
529
+ let journal;
530
+ try {
531
+ const parsed = JSON.parse(raw);
532
+ if (typeof parsed !== "object" || parsed === null ||
533
+ parsed.version !== WORKSPACE_COMMIT_VERSION ||
534
+ !Array.isArray(parsed.operations)) {
535
+ return { ok: false, code: "journal-unreadable", message: `${path} is not a v1 workspace transaction journal` };
536
+ }
537
+ journal = parsed;
538
+ }
539
+ catch (cause) {
540
+ return {
541
+ ok: false,
542
+ code: "journal-unreadable",
543
+ message: `${path} is not parseable JSON: ${cause instanceof Error ? cause.message : String(cause)}`,
544
+ };
545
+ }
546
+ const inspection = inspectWorkspaceState(journal);
547
+ return { ok: true, state: inspection.state, inspection, journal };
548
+ }
549
+ //# sourceMappingURL=workspace-commit.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"workspace-commit.js","sourceRoot":"","sources":["../../../src/codex/workspace-commit.ts"],"names":[],"mappings":"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"}
@@ -9,7 +9,9 @@
9
9
  * (`tests/layering.test.ts`). One home, so the three cannot disagree about
10
10
  * which records are an advance's own.
11
11
  */
12
+ import { ADVANCE_DAEMON_CLASS } from "./command-class.js";
12
13
  import type { EventRecord } from "./log.js";
14
+ import type { PolicyLoadResult } from "./policy-load.js";
13
15
  import { type RequestState } from "./state.js";
14
16
  /**
15
17
  * Who proposes an advance.
@@ -23,6 +25,55 @@ import { type RequestState } from "./state.js";
23
25
  export declare const ADVANCE_ACTOR = "agent:daemon";
24
26
  /** The class an advance is gated as. Declared, resolved, and never assumed. */
25
27
  export declare const ADVANCE_CLASS = "log.advance";
28
+ export { ADVANCE_DAEMON_CLASS };
29
+ /** Every class an advance cycle may be gated as, newest first. */
30
+ export declare const ADVANCE_CLASSES: readonly string[];
31
+ /** The refusal a non-daemon actor gets where the route would be autonomous. */
32
+ export declare const ADVANCE_ACTOR_REFUSAL = "advance-actor-not-daemon";
33
+ /** Which class this cycle asks under, or the refusal that stops it. */
34
+ export type AdvanceRoute = {
35
+ ok: true;
36
+ cls: string;
37
+ daemon: boolean;
38
+ } | {
39
+ ok: false;
40
+ code: typeof ADVANCE_ACTOR_REFUSAL;
41
+ message: string;
42
+ };
43
+ /**
44
+ * The class this advance asks under, decided by WHO is running it (APRV-382).
45
+ *
46
+ * Carter's 2026-09-19 question, answered as policy plus one check. An advance
47
+ * appends nothing and decides nothing: it publishes records the log already
48
+ * holds to a records branch, and the CI guard reads grants that exist whether
49
+ * or not an advance ran. So the DAEMON may make it unattended. Every other
50
+ * actor — a session in a worktree, a human terminal — stays where the class was
51
+ * declared at seq 513, because a lane that could publish the log could choose
52
+ * which records the world sees first.
53
+ *
54
+ * The policy grammar has no actor condition, so the rule is two classes and
55
+ * this function is the actor half of it. Three outcomes, and each one is the
56
+ * stricter reading of its case:
57
+ *
58
+ * 1. **The daemon, with a rule on {@link ADVANCE_DAEMON_CLASS}.** The daemon's
59
+ * own class, whatever autonomy the operator gave it.
60
+ * 2. **The daemon, with no such rule.** {@link ADVANCE_CLASS}, exactly as every
61
+ * build before this one. A policy that has not declared the daemon class has
62
+ * not decided anything about it, and resolving to the fail-closed default
63
+ * there would put a phone tap on every cadence advance between the day this
64
+ * code ships and the day the page is applied. Falling back to the class the
65
+ * operator DID write is the behaviour they attested to.
66
+ * 3. **Any other actor.** {@link ADVANCE_CLASS}, unless that class resolves
67
+ * `autonomous`, which is refused {@link ADVANCE_ACTOR_REFUSAL}. The
68
+ * autonomous route is the daemon's, so an actor that is not the daemon
69
+ * taking it is a policy that was loosened past what this repository decided,
70
+ * and the refusal says so rather than publishing the log unattended.
71
+ *
72
+ * PURE: the policy load is an argument and the actor answer is a boolean the
73
+ * caller read from `core/daemon-actor.ts`, so the whole rule is testable in
74
+ * both directions with no daemon, no git, and no clock.
75
+ */
76
+ export declare function advanceRoute(daemon: boolean, load: PolicyLoadResult): AdvanceRoute;
26
77
  /** The task id every advance cycle registers under, plus its head seq. */
27
78
  export declare const ADVANCE_TASK_PREFIX = "daemon-advance";
28
79
  /** The idempotency key prefix. One key per (published head → working head) span. */