approval-md 0.1.0 → 0.2.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 (266) hide show
  1. package/README.md +584 -553
  2. package/SPEC.md +42 -13
  3. package/dist/src/adapters/agentmail.d.ts +426 -0
  4. package/dist/src/adapters/agentmail.js +2 -2
  5. package/dist/src/adapters/conformance.d.ts +149 -0
  6. package/dist/src/adapters/contract.d.ts +628 -0
  7. package/dist/src/adapters/contract.js +110 -16
  8. package/dist/src/adapters/contract.js.map +1 -1
  9. package/dist/src/adapters/email.d.ts +324 -0
  10. package/dist/src/adapters/env-passphrase.d.ts +93 -0
  11. package/dist/src/adapters/public.d.ts +11 -0
  12. package/dist/src/adapters/public.js +11 -0
  13. package/dist/src/adapters/public.js.map +1 -0
  14. package/dist/src/adapters/registry.d.ts +59 -0
  15. package/dist/src/adapters/registry.js +2 -1
  16. package/dist/src/adapters/registry.js.map +1 -1
  17. package/dist/src/adapters/smtp.d.ts +213 -0
  18. package/dist/src/adapters/vault-provider.d.ts +114 -0
  19. package/dist/src/adapters/vault-provider.js +3 -3
  20. package/dist/src/adapters/zzz.d.ts +66 -0
  21. package/dist/src/adapters/zzz.js +299 -0
  22. package/dist/src/adapters/zzz.js.map +1 -0
  23. package/dist/src/channels/batch.d.ts +109 -0
  24. package/dist/src/channels/cli.d.ts +193 -0
  25. package/dist/src/channels/conformance.d.ts +92 -0
  26. package/dist/src/channels/contract.d.ts +623 -0
  27. package/dist/src/channels/payload-view.d.ts +35 -0
  28. package/dist/src/channels/render-queue.d.ts +149 -0
  29. package/dist/src/channels/tagging.d.ts +196 -0
  30. package/dist/src/channels/telegram.d.ts +1832 -0
  31. package/dist/src/channels/web.d.ts +341 -0
  32. package/dist/src/cli/adapter.d.ts +90 -0
  33. package/dist/src/cli/adapter.js +25 -15
  34. package/dist/src/cli/adapter.js.map +1 -1
  35. package/dist/src/cli/amend.d.ts +59 -0
  36. package/dist/src/cli/args.d.ts +43 -0
  37. package/dist/src/cli/attest.d.ts +41 -0
  38. package/dist/src/cli/audit-card.d.ts +62 -0
  39. package/dist/src/cli/audit.d.ts +59 -0
  40. package/dist/src/cli/channel-telegram.d.ts +806 -0
  41. package/dist/src/cli/channel-web.d.ts +131 -0
  42. package/dist/src/cli/channel.d.ts +71 -0
  43. package/dist/src/cli/checkpoint-tap.d.ts +169 -0
  44. package/dist/src/cli/codex.d.ts +2 -0
  45. package/dist/src/cli/codex.js +172 -0
  46. package/dist/src/cli/codex.js.map +1 -0
  47. package/dist/src/cli/coverage.d.ts +61 -0
  48. package/dist/src/cli/daemon.d.ts +120 -0
  49. package/dist/src/cli/doctor.d.ts +129 -0
  50. package/dist/src/cli/doctor.js +119 -5
  51. package/dist/src/cli/doctor.js.map +1 -1
  52. package/dist/src/cli/env.d.ts +65 -0
  53. package/dist/src/cli/execute.d.ts +202 -0
  54. package/dist/src/cli/exit-codes.d.ts +73 -0
  55. package/dist/src/cli/feedback.d.ts +60 -0
  56. package/dist/src/cli/gate-window.d.ts +40 -0
  57. package/dist/src/cli/gate.d.ts +68 -0
  58. package/dist/src/cli/git-scope.d.ts +190 -0
  59. package/dist/src/cli/gloss-attach.d.ts +85 -0
  60. package/dist/src/cli/gloss-codex-child.d.ts +9 -0
  61. package/dist/src/cli/gloss-codex.d.ts +24 -0
  62. package/dist/src/cli/gloss-options.d.ts +42 -0
  63. package/dist/src/cli/gloss.d.ts +265 -0
  64. package/dist/src/cli/help.d.ts +103 -0
  65. package/dist/src/cli/help.js +173 -51
  66. package/dist/src/cli/help.js.map +1 -1
  67. package/dist/src/cli/hook-codex.d.ts +78 -0
  68. package/dist/src/cli/hook-codex.js +167 -0
  69. package/dist/src/cli/hook-codex.js.map +1 -0
  70. package/dist/src/cli/hook.d.ts +331 -0
  71. package/dist/src/cli/hook.js +186 -80
  72. package/dist/src/cli/hook.js.map +1 -1
  73. package/dist/src/cli/import.d.ts +35 -0
  74. package/dist/src/cli/init.d.ts +84 -0
  75. package/dist/src/cli/init.js +2 -2
  76. package/dist/src/cli/init.js.map +1 -1
  77. package/dist/src/cli/instructions.d.ts +23 -0
  78. package/dist/src/cli/journal.d.ts +41 -0
  79. package/dist/src/cli/log-advance.d.ts +287 -0
  80. package/dist/src/cli/log-advance.js +102 -11
  81. package/dist/src/cli/log-advance.js.map +1 -1
  82. package/dist/src/cli/log-anchor.d.ts +176 -0
  83. package/dist/src/cli/log-checkpoint.d.ts +22 -0
  84. package/dist/src/cli/log-sync.d.ts +243 -0
  85. package/dist/src/cli/log-verbs.d.ts +16 -0
  86. package/dist/src/cli/log-verbs.js +7 -1
  87. package/dist/src/cli/log-verbs.js.map +1 -1
  88. package/dist/src/cli/long-help.d.ts +70 -0
  89. package/dist/src/cli/main.d.ts +77 -0
  90. package/dist/src/cli/main.js +155 -5
  91. package/dist/src/cli/main.js.map +1 -1
  92. package/dist/src/cli/mcp.d.ts +52 -0
  93. package/dist/src/cli/paths.d.ts +56 -0
  94. package/dist/src/cli/payload.d.ts +58 -0
  95. package/dist/src/cli/policy.d.ts +43 -0
  96. package/dist/src/cli/preflight.d.ts +363 -0
  97. package/dist/src/cli/preflight.js +294 -7
  98. package/dist/src/cli/preflight.js.map +1 -1
  99. package/dist/src/cli/progress.d.ts +78 -0
  100. package/dist/src/cli/prompt.d.ts +209 -0
  101. package/dist/src/cli/quickstart.d.ts +46 -0
  102. package/dist/src/cli/quickstart.js +297 -0
  103. package/dist/src/cli/quickstart.js.map +1 -0
  104. package/dist/src/cli/records.d.ts +34 -0
  105. package/dist/src/cli/render.d.ts +22 -0
  106. package/dist/src/cli/sandbox.d.ts +51 -0
  107. package/dist/src/cli/scaffold.d.ts +79 -0
  108. package/dist/src/cli/setup-adapter.d.ts +137 -0
  109. package/dist/src/cli/setup-adapter.js +38 -4
  110. package/dist/src/cli/setup-adapter.js.map +1 -1
  111. package/dist/src/cli/setup-channel.d.ts +117 -0
  112. package/dist/src/cli/setup-checkpoint.d.ts +57 -0
  113. package/dist/src/cli/setup-common.d.ts +275 -0
  114. package/dist/src/cli/setup-flow.d.ts +287 -0
  115. package/dist/src/cli/setup-service.d.ts +96 -0
  116. package/dist/src/cli/setup.d.ts +202 -0
  117. package/dist/src/cli/style.d.ts +320 -0
  118. package/dist/src/cli/token.d.ts +39 -0
  119. package/dist/src/cli/up.d.ts +155 -0
  120. package/dist/src/cli/up.js +4 -2
  121. package/dist/src/cli/up.js.map +1 -1
  122. package/dist/src/cli/usage.d.ts +37 -0
  123. package/dist/src/cli/values.d.ts +40 -0
  124. package/dist/src/cli/vault.d.ts +59 -0
  125. package/dist/src/cli/vault.js +2 -2
  126. package/dist/src/cli/vault.js.map +1 -1
  127. package/dist/src/cli/verb-registry.d.ts +76 -0
  128. package/dist/src/cli/verb-registry.js +176 -8
  129. package/dist/src/cli/verb-registry.js.map +1 -1
  130. package/dist/src/cli/wordmark.d.ts +31 -0
  131. package/dist/src/cli/wordmark.js +2 -2
  132. package/dist/src/codex/doctor.d.ts +13 -0
  133. package/dist/src/codex/doctor.js +41 -0
  134. package/dist/src/codex/doctor.js.map +1 -0
  135. package/dist/src/codex/manifest.d.ts +49 -0
  136. package/dist/src/codex/manifest.js +103 -0
  137. package/dist/src/codex/manifest.js.map +1 -0
  138. package/dist/src/codex/templates.d.ts +41 -0
  139. package/dist/src/codex/templates.js +319 -0
  140. package/dist/src/codex/templates.js.map +1 -0
  141. package/dist/src/codex/trust.d.ts +19 -0
  142. package/dist/src/codex/trust.js +183 -0
  143. package/dist/src/codex/trust.js.map +1 -0
  144. package/dist/src/codex/workspace-plan.d.ts +131 -0
  145. package/dist/src/codex/workspace-plan.js +561 -0
  146. package/dist/src/codex/workspace-plan.js.map +1 -0
  147. package/dist/src/core/actor.d.ts +2 -0
  148. package/dist/src/core/actor.js +5 -0
  149. package/dist/src/core/actor.js.map +1 -0
  150. package/dist/src/core/advance-cycle.d.ts +170 -0
  151. package/dist/src/core/agents-md.d.ts +276 -0
  152. package/dist/src/core/apply-patch.d.ts +49 -0
  153. package/dist/src/core/apply-patch.js +266 -0
  154. package/dist/src/core/apply-patch.js.map +1 -0
  155. package/dist/src/core/attest.d.ts +420 -0
  156. package/dist/src/core/attest.js +13 -1
  157. package/dist/src/core/attest.js.map +1 -1
  158. package/dist/src/core/audit.d.ts +492 -0
  159. package/dist/src/core/budgets.d.ts +238 -0
  160. package/dist/src/core/checkpoint.d.ts +500 -0
  161. package/dist/src/core/child-env.d.ts +88 -0
  162. package/dist/src/core/clock.d.ts +52 -0
  163. package/dist/src/core/command-class.d.ts +543 -0
  164. package/dist/src/core/command-class.js +43 -8
  165. package/dist/src/core/command-class.js.map +1 -1
  166. package/dist/src/core/coverage-sources/adapter.d.ts +40 -0
  167. package/dist/src/core/coverage-sources/gh.d.ts +48 -0
  168. package/dist/src/core/coverage-sources/git.d.ts +101 -0
  169. package/dist/src/core/coverage.d.ts +217 -0
  170. package/dist/src/core/credential-spec.d.ts +72 -0
  171. package/dist/src/core/dark-session.d.ts +331 -0
  172. package/dist/src/core/decision-refusal.d.ts +185 -0
  173. package/dist/src/core/env-file.d.ts +450 -0
  174. package/dist/src/core/execute.d.ts +858 -0
  175. package/dist/src/core/execute.js +44 -6
  176. package/dist/src/core/execute.js.map +1 -1
  177. package/dist/src/core/frontmatter.d.ts +78 -0
  178. package/dist/src/core/gate-window.d.ts +312 -0
  179. package/dist/src/core/gate.d.ts +1364 -0
  180. package/dist/src/core/gate.js +68 -13
  181. package/dist/src/core/gate.js.map +1 -1
  182. package/dist/src/core/git-run.d.ts +73 -0
  183. package/dist/src/core/harness-version.d.ts +157 -0
  184. package/dist/src/core/harness-version.js +2 -1
  185. package/dist/src/core/harness-version.js.map +1 -1
  186. package/dist/src/core/harness-wait.d.ts +55 -0
  187. package/dist/src/core/head-retry.d.ts +107 -0
  188. package/dist/src/core/instance.d.ts +253 -0
  189. package/dist/src/core/intake-limits.d.ts +247 -0
  190. package/dist/src/core/jcs.d.ts +52 -0
  191. package/dist/src/core/journal.d.ts +144 -0
  192. package/dist/src/core/live-draw.d.ts +436 -0
  193. package/dist/src/core/log-reconcile.d.ts +89 -0
  194. package/dist/src/core/log-subscribe.d.ts +36 -0
  195. package/dist/src/core/log-subscribe.js +162 -0
  196. package/dist/src/core/log-subscribe.js.map +1 -0
  197. package/dist/src/core/log.d.ts +278 -0
  198. package/dist/src/core/loop.d.ts +274 -0
  199. package/dist/src/core/loop.js +11 -0
  200. package/dist/src/core/loop.js.map +1 -1
  201. package/dist/src/core/md-fence.d.ts +41 -0
  202. package/dist/src/core/money.d.ts +147 -0
  203. package/dist/src/core/payload-census.d.ts +74 -0
  204. package/dist/src/core/payload-store.d.ts +175 -0
  205. package/dist/src/core/payload.d.ts +71 -0
  206. package/dist/src/core/policy-diff.d.ts +292 -0
  207. package/dist/src/core/policy-diff.js +27 -4
  208. package/dist/src/core/policy-diff.js.map +1 -1
  209. package/dist/src/core/policy-expectations.d.ts +199 -0
  210. package/dist/src/core/policy-explain.d.ts +150 -0
  211. package/dist/src/core/policy-explain.js +31 -3
  212. package/dist/src/core/policy-explain.js.map +1 -1
  213. package/dist/src/core/policy-load.d.ts +527 -0
  214. package/dist/src/core/policy-load.js +15 -3
  215. package/dist/src/core/policy-load.js.map +1 -1
  216. package/dist/src/core/policy-match.d.ts +281 -0
  217. package/dist/src/core/policy-match.js +20 -9
  218. package/dist/src/core/policy-match.js.map +1 -1
  219. package/dist/src/core/policy-proposal.d.ts +265 -0
  220. package/dist/src/core/prompt-layout.d.ts +221 -0
  221. package/dist/src/core/protected-path-guard.d.ts +453 -0
  222. package/dist/src/core/protected-path-guard.js +514 -35
  223. package/dist/src/core/protected-path-guard.js.map +1 -1
  224. package/dist/src/core/registration.d.ts +25 -0
  225. package/dist/src/core/reindex.d.ts +99 -0
  226. package/dist/src/core/sampler.d.ts +313 -0
  227. package/dist/src/core/sandbox.d.ts +290 -0
  228. package/dist/src/core/seal.d.ts +165 -0
  229. package/dist/src/core/state.d.ts +505 -0
  230. package/dist/src/core/task-file.d.ts +185 -0
  231. package/dist/src/core/telegram-config.d.ts +93 -0
  232. package/dist/src/core/token.d.ts +409 -0
  233. package/dist/src/core/token.js +21 -38
  234. package/dist/src/core/token.js.map +1 -1
  235. package/dist/src/core/validate.d.ts +138 -0
  236. package/dist/src/core/values.d.ts +137 -0
  237. package/dist/src/core/vault.d.ts +291 -0
  238. package/dist/src/core/verified-snapshot.d.ts +204 -0
  239. package/dist/src/core/verify.d.ts +336 -0
  240. package/dist/src/core/version.d.ts +8 -0
  241. package/dist/src/core/wysiwys.d.ts +370 -0
  242. package/dist/src/daemon/advance-child.d.ts +39 -0
  243. package/dist/src/daemon/advance.d.ts +466 -0
  244. package/dist/src/daemon/audit.d.ts +87 -0
  245. package/dist/src/daemon/daemon.d.ts +1180 -0
  246. package/dist/src/daemon/dark-session.d.ts +64 -0
  247. package/dist/src/daemon/draw-child.d.ts +36 -0
  248. package/dist/src/daemon/draw.d.ts +154 -0
  249. package/dist/src/daemon/git-evidence.d.ts +173 -0
  250. package/dist/src/daemon/git-evidence.js +1 -1
  251. package/dist/src/daemon/projection.d.ts +180 -0
  252. package/dist/src/daemon/prune.d.ts +207 -0
  253. package/dist/src/mcp/http.d.ts +113 -0
  254. package/dist/src/mcp/server.d.ts +265 -0
  255. package/dist/src/mcp/server.js +9 -1
  256. package/dist/src/mcp/server.js.map +1 -1
  257. package/docs/adapter-api.md +106 -0
  258. package/docs/cli-reference.md +389 -36
  259. package/docs/codex-enforced-session.md +30 -0
  260. package/package.json +12 -2
  261. package/schema/codex-instance.schema.json +82 -0
  262. package/schema/event.schema.json +2 -1
  263. package/schema/fixtures/codex-instance/invalid/unpinned-codex-version.json +40 -0
  264. package/schema/fixtures/codex-instance/valid/canonical.json +40 -0
  265. package/schema/policy.schema.json +21 -1
  266. package/templates/codex/README.md +9 -0
@@ -0,0 +1,162 @@
1
+ /**
2
+ * Pull-based subscription to the verified event log (APRV-322).
3
+ *
4
+ * A filesystem notification is only a hint that another read may be useful.
5
+ * Every emitted batch comes from one complete, genesis-to-head verification;
6
+ * a notification, a parsed line, or an intact prefix is never authority by
7
+ * itself. The iterator queues no events: while a consumer is slow it retains
8
+ * only the current verified snapshot and coalesces every wakeup into one bit.
9
+ */
10
+ import { readFileSync, watch } from "node:fs";
11
+ import { dirname } from "node:path";
12
+ import { verifyText } from "./verify.js";
13
+ /** A terminal subscription failure, mapped by the CLI to the existing exits. */
14
+ export class LogSubscriptionError extends Error {
15
+ kind;
16
+ reason;
17
+ constructor(kind, message, reason = null) {
18
+ super(message);
19
+ this.name = "LogSubscriptionError";
20
+ this.kind = kind;
21
+ this.reason = reason;
22
+ }
23
+ }
24
+ const DEFAULT_POLL_INTERVAL_MS = 500;
25
+ function readSnapshot(logPath) {
26
+ try {
27
+ return readFileSync(logPath, "utf8");
28
+ }
29
+ catch (cause) {
30
+ if (cause.code === "ENOENT")
31
+ return "";
32
+ const detail = cause instanceof Error ? cause.message : String(cause);
33
+ throw new LogSubscriptionError("io", `log ${logPath} could not be read: ${detail}`);
34
+ }
35
+ }
36
+ function checkedSnapshot(logPath) {
37
+ const verified = verifyText(logPath, readSnapshot(logPath));
38
+ if (verified.result.status === "torn-tail") {
39
+ throw new LogSubscriptionError("torn-tail", verified.result.message);
40
+ }
41
+ if (verified.result.status === "corrupt") {
42
+ throw new LogSubscriptionError("integrity", verified.result.message, verified.result.reason);
43
+ }
44
+ return verified.records;
45
+ }
46
+ function validateOptions(options) {
47
+ const from = options.from ?? 0;
48
+ if (!Number.isSafeInteger(from) || from < 0) {
49
+ throw new TypeError(`from must be a non-negative safe integer, got ${String(from)}`);
50
+ }
51
+ if (options.expectedHash !== undefined) {
52
+ if (from === 0)
53
+ throw new TypeError("expectedHash requires from to be greater than zero");
54
+ if (!/^[a-f0-9]{64}$/u.test(options.expectedHash)) {
55
+ throw new TypeError("expectedHash must be a lowercase 64-character SHA-256 digest");
56
+ }
57
+ }
58
+ const pollIntervalMs = options.pollIntervalMs ?? DEFAULT_POLL_INTERVAL_MS;
59
+ if (!Number.isSafeInteger(pollIntervalMs) || pollIntervalMs < 1) {
60
+ throw new TypeError(`pollIntervalMs must be a positive safe integer, got ${String(pollIntervalMs)}`);
61
+ }
62
+ return { from, expectedHash: options.expectedHash, pollIntervalMs };
63
+ }
64
+ /**
65
+ * Yield verified records after an exclusive cursor, then wait for appends.
66
+ *
67
+ * The optional expected hash binds the first read to the caller's stored
68
+ * cursor. After every yield, the iterator carries that same binding forward,
69
+ * so truncation or replacement during one process lifetime is also refused.
70
+ */
71
+ export async function* subscribeVerifiedLog(logPath, options = {}) {
72
+ const parsed = validateOptions(options);
73
+ let cursorSeq = parsed.from;
74
+ let cursorHash = parsed.expectedHash;
75
+ let wakeVersion = 0;
76
+ let wake = null;
77
+ let watcher = null;
78
+ let timer = null;
79
+ const hint = () => {
80
+ wakeVersion += 1;
81
+ const pending = wake;
82
+ wake = null;
83
+ pending?.();
84
+ };
85
+ const abort = () => hint();
86
+ options.signal?.addEventListener("abort", abort, { once: false });
87
+ // Watch the directory so creation of an initially absent log is visible.
88
+ // Failure to establish a watch is harmless: bounded polling remains active.
89
+ try {
90
+ watcher = watch(dirname(logPath), { persistent: false }, hint);
91
+ watcher.on("error", hint);
92
+ }
93
+ catch {
94
+ watcher = null;
95
+ }
96
+ try {
97
+ while (!options.signal?.aborted) {
98
+ const versionBeforeRead = wakeVersion;
99
+ const records = checkedSnapshot(logPath);
100
+ const headSeq = records.at(-1)?.seq ?? 0;
101
+ if (cursorSeq > headSeq) {
102
+ throw new LogSubscriptionError("integrity", `log ${logPath} ends at seq ${String(headSeq)}, before subscription cursor seq ${String(cursorSeq)}: records have been removed or this cursor belongs to another log`, "cursor-mismatch");
103
+ }
104
+ if (cursorSeq > 0) {
105
+ const actual = records[cursorSeq - 1];
106
+ if (actual === undefined || (cursorHash !== undefined && actual.hash !== cursorHash)) {
107
+ throw new LogSubscriptionError("integrity", `log ${logPath} does not match subscription cursor seq ${String(cursorSeq)}${cursorHash === undefined ? "" : ` hash ${cursorHash}`}: the retained prefix was truncated, replaced, or belongs to another log`, "cursor-mismatch");
108
+ }
109
+ // A sequence-only bootstrap is weaker on its first read, by design,
110
+ // but once this process has verified that prefix it can retain the
111
+ // actual digest and detect any later replacement even while caught up.
112
+ cursorHash ??= actual.hash;
113
+ }
114
+ // No slice: it would duplicate an arbitrarily large suffix. The verified
115
+ // snapshot is the only event-bearing allocation retained by this batch.
116
+ for (let index = cursorSeq; index < records.length; index += 1) {
117
+ if (options.signal?.aborted)
118
+ return;
119
+ const record = records[index];
120
+ if (record === undefined)
121
+ break;
122
+ // The yielded object is ordinary mutable JavaScript. Capture the
123
+ // verified cursor before handing it to untrusted consumer code, so a
124
+ // mutation cannot change where this iterator resumes or what prefix it
125
+ // binds on the next verification.
126
+ const verifiedSeq = record.seq;
127
+ const verifiedHash = record.hash;
128
+ yield record;
129
+ cursorSeq = verifiedSeq;
130
+ cursorHash = verifiedHash;
131
+ }
132
+ if (options.signal?.aborted)
133
+ return;
134
+ // An append between watcher setup/read/drain and this point changes the
135
+ // version and causes an immediate verification instead of a lost wakeup.
136
+ if (wakeVersion !== versionBeforeRead)
137
+ continue;
138
+ const versionBeforeWait = wakeVersion;
139
+ await new Promise((resolve) => {
140
+ wake = resolve;
141
+ timer = setTimeout(hint, parsed.pollIntervalMs);
142
+ // Close the last race: an abort or append may land after the check
143
+ // above and before `wake` is installed. Its version change must turn
144
+ // into an immediate retry, not a wait for the polling fallback.
145
+ if (options.signal?.aborted || wakeVersion !== versionBeforeWait)
146
+ hint();
147
+ });
148
+ if (timer !== null)
149
+ clearTimeout(timer);
150
+ timer = null;
151
+ wake = null;
152
+ }
153
+ }
154
+ finally {
155
+ if (timer !== null)
156
+ clearTimeout(timer);
157
+ wake = null;
158
+ watcher?.close();
159
+ options.signal?.removeEventListener("abort", abort);
160
+ }
161
+ }
162
+ //# sourceMappingURL=log-subscribe.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"log-subscribe.js","sourceRoot":"","sources":["../../../src/core/log-subscribe.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,278 @@
1
+ /**
2
+ * Append-only event log writer (SPEC.md §8, `.approval/log/events.jsonl`).
3
+ *
4
+ * The log is the truth. This module is the *only* sanctioned way to put a line
5
+ * into it, and its public API deliberately exposes no mutation, reorder,
6
+ * rewrite, or truncate operation — there is nothing here to call that could
7
+ * disturb an existing byte. Reading, verifying, and projecting live elsewhere.
8
+ *
9
+ * Guarantees, in the order they are enforced by {@link appendEvent}:
10
+ *
11
+ * 1. **Exclusive access.** A dependency-free advisory lockfile (`<log>.lock`,
12
+ * created `wx`) serializes the read-tail → compute → write sequence, so two
13
+ * concurrent appenders cannot both read seq N and both write seq N+1.
14
+ * 1b. **Compare-and-append (APRV-20 finding B1).** The lock serializes *writes*,
15
+ * but every caller that checks the log before appending — "no live request
16
+ * exists", "this token is unspent", "the budget has room" — made that check
17
+ * outside the lock, against a log that could have moved on before its own
18
+ * append took the lock. {@link AppendOptions.expectedHead} closes that
19
+ * window: the caller states the `(seq, hash)` it read, this module compares
20
+ * it against the actual tail **under the lock**, and refuses `head-moved`
21
+ * when they differ. Nothing is written. The refusal is deliberately *not*
22
+ * retried here: only the caller knows whether re-deriving its decision
23
+ * against the newer log is safe, so the core reports and stops.
24
+ * 2. **Refuse to build on a corrupt tail.** If the file's last line is
25
+ * truncated (no terminating newline) or unparseable, the append is
26
+ * rejected. Chaining onto a half-written record would bake the corruption
27
+ * into every subsequent hash. Since APRV-206 that tail is read from the END
28
+ * of the file rather than by reading the whole log: an append needs the last
29
+ * line and nothing else, and paying one full read of an ever-growing file per
30
+ * append put log length into the latency of every grant. Every refusal above
31
+ * is unchanged, byte for byte.
32
+ * 3. **The runtime stamps the chain fields.** Callers supply content only;
33
+ * `seq`, `prev`, `alg`, and `hash` are computed here. `alg` is always
34
+ * `sha256/jcs` (SPEC.md §8 defines exactly one value at v0.1).
35
+ * 4. **Validate at the write boundary.** The complete record — chain fields
36
+ * included — must pass the `event` JSON Schema before any byte is written.
37
+ * On failure the file is left byte-identical and a structured error is
38
+ * returned. Fail closed: nothing here throws a validation problem past the
39
+ * caller as a partially-written line.
40
+ * 5. **One line, one write.** The stored line is the JCS canonicalization of
41
+ * the complete record (`hash` included); the digest input is that same
42
+ * canonicalization minus the `hash` field. One scheme, two inputs — a
43
+ * verifier strips `hash` and re-derives. Appended with a single `write(2)`
44
+ * on a handle opened `O_APPEND`.
45
+ *
46
+ * Determinism: `ts` is supplied by the caller. This module never reads the
47
+ * clock, because a hash-relevant field sourced from ambient state would make
48
+ * the log irreproducible.
49
+ */
50
+ import { type ValidateOptions, type ValidationError } from "./validate.js";
51
+ /** SPEC.md §8: the hash scheme identifier stamped on every v0.1 record. */
52
+ export declare const ALG = "sha256/jcs";
53
+ /**
54
+ * SPEC.md §8: `prev` of the first record in a log. `null`, not a zero digest —
55
+ * the schema and the `genesis-null-prev` fixture both encode this choice.
56
+ */
57
+ export declare const GENESIS_PREV: null;
58
+ /**
59
+ * The closed set of event types (SPEC.md §8, mirrored by the schema enum).
60
+ *
61
+ * `payload.pruned` (APRV-38) is the first addition after the v0.1 draft set of
62
+ * sixteen: the daemon appends one per payload file it removes under
63
+ * `payload_retention`, so a log states what its payload store no longer holds.
64
+ * `approval.withdrawn` (APRV-106) is the second: the requester's retraction of
65
+ * a request nobody has answered yet (amended SPEC.md §6.3).
66
+ * `execution.indeterminate` and `execution.reconciled` (APRV-120) are the third
67
+ * and fourth: a side effect that was attempted and whose outcome is unknown, and
68
+ * the human resolution that later says which it was (amended SPEC.md §6.3,
69
+ * §10.4).
70
+ * `reconciliation.required` / `reconciliation.satisfied` (APRV-127) are the
71
+ * fifth and sixth: the obligation a retrospective DENIAL creates, and the
72
+ * human act that discharges it (amended SPEC.md §5.2).
73
+ * `policy.proposed` / `policy.declined` (APRV-109) are the seventh and eighth:
74
+ * an agent asking a human to attest prepared policy bytes, and the human's
75
+ * refusal (amended SPEC.md §10.1, §10.3). The acceptance is `policy.updated`,
76
+ * which already existed and is still the only event an attestation is.
77
+ * `audit.dark_session` (APRV-192) is the ninth: the daemon recording git
78
+ * activity it can see for which the log carries no corresponding record
79
+ * (amended SPEC.md §10.2). It is an observation the RUNTIME makes about a
80
+ * session, never a record written on a session's behalf, so it carries a
81
+ * `system:` actor and the schema refuses any other.
82
+ * `gate.opened`, `gate.closed` and `gate.bypassed` (APRV-214) are the tenth,
83
+ * eleventh and twelfth: a human time-boxing a full harness bypass so the gate
84
+ * itself can be debugged, that human closing it early, and one gated tool call
85
+ * the hook allowed while it stood (amended SPEC.md §5.2). The window's whole
86
+ * state is these records, deliberately: a file the runtime read on its own
87
+ * would let anything able to write it act as the human. The first two carry a
88
+ * `human:` actor and the schema refuses any other.
89
+ * `log.checkpoint` (APRV-220) is the thirteenth: a human's signature over the
90
+ * chain head at a moment, made with a key no agent process holds. The chain is
91
+ * unkeyed, so a party with write access to this file can truncate it and
92
+ * recompute a self-consistent forgery; a checkpoint is a witness that survives
93
+ * them, because a rewritten chain cannot reproduce a signature over the hashes
94
+ * it replaced. Human actor, for the reason `gate.opened` carries one.
95
+ * `audit.decision_refused` (APRV-235) is the fourteenth: a human decided
96
+ * through a channel or the CLI and the gate refused to record the decision, so
97
+ * the log states that the answer was given and could not be taken (amended
98
+ * SPEC.md §5.2). Audit tier — it grants nothing, and no verdict, budget, streak
99
+ * or sampling path reads it. `system:` actor, like `audit.dark_session`: it is
100
+ * the runtime's statement about its own refusal, and the human whose decision
101
+ * it was is named in the payload. Refusals handed to AGENTS are not recorded;
102
+ * the asymmetry is deliberate, and `core/decision-refusal.ts` states why.
103
+ * `gate.organ.attested` (APRV-272) is the fifteenth: a human's sign-off on the
104
+ * exact bytes of one of the gate's ORGANS, the harness files that install the
105
+ * hook (amended SPEC.md §5.2, §8). Those files are `policy.core`, which is
106
+ * human-only, so the gate mints no record of any kind for them and no
107
+ * grant-shaped evidence for a hand edit to one can exist; content attestation
108
+ * is the only evidence there is, and this is it. `human:` actor and the schema
109
+ * refuses any other, for the reason `gate.opened` carries the same rule.
110
+ *
111
+ * It is a type of its own rather than a `policy.updated` variant, and that is
112
+ * the load-bearing choice: every reader of the POLICY attestation selects on
113
+ * `event === "policy.updated"` (`core/attest.ts`, `core/policy-proposal.ts`,
114
+ * `cli/amend.ts`, `cli/channel-telegram.ts`, `core/protected-path-guard.ts`),
115
+ * so a distinct type is ignored by all of them by construction rather than by a
116
+ * filter each one would have to remember. `checkAttestationOfBytes` returns at
117
+ * the FIRST record carrying a `sha256` as it scans backwards, so an organ
118
+ * attestation written as a `policy.updated` would have reported a correctly
119
+ * attested policy as `hash-mismatch` and refused every gate operation until
120
+ * somebody re-attested it. The `gate.` prefix already carries the write-boundary
121
+ * clock in `core/verify.ts`, which is what an attestation's `ts` has to be.
122
+ */
123
+ export type EventType = "task.registered" | "route.proposed" | "route.accepted" | "approval.requested" | "approval.granted" | "approval.rejected" | "approval.expired" | "approval.revoked" | "approval.withdrawn" | "execution.started" | "execution.completed" | "execution.failed" | "execution.indeterminate" | "execution.reconciled" | "budget.exceeded" | "policy.updated" | "policy.proposed" | "policy.declined" | "envelope.drift" | "audit.sampled" | "audit.reviewed" | "audit.dark_session" | "audit.decision_refused" | "reconciliation.required" | "reconciliation.satisfied" | "payload.pruned" | "gate.opened" | "gate.closed" | "gate.bypassed" | "gate.organ.attested" | "log.checkpoint";
124
+ /** Caller-supplied content of an event. Chain fields are not accepted. */
125
+ export interface EventInput {
126
+ /** RFC 3339 timestamp. Supplied by the caller; never read from the clock. */
127
+ ts: string;
128
+ event: EventType;
129
+ /** `human:`, `agent:`, or `system:` prefixed identity (SPEC.md §8). */
130
+ actor: string;
131
+ task?: string;
132
+ action_key?: string;
133
+ channel?: string;
134
+ payload?: Record<string, unknown>;
135
+ }
136
+ /** A complete log record: caller content plus runtime-stamped chain fields. */
137
+ export interface EventRecord extends EventInput {
138
+ seq: number;
139
+ alg: typeof ALG;
140
+ hash: string;
141
+ prev: string | null;
142
+ }
143
+ /** The hash input: a record with every field except `hash`. */
144
+ export type UnhashedRecord = Omit<EventRecord, "hash">;
145
+ /**
146
+ * Why an append was refused. Every failure is one of these, never a throw.
147
+ *
148
+ * Frozen public API in the same sense the gate's refusal codes are: callers
149
+ * branch on these strings, so adding one is a spec change and renaming one is a
150
+ * breaking change. `head-moved` is the APRV-20 addition (finding B1), sanctioned
151
+ * by the human decision of 2026-08-07.
152
+ */
153
+ export declare const APPEND_ERROR_CODES: readonly [
154
+ /** The lockfile was held by another writer for longer than the timeout. */
155
+ "lock-timeout",
156
+ /** The file's last line is truncated or unparseable; nothing may chain onto it. */
157
+ "corrupt-tail",
158
+ /** The complete record failed the `event` schema at the write boundary. */
159
+ "validation",
160
+ /** The record could not be canonicalized (RFC 8785). */
161
+ "canonicalization",
162
+ /** The log could not be created, opened, or written. */
163
+ "io",
164
+ /**
165
+ * The caller supplied {@link AppendOptions.expectedHead} and the log's actual
166
+ * tail, read under the lock, is a different `(seq, hash)`. Someone appended
167
+ * between the caller's read and this append, so every read-dependent check the
168
+ * caller made is stale. Nothing was written.
169
+ */
170
+ "head-moved"];
171
+ export type AppendErrorCode = (typeof APPEND_ERROR_CODES)[number];
172
+ export interface AppendError {
173
+ code: AppendErrorCode;
174
+ message: string;
175
+ /** Schema errors, present when `code` is "validation". */
176
+ errors?: ValidationError[];
177
+ }
178
+ export type AppendResult = {
179
+ ok: true;
180
+ record: EventRecord;
181
+ line: string;
182
+ } | {
183
+ ok: false;
184
+ error: AppendError;
185
+ };
186
+ /**
187
+ * A chain head: the last record's position and digest.
188
+ *
189
+ * Defined here rather than in `core/verify.ts` because the writer needs it for
190
+ * {@link AppendOptions.expectedHead} and the writer cannot import the verifier
191
+ * (the verifier imports the writer). `core/verify.ts` re-exports this exact
192
+ * type, so there is one definition and one meaning.
193
+ */
194
+ export interface LogHead {
195
+ seq: number;
196
+ hash: string;
197
+ }
198
+ /** Options for {@link appendEvent}. */
199
+ export interface AppendOptions extends ValidateOptions {
200
+ /** Milliseconds to keep retrying the lockfile before giving up. */
201
+ lockTimeoutMs?: number;
202
+ /** Milliseconds between lock acquisition attempts. */
203
+ lockRetryMs?: number;
204
+ /**
205
+ * Compare-and-append precondition (guarantee 1b in the module header).
206
+ *
207
+ * - a `LogHead` — the append proceeds only if the log's tail is exactly that
208
+ * `(seq, hash)`;
209
+ * - `null` — the append proceeds only if the log is empty or absent;
210
+ * - absent/`undefined` — no precondition, the pre-APRV-20 behavior.
211
+ *
212
+ * Evaluated **under the lock**, after the tail is read and before anything is
213
+ * computed or written. A mismatch is `head-moved` and writes nothing. Callers
214
+ * that made a decision from the log MUST pass the head they read; callers with
215
+ * no read-dependent decision (an unconditional append) legitimately omit it.
216
+ */
217
+ expectedHead?: LogHead | null;
218
+ }
219
+ /** How long a whole-operation lock holder waits before reporting `lock-timeout`. */
220
+ export interface LockOptions {
221
+ lockTimeoutMs?: number;
222
+ lockRetryMs?: number;
223
+ }
224
+ /** The outcome of {@link withAppendLock}: the callback's value, or a refusal. */
225
+ export type LockedResult<T> = {
226
+ ok: true;
227
+ value: T;
228
+ } | {
229
+ ok: false;
230
+ error: AppendError;
231
+ };
232
+ /**
233
+ * The record's digest under `alg: "sha256/jcs"`.
234
+ *
235
+ * Hash input = **the full record minus its `hash` property**, with `prev`
236
+ * included, serialized per RFC 8785 (JCS) and digested with SHA-256. Output is
237
+ * lowercase hex. Passing an already-hashed record is fine: the `hash` field is
238
+ * removed before canonicalization, so `computeRecordHash(r)` is stable whether
239
+ * or not `r` carries a digest.
240
+ */
241
+ export declare function computeRecordHash(record: UnhashedRecord | EventRecord): string;
242
+ /**
243
+ * Inverse of {@link computeRecordHash}: recompute and compare. Lives beside
244
+ * the writer so the two can never diverge; the M1 chain verifier consumes it.
245
+ */
246
+ export declare function verifyRecordHash(record: EventRecord): boolean;
247
+ /** The stored line for a record: its canonical serialization (no newline). */
248
+ export declare function serializeRecord(record: EventRecord): string;
249
+ /**
250
+ * Append exactly one event to `logPath`, stamping `seq`, `prev`, `alg`, and
251
+ * `hash`.
252
+ *
253
+ * Returns a structured result rather than throwing: an append that cannot be
254
+ * made safely leaves the file byte-identical and says why.
255
+ *
256
+ * Pass {@link AppendOptions.expectedHead} whenever the append is authorized by
257
+ * something read from the log: the head is compared under the lock and a moved
258
+ * head refuses `head-moved` without writing.
259
+ */
260
+ /**
261
+ * Hold `<logPath>.lock` for the whole of `run`, then release it.
262
+ *
263
+ * The lockfile exists so that a read-tail → compute → write sequence cannot
264
+ * interleave with another writer's. Some operations need that exclusion over a
265
+ * span much longer than one append: `approval log sync` (APRV-125) reads the
266
+ * chain, moves the file aside, lets git advance the committed baseline, and
267
+ * puts the chain back, and an append landing anywhere inside that window is
268
+ * exactly the interleaving that forked this repository's own log on 2026-08-20.
269
+ *
270
+ * The callback is handed no lock handle and no write primitive: this module
271
+ * still exposes nothing that mutates an existing byte, and a caller holding the
272
+ * lock has the same append-only API everyone else has. What it gains is the
273
+ * guarantee that nobody else is appending while it works.
274
+ */
275
+ export declare function withAppendLock<T>(logPath: string, run: () => T, options?: LockOptions): LockedResult<T>;
276
+ /** Subscribe to successful appends. Process-wide, memory-only, additive. */
277
+ export declare function onLogAppended(listener: (logPath: string) => void): void;
278
+ export declare function appendEvent(logPath: string, input: EventInput, options?: AppendOptions): AppendResult;