approval-md 0.4.3 → 0.4.6

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 (52) hide show
  1. package/SPEC.md +13 -2
  2. package/dist/src/channels/telegram.d.ts +105 -31
  3. package/dist/src/channels/telegram.js +157 -81
  4. package/dist/src/channels/telegram.js.map +1 -1
  5. package/dist/src/cli/channel-telegram.d.ts +100 -35
  6. package/dist/src/cli/channel-telegram.js +216 -119
  7. package/dist/src/cli/channel-telegram.js.map +1 -1
  8. package/dist/src/cli/doctor.js +33 -13
  9. package/dist/src/cli/doctor.js.map +1 -1
  10. package/dist/src/cli/help.js +3 -3
  11. package/dist/src/cli/hook-exec-code.d.ts +150 -0
  12. package/dist/src/cli/hook-exec-code.js +357 -0
  13. package/dist/src/cli/hook-exec-code.js.map +1 -0
  14. package/dist/src/cli/hook.d.ts +125 -1
  15. package/dist/src/cli/hook.js +2177 -107
  16. package/dist/src/cli/hook.js.map +1 -1
  17. package/dist/src/cli/wordmark.d.ts +2 -2
  18. package/dist/src/cli/wordmark.js +2 -2
  19. package/dist/src/core/command-class.d.ts +368 -1
  20. package/dist/src/core/command-class.js +2830 -93
  21. package/dist/src/core/command-class.js.map +1 -1
  22. package/dist/src/core/date-argv.d.ts +83 -0
  23. package/dist/src/core/date-argv.js +212 -0
  24. package/dist/src/core/date-argv.js.map +1 -0
  25. package/dist/src/core/exec-code.d.ts +77 -0
  26. package/dist/src/core/exec-code.js +60 -0
  27. package/dist/src/core/exec-code.js.map +1 -0
  28. package/dist/src/core/git-push-argv.d.ts +111 -0
  29. package/dist/src/core/git-push-argv.js +375 -0
  30. package/dist/src/core/git-push-argv.js.map +1 -0
  31. package/dist/src/core/harness-version.d.ts +71 -30
  32. package/dist/src/core/harness-version.js +101 -34
  33. package/dist/src/core/harness-version.js.map +1 -1
  34. package/dist/src/core/policy-load.d.ts +8 -0
  35. package/dist/src/core/policy-load.js.map +1 -1
  36. package/dist/src/core/read-scope.js +1 -0
  37. package/dist/src/core/read-scope.js.map +1 -1
  38. package/dist/src/core/sysinfo-argv.d.ts +48 -0
  39. package/dist/src/core/sysinfo-argv.js +340 -0
  40. package/dist/src/core/sysinfo-argv.js.map +1 -0
  41. package/dist/src/daemon/git-evidence.d.ts +2 -2
  42. package/dist/src/daemon/git-evidence.js +1 -1
  43. package/docs/README-extended.md +3 -2
  44. package/docs/cli-reference.md +20 -7
  45. package/package.json +2 -2
  46. package/schema/event.schema.json +1 -1
  47. package/schema/fixtures/policy/invalid/exec-code-custody-boolean.json +11 -0
  48. package/schema/fixtures/policy/invalid/exec-code-execute-code-value.json +11 -0
  49. package/schema/fixtures/policy/invalid/exec-code-unknown-key.json +12 -0
  50. package/schema/fixtures/policy/valid/exec-code-both.json +17 -0
  51. package/schema/fixtures/policy/valid/exec-code-custody.json +11 -0
  52. package/schema/policy.schema.json +23 -0
@@ -0,0 +1,340 @@
1
+ /**
2
+ * The system-information readers' safe forms (APRV-508).
3
+ *
4
+ * `uptime`, `whoami`, `hostname`, `df`, `free`, `seq` and `paste` each print
5
+ * something and change nothing in the forms below, and some of them change
6
+ * something in forms that look almost the same: `hostname NAME` and `hostname
7
+ * -F FILE` SET the host name, `free -s N` loops forever, `uptime FILE` reads a
8
+ * utmp file. So each binary gets a STRICT allow-grammar rather than a seat in
9
+ * the read table:
10
+ *
11
+ * - every flag is matched by its exact spelling. No abbreviation (getopt and
12
+ * clap both accept a unique prefix of a long option), no cluster except the
13
+ * ones a table below lists word for word, no `=` joined to a short flag;
14
+ * - `--` is never accepted;
15
+ * - an operand is accepted only where the binary's grammar names one, and
16
+ * only in the shape it names (a number for `seq`, a file for `paste`);
17
+ * - a word carrying `$`, a backtick, a glob or a brace is never accepted,
18
+ * since the shell may expand it into words this grammar never saw;
19
+ * - nothing else. An argv this file does not recognise answers `false`, and
20
+ * the classifier row does not match, so the command keeps the
21
+ * `unclassified` refusal it had before the row existed.
22
+ *
23
+ * Two implementations are read at once: GNU (coreutils, procps, net-tools
24
+ * `hostname`) and uutils coreutils 0.10, which the hosted boxes run. Where a
25
+ * spelling exists in one and not the other, the other errors out on it, which
26
+ * is a refusal by the tool rather than a different act.
27
+ */
28
+ /**
29
+ * Characters the shell may expand into more words, or into other words. A
30
+ * leading `~` is not among them: it expands to one path and never splits, and
31
+ * the read scope already judges a `~` path as one it cannot place.
32
+ */
33
+ const UNREADABLE_WORD = /[$`*?[\]{}]/u;
34
+ /** No word of this argv is one the shell may expand. */
35
+ function literal(args) {
36
+ return !args.some((arg) => UNREADABLE_WORD.test(arg));
37
+ }
38
+ /**
39
+ * The option each flag word sets, so that two spellings of one option count
40
+ * as a repeat (`-s --serial`, `-h --human-readable`, `-h -hT`). A word not in
41
+ * the map is its own option.
42
+ */
43
+ const SAME_OPTION = {
44
+ "--human-readable": ["-h"],
45
+ "--si": ["-H"],
46
+ "--print-type": ["-T"],
47
+ "--inodes": ["-i"],
48
+ "--all": ["-a"],
49
+ "--local": ["-l"],
50
+ "--portability": ["-P"],
51
+ "-hT": ["-h", "-T"],
52
+ "-Th": ["-h", "-T"],
53
+ "-hi": ["-h", "-i"],
54
+ "-ih": ["-h", "-i"],
55
+ "-hl": ["-h", "-l"],
56
+ "-lh": ["-h", "-l"],
57
+ "--bytes": ["-b"],
58
+ "--kibi": ["-k"],
59
+ "--mebi": ["-m"],
60
+ "--gibi": ["-g"],
61
+ "--human": ["-h"],
62
+ "--lohi": ["-l"],
63
+ "--total": ["-t"],
64
+ "--wide": ["-w"],
65
+ "--committed": ["-v"],
66
+ "-ht": ["-h", "-t"],
67
+ "-th": ["-h", "-t"],
68
+ "-hw": ["-h", "-w"],
69
+ "-wh": ["-h", "-w"],
70
+ "--equal-width": ["-w"],
71
+ "--separator": ["-s"],
72
+ "--format": ["-f"],
73
+ "--serial": ["-s"],
74
+ "--zero-terminated": ["-z"],
75
+ };
76
+ /**
77
+ * No option is given twice, in any spelling. clap (uutils) refuses a repeated
78
+ * option where getopt takes the last one, so the grammar admits neither.
79
+ * `-dX` / `--delimiters=X` and `--separator=X` / `--format=X` count by name.
80
+ */
81
+ function noRepeatedFlag(args, valueWords = []) {
82
+ const seen = new Set();
83
+ for (let index = 0; index < args.length; index += 1) {
84
+ const arg = args[index];
85
+ if (!arg.startsWith("-") || arg === "-" || /^-[\d.]/u.test(arg))
86
+ continue;
87
+ let name = arg;
88
+ if (arg.startsWith("--delimiters=") || (arg.startsWith("-d") && !arg.startsWith("--")))
89
+ name = "-d";
90
+ else if (arg.startsWith("--separator="))
91
+ name = "-s";
92
+ else if (arg.startsWith("--format="))
93
+ name = "-f";
94
+ for (const option of SAME_OPTION[name] ?? [name]) {
95
+ if (seen.has(option))
96
+ return false;
97
+ seen.add(option);
98
+ }
99
+ // A separate-word value is not a flag, even when it looks like one.
100
+ if (valueWords.includes(arg))
101
+ index += 1;
102
+ }
103
+ return true;
104
+ }
105
+ /** `--help` or `--version`, alone. */
106
+ function isHelpOrVersion(args) {
107
+ return args.length === 1 && (args[0] === "--help" || args[0] === "--version");
108
+ }
109
+ /** `uptime` and `whoami`: nothing, or `--help` / `--version`. */
110
+ export function isBareInfo(args) {
111
+ return args.length === 0 || isHelpOrVersion(args);
112
+ }
113
+ /**
114
+ * `hostname`: nothing, or ONE display flag. Any operand sets the host name,
115
+ * and `-F`/`--file`/`-b`/`--boot` set it from a file or at boot, so none of
116
+ * them is in the list.
117
+ */
118
+ const HOSTNAME_FLAGS = [
119
+ "-s",
120
+ "--short",
121
+ "-f",
122
+ "--fqdn",
123
+ "--long",
124
+ "-d",
125
+ "--domain",
126
+ "-i",
127
+ "--ip-address",
128
+ "-I",
129
+ "--all-ip-addresses",
130
+ "--help",
131
+ "--version",
132
+ ];
133
+ export function isHostnameDisplay(args) {
134
+ if (args.length === 0)
135
+ return true;
136
+ return args.length === 1 && HOSTNAME_FLAGS.includes(args[0]);
137
+ }
138
+ /**
139
+ * `df`: display flags only. A path operand would be a read the read scope
140
+ * judges, and the agents asking need the mount table, so operands are not in
141
+ * the grammar. `--output` and the type filters are left out with them.
142
+ */
143
+ const DF_FLAGS = [
144
+ "-h",
145
+ "--human-readable",
146
+ "-H",
147
+ "--si",
148
+ "-k",
149
+ "-T",
150
+ "--print-type",
151
+ "-i",
152
+ "--inodes",
153
+ "-a",
154
+ "--all",
155
+ "-l",
156
+ "--local",
157
+ "-P",
158
+ "--portability",
159
+ "--total",
160
+ "--help",
161
+ "--version",
162
+ // The clusters, by exact spelling.
163
+ "-hT",
164
+ "-Th",
165
+ "-hi",
166
+ "-ih",
167
+ "-hl",
168
+ "-lh",
169
+ ];
170
+ export function isDfDisplay(args) {
171
+ return literal(args) && noRepeatedFlag(args) && args.every((arg) => DF_FLAGS.includes(arg));
172
+ }
173
+ /**
174
+ * `free`: display flags only. `-s`/`--seconds` and `-c`/`--count` repeat the
175
+ * report on a timer, which holds the turn rather than reading anything, and
176
+ * stay out.
177
+ */
178
+ const FREE_FLAGS = [
179
+ "-b",
180
+ "--bytes",
181
+ "-k",
182
+ "--kibi",
183
+ "--kilo",
184
+ "-m",
185
+ "--mebi",
186
+ "--mega",
187
+ "-g",
188
+ "--gibi",
189
+ "--giga",
190
+ "-h",
191
+ "--human",
192
+ "--si",
193
+ "-l",
194
+ "--lohi",
195
+ "-t",
196
+ "--total",
197
+ "-w",
198
+ "--wide",
199
+ "-v",
200
+ "--committed",
201
+ "--help",
202
+ "--version",
203
+ "-ht",
204
+ "-th",
205
+ "-hw",
206
+ "-wh",
207
+ ];
208
+ export function isFreeDisplay(args) {
209
+ return literal(args) && noRepeatedFlag(args) && args.every((arg) => FREE_FLAGS.includes(arg));
210
+ }
211
+ /** A decimal number, signed or not, with an optional exponent. No hex, `inf` or `nan`. */
212
+ const SEQ_NUMBER = /^[-+]?(?:\d+(?:\.\d*)?|\.\d+)(?:[eE][-+]?\d+)?$/u;
213
+ /**
214
+ * The most values an admitted `seq` prints (refutation S4). `seq 1e18` is a
215
+ * read in the sense that it changes nothing, but it holds the turn the way
216
+ * `free -s` does, so a range past this is not admitted.
217
+ */
218
+ export const SEQ_MAX_VALUES = 100_000;
219
+ /**
220
+ * Can a double hold this number exactly enough to count with? (Recheck S-R2.)
221
+ * GNU and uutils count digit strings exactly, so a range narrower than half a
222
+ * ULP of its ends (`seq 1e30-ish 1e30-ish+5e13`) reads as one value to a
223
+ * double and prints fifty trillion. No more than 15 significant digits, and no
224
+ * magnitude above 2^53, keeps every admitted operand exact enough.
225
+ */
226
+ function isCountable(word) {
227
+ const mantissa = word.replace(/^[-+]/u, "").replace(/[eE].*$/u, "").replace(".", "");
228
+ const significant = mantissa.replace(/^0+/u, "");
229
+ if (significant.length > 15)
230
+ return false;
231
+ return Math.abs(Number(word)) <= 2 ** 53;
232
+ }
233
+ /** How many values `seq FIRST [INCREMENT] LAST` prints; Infinity when it never ends. */
234
+ function seqCount(numbers) {
235
+ const [first, increment, last] = numbers.length === 1
236
+ ? [1, 1, numbers[0]]
237
+ : numbers.length === 2
238
+ ? [numbers[0], 1, numbers[1]]
239
+ : [numbers[0], numbers[1], numbers[2]];
240
+ if (![first, increment, last].every((n) => Number.isFinite(n)))
241
+ return Infinity;
242
+ if (increment === 0)
243
+ return Infinity;
244
+ const steps = Math.floor((last - first) / increment);
245
+ return steps < 0 ? 0 : steps + 1;
246
+ }
247
+ /** One floating-point directive, `%%` escapes around it, nothing else. */
248
+ const SEQ_FORMAT = /^(?:[^%]|%%)*%[-+ #0]*\d*(?:\.\d+)?[eEfFgG](?:[^%]|%%)*$/u;
249
+ /** A separator: any literal text that does not look like a flag. */
250
+ function isSeqSeparator(value) {
251
+ return value !== undefined && !value.startsWith("-");
252
+ }
253
+ /**
254
+ * `seq`: one to three numbers, with `-w`/`--equal-width`, `-s SEP` /
255
+ * `--separator=SEP` and `-f FMT` / `--format=FMT`. A separate-word value is
256
+ * the next word; the attached short spellings (`-s,`) are not in the grammar.
257
+ * Every flag comes BEFORE the first number: uutils reads a word after a number
258
+ * as another number (`seq 1 -w` is an error there and a flag to GNU), and the
259
+ * grammar takes the reading both agree on.
260
+ */
261
+ export function isSeqDisplay(args) {
262
+ if (!literal(args) || !noRepeatedFlag(args, ["-s", "--separator", "-f", "--format"]))
263
+ return false;
264
+ if (isHelpOrVersion(args))
265
+ return true;
266
+ let numbers = 0;
267
+ for (let index = 0; index < args.length; index += 1) {
268
+ const arg = args[index];
269
+ if (numbers > 0) {
270
+ if (!SEQ_NUMBER.test(arg))
271
+ return false;
272
+ numbers += 1;
273
+ continue;
274
+ }
275
+ if (arg === "-w" || arg === "--equal-width")
276
+ continue;
277
+ if (arg === "-s" || arg === "--separator") {
278
+ index += 1;
279
+ if (!isSeqSeparator(args[index]))
280
+ return false;
281
+ continue;
282
+ }
283
+ if (arg.startsWith("--separator="))
284
+ continue;
285
+ if (arg === "-f" || arg === "--format") {
286
+ index += 1;
287
+ // A flag-shaped value is the value to getopt and the next flag to clap.
288
+ const value = args[index] ?? "";
289
+ if (value.startsWith("-") || !SEQ_FORMAT.test(value))
290
+ return false;
291
+ continue;
292
+ }
293
+ if (arg.startsWith("--format=")) {
294
+ if (!SEQ_FORMAT.test(arg.slice("--format=".length)))
295
+ return false;
296
+ continue;
297
+ }
298
+ if (!SEQ_NUMBER.test(arg))
299
+ return false;
300
+ numbers += 1;
301
+ }
302
+ // GNU and uutils both refuse a format beside equal width.
303
+ const width = args.includes("-w") || args.includes("--equal-width");
304
+ const format = args.some((arg) => arg === "-f" || arg === "--format" || arg.startsWith("--format="));
305
+ if (width && format)
306
+ return false;
307
+ if (numbers < 1 || numbers > 3)
308
+ return false;
309
+ const operands = args.slice(args.length - numbers);
310
+ if (!operands.every((word) => isCountable(word)))
311
+ return false;
312
+ return seqCount(operands.map(Number)) <= SEQ_MAX_VALUES;
313
+ }
314
+ /**
315
+ * `paste`: files (or `-`, standard input) with `-s`/`--serial`,
316
+ * `-z`/`--zero-terminated`, and the delimiter list ATTACHED (`-d,`,
317
+ * `--delimiters=,`). Attached, so that every positional is a file: the read
318
+ * scope takes paste's operands exactly as it takes `cat`'s (`READ_TARGET_SHAPES`
319
+ * in read-scope.ts), and a delimiter in its own word would be taken for one.
320
+ */
321
+ const PASTE_FLAGS = ["-s", "--serial", "-z", "--zero-terminated", "--help", "--version"];
322
+ export function isPasteRead(args) {
323
+ if (!literal(args) || !noRepeatedFlag(args))
324
+ return false;
325
+ for (const arg of args) {
326
+ if (PASTE_FLAGS.includes(arg))
327
+ continue;
328
+ if (arg.startsWith("--delimiters="))
329
+ continue;
330
+ if (arg.startsWith("-d") && arg.length > 2)
331
+ continue;
332
+ // A file, or `-` for standard input. Every other flag-shaped word is not
333
+ // in the grammar (`--`, `-d` with a separate value, abbreviations).
334
+ if (arg === "-" || !arg.startsWith("-"))
335
+ continue;
336
+ return false;
337
+ }
338
+ return true;
339
+ }
340
+ //# sourceMappingURL=sysinfo-argv.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sysinfo-argv.js","sourceRoot":"","sources":["../../../src/core/sysinfo-argv.ts"],"names":[],"mappings":"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"}
@@ -71,9 +71,9 @@ import type { LogHead } from "../core/log.js";
71
71
  * because a module that resolves its own package root differs between the source
72
72
  * tree and the build output for no benefit at all.
73
73
  */
74
- export declare const APPROVALD_VERSION = "0.4.3";
74
+ export declare const APPROVALD_VERSION = "0.4.6";
75
75
  /** `user.name` on every commit this module makes. */
76
- export declare const GIT_EVIDENCE_AUTHOR_NAME = "approvald 0.4.3";
76
+ export declare const GIT_EVIDENCE_AUTHOR_NAME = "approvald 0.4.6";
77
77
  /** `user.email` on every commit: fixed, and deliberately undeliverable. */
78
78
  export declare const GIT_EVIDENCE_AUTHOR_EMAIL = "approvald@noreply.approval.md";
79
79
  /**
@@ -75,7 +75,7 @@ import { PAYLOAD_STORE_DIRNAME } from "../core/payload-store.js";
75
75
  * because a module that resolves its own package root differs between the source
76
76
  * tree and the build output for no benefit at all.
77
77
  */
78
- export const APPROVALD_VERSION = "0.4.3";
78
+ export const APPROVALD_VERSION = "0.4.6";
79
79
  /** `user.name` on every commit this module makes. */
80
80
  export const GIT_EVIDENCE_AUTHOR_NAME = `approvald ${APPROVALD_VERSION}`;
81
81
  /** `user.email` on every commit: fixed, and deliberately undeliverable. */
@@ -341,6 +341,7 @@ semantics: SPEC.md section 5.
341
341
  | `audit.skew_tolerance` | How far a gate-typed event's timestamp may step back before verification reports an anomaly. Report-only; default 2 seconds (§8). |
342
342
  | `audit.checkpoint_keys` | Public halves of the Ed25519 keys permitted to sign a `log.checkpoint`, base64 DER SPKI. The private halves live in the vault and never in this file. A list, so a retired key stays listed: a checkpoint signed by a key the list does not carry is refused. Absent, empty or unreadable means verification skips the checkpoint check with a reason and never reports it as a pass (§9, APRV-220). |
343
343
  | `delegation` | The judge's bounds (a pinned model that may later review samples, advise on cards and decide manual cards in named classes). RESERVED in 0.4.2: the block is validated and loads only in its off form (`model: null`, every list empty, `max_autonomy: manual`, `daily_cap: 0`, `advice: false`; `{}` is the same), which changes nothing. Any other value fails the policy closed with `delegation-not-supported`, so every class resolves `manual`. Absent means no judge, exactly as before the key existed. Cores before 0.4.2 refuse the key outright (§5.2 proposed, APRV-500; `docs/cli-reference.md`, policy check). |
344
+ | `exec` | What the operator attests about opaque agent code (`python3 -c`, `node -e`, `deno eval` and kin, an interpreter running a file that is not a skill script, Hermes `execute_code`). `exec.code.custody: attested` opens the `exec.code` class, which then resolves like any class and is recorded by the program's SHA-256 and length, never its text; `exec.code.execute_code: hooked` beside it opens Hermes `execute_code`. Absent, those calls are refused exactly as before the key existed; a policy that names `exec.code` without custody refuses `hook-exec-code-custody-unattested`. Cores before 0.4.5 refuse the key and fail the policy closed (§5.2 and §7 proposed, APRV-510; `docs/hermes-hook.md`). |
344
345
  | `delegation.model` | The judge identity, `model:<name>@<major>.<minor>.<patch>`, or `null`. Reserved: non-null is `delegation-not-supported` (APRV-500). |
345
346
  | `delegation.classes` | Exact keys of `classes` the judge may act on: no wildcard, not `human-only`, not `autonomous`. Reserved: non-empty is `delegation-not-supported` (APRV-500). |
346
347
  | `delegation.max_autonomy` | `manual` (default), `supervised-live` or `supervised-retro`: a pin, so every delegated class must declare an autonomy at least as strict. Reserved: anything but `manual` is `delegation-not-supported` (APRV-500). |
@@ -740,9 +741,9 @@ and the reference phone channel is one app, Telegram.
740
741
  | [`backlog/`](../backlog/) | Implementation work and remaining acceptance criteria. A task's existence is not a shipped feature. |
741
742
  | [`index.html`](../index.html), [`features/`](../features/), [`llms.txt`](../llms.txt), [`llms-full.txt`](../llms-full.txt) | The website and its machine-readable companions. |
742
743
  | [`metrics/`](../metrics/), [`brand/`](../brand/) | Agent-hour badge data and visual assets. Badge activity is not an adoption or security certification. |
743
- | [`hosted/`](../hosted/README.md), [`judgy/`](../judgy/README.md) | Hosting scaffolding and reviewer/demo pages; separate from the core runtime contract. |
744
+ | [`hosted/`](../hosted/README.md) | Hosting scaffolding; separate from the core runtime contract. |
744
745
 
745
- The hosted directory contains a policy builder and tenant launcher, not a complete account system or managed multi-tenant control plane. Judgy's pages render supplied data and retain simulated/replay/not-run provenance labels; a visualization is not a new authorization path. Neither should be advertised as a finished service merely because its directory exists.
746
+ The hosted directory contains a policy builder and tenant launcher, not a complete account system or managed multi-tenant control plane. It should not be advertised as a finished service merely because its directory exists. The judgy reviewer pages that used to sit beside it left the repository under APRV-393; commit `49574e4d` is the last one that holds them.
746
747
 
747
748
  ### Running the checks
748
749
 
@@ -3424,9 +3424,13 @@ The checks, at length:
3424
3424
  **fail-closed version floor** (a build at or after `main` `118984d7` of
3425
3425
  2026-09-20) FAILS, because `v0.21.3` ignores `fail_closed` silently and every
3426
3426
  broken hook on it proceeds. That half is read from `hermes --version` rather than
3427
- from a record — Hermes prints its build stamp behind a non-ASCII separator the
3428
- write boundary refuses, so no record can carry it — and it compares the build
3429
- date and the upstream commit, because the two builds report the same semver.
3427
+ from a record, because a record keeps only the printable-ASCII prefix of
3428
+ Hermes's line (APRV-419): the build date survives and the upstream commit
3429
+ behind the non-ASCII separator does not. It compares the build date and the
3430
+ upstream commit, because the two builds report the same semver. Since APRV-419
3431
+ the unverified-change half compares Hermes like any other harness: the prefix,
3432
+ for example `Hermes Agent v0.21.3 (2026.9.14)`, on the newest hook record
3433
+ against the prefix of what `hermes --version` prints now.
3430
3434
  - **live-draw** — whether a daemon is answering `supervised-live` draws for this
3431
3435
  log (APRV-208). SKIP when the policy declares no live class: no draw is ever
3432
3436
  made, and a missing socket is nothing. It FAILS in three shapes, all of them
@@ -4595,7 +4599,7 @@ to.
4595
4599
 
4596
4600
  | Command | What it does |
4597
4601
  | -------- | ------------ |
4598
- | `/queue` | Replies with the summary and a numbered list of every pending request (action key, task, class, age), marking the one this listener has selected. Derived from the verified log at reply time, and it works while a request is selected. |
4602
+ | `/queue` | Replies with the count line and a numbered list of every pending request (the agent's summary and its age, no action key or task id), marking the one this listener has selected. Derived from the verified log at reply time, and it works while a request is selected. |
4599
4603
  | `/skip` | Selects the next request; the skipped one goes to the BACK of this process's order and comes round again after the rest, with a fresh card when it does. |
4600
4604
  | `/next` | Selects the next request; this process does not offer the passed-over one again, and sends no further card for it. |
4601
4605
 
@@ -4609,7 +4613,7 @@ it away from you.
4609
4613
  **`/queue` is a list, and it says so** (APRV-256). The reply carries no decision
4610
4614
  buttons of its own, and it names no position for the ones it points at: a
4611
4615
  request is decided on its own approval card, wherever that card has ended up in
4612
- the chat. The marker on the selected line reads `selected — card sent earlier`,
4616
+ the chat. The marker on the selected line reads `sent to you earlier`,
4613
4617
  because that is the whole of what the listener knows. Delivery bookkeeping
4614
4618
  records that a send returned success; the Bot API never reports that a message
4615
4619
  is still there, and a card can be deleted, buried, or lost with the chat
@@ -5652,7 +5656,11 @@ semantic-version-shaped tag, `tag <name>`, `--tags`, or `--follow-tags` is
5652
5656
  that DELETES a remote ref — `--delete`, `-d`, or a colon refspec such as
5653
5657
  `:refs/heads/x` — is `vcs.ref.delete` with the ref names bound (APRV-352), and a
5654
5658
  tag deletion keeps `release.publish`; ordinary branch pushes retain their branch
5655
- or trunk class.
5659
+ or trunk class. Flags are read the way git's option parser reads them, short
5660
+ bundles (`-ud`) and unique-prefix abbreviations (`--del`, `--m`) included, and
5661
+ `--prune` is a deletion too (APRV-517); a push git would refuse, or one naming
5662
+ a far-side program (`--exec`, `--receive-pack`), is `opaque`, and a word the
5663
+ shell may expand is `vcs.history.rewrite`.
5656
5664
 
5657
5665
  `git tag` splits on its flags since APRV-397: a LISTING (`-l`, `--list`, `-n`,
5658
5666
  `-n<num>`, `--contains`, `--points-at`, `--merged`, `--sort`, `--format`,
@@ -5715,7 +5723,12 @@ its own flags are not parsed as this verb's.
5715
5723
  ONE inline script is classified by that script since APRV-380, so
5716
5724
  `zsh -lc 'git push origin main'` is `vcs.push.main`; a script file, an extra
5717
5725
  word, a redirection on the wrapper, an assignment prefix and a nested shell
5718
- all stay opaque.
5726
+ all stay opaque. So does a shell that is not the system's, named as written
5727
+ (APRV-523): one spelled by a path outside `/bin`, `/usr/bin`,
5728
+ `/usr/local/bin`, `/sbin` and `/usr/sbin` (a relative path, a Homebrew
5729
+ shell under `/opt`), or by a word with an expansion or a backslash in it.
5730
+ An assignment-shaped shell word with a quoted or escaped name is
5731
+ `hook-unclassified`.
5719
5732
  - `hook-unparseable` — the command line could not be tokenized.
5720
5733
  - `hook-rejected` — a human said no.
5721
5734
  - `hook-revoked` — a granted approval was withdrawn.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "approval-md",
3
- "version": "0.4.3",
3
+ "version": "0.4.6",
4
4
  "description": "Human approval for agent actions. CLI runtime for the approval.md convention (pre-release).",
5
5
  "type": "module",
6
6
  "bin": {
@@ -59,5 +59,5 @@
59
59
  "better-sqlite3": "12.11.1",
60
60
  "yaml": "2.9.0"
61
61
  },
62
- "gitHead": "bd5c7562b226424e1c3b0715d55731604ff33088"
62
+ "gitHead": "539cf3b6b847b0446730ed110b7f188660f2a9d7"
63
63
  }
@@ -136,7 +136,7 @@
136
136
  "minLength": 1,
137
137
  "maxLength": 64,
138
138
  "pattern": "^[\\u0020-\\u007e]+$",
139
- "description": "SPEC.md §10.1 (amended, APRV-227): the version of the harness binary that issued this record, taken from the hook event where the harness supplies one and otherwise from `<binary> --version`, normalized to its first line. SELF-REPORTED, and informational only: nothing in the runtime reads it back as an input to a verdict, a floor, a budget, a streak or a sampling draw, so it satisfies SPEC.md §11.1 invariant 4 by having no path along which it could reduce scrutiny — its single reader is a doctor row that can only ADD a red line. Its SHAPE is constrained here because the value is the output of a third-party process being written to an append-only log: one line, printable ASCII, capped at 64 characters, so a banner or a stack trace (and whatever a stack trace quotes) cannot arrive through it. §11.1 invariant 3 has no exception for provenance. OPTIONAL and additive."
139
+ "description": "SPEC.md §10.1 (amended, APRV-227): the version of the harness binary that issued this record, taken from the hook event where the harness supplies one and otherwise from `<binary> --version`, normalized to its first line. SELF-REPORTED, and informational only: nothing in the runtime reads it back as an input to a verdict, a floor, a budget, a streak or a sampling draw, so it satisfies SPEC.md §11.1 invariant 4 by having no path along which it could reduce scrutiny — its single reader is a doctor row that can only ADD a red line. Its SHAPE is constrained here because the value is the output of a third-party process being written to an append-only log: one line, printable ASCII, capped at 64 characters, so a banner or a stack trace (and whatever a stack trace quotes) cannot arrive through it. §11.1 invariant 3 has no exception for provenance. APRV-419 chose option (c) in its no-schema form: a writer records the first line's printable-ASCII PREFIX, trimmed, cutting everything from the first character outside U+0020..U+007E, where it used to refuse the whole line. That is why a Hermes record carries `Hermes Agent v0.21.3 (2026.9.14)` from the line `Hermes Agent v0.21.3 (2026.9.14) \u00b7 upstream 913d4098`. Every value the prefix rule produces already satisfied this pattern and cap, so neither moved and no record changes validity. OPTIONAL and additive."
140
140
  },
141
141
  "est_cost_usd": {
142
142
  "$ref": "#/$defs/usd_amount",
@@ -0,0 +1,11 @@
1
+ {
2
+ "version": "0.1",
3
+ "defaults": {
4
+ "autonomy": "manual"
5
+ },
6
+ "exec": {
7
+ "code": {
8
+ "custody": true
9
+ }
10
+ }
11
+ }
@@ -0,0 +1,11 @@
1
+ {
2
+ "version": "0.1",
3
+ "defaults": {
4
+ "autonomy": "manual"
5
+ },
6
+ "exec": {
7
+ "code": {
8
+ "execute_code": "yes"
9
+ }
10
+ }
11
+ }
@@ -0,0 +1,12 @@
1
+ {
2
+ "version": "0.1",
3
+ "defaults": {
4
+ "autonomy": "manual"
5
+ },
6
+ "exec": {
7
+ "code": {
8
+ "custody": "attested",
9
+ "inline": "on"
10
+ }
11
+ }
12
+ }
@@ -0,0 +1,17 @@
1
+ {
2
+ "version": "0.1",
3
+ "defaults": {
4
+ "autonomy": "manual"
5
+ },
6
+ "exec": {
7
+ "code": {
8
+ "custody": "attested",
9
+ "execute_code": "hooked"
10
+ }
11
+ },
12
+ "classes": {
13
+ "exec.code": {
14
+ "autonomy": "autonomous"
15
+ }
16
+ }
17
+ }
@@ -0,0 +1,11 @@
1
+ {
2
+ "version": "0.1",
3
+ "defaults": {
4
+ "autonomy": "manual"
5
+ },
6
+ "exec": {
7
+ "code": {
8
+ "custody": "attested"
9
+ }
10
+ }
11
+ }
@@ -197,6 +197,29 @@
197
197
  }
198
198
  ]
199
199
  },
200
+ "exec": {
201
+ "type": "object",
202
+ "additionalProperties": false,
203
+ "description": "SPEC.md §5.2 and §7 (APRV-510, proposed; 0.4.5 candidate): what an operator has attested about opaque agent code. Absent, every policy behaves exactly as before the key existed: `python3 -c`, `node -e` and kin are refused `hook-opaque`, an interpreter running a file that is not a skill script is refused `hook-unclassified`, and a Hermes `execute_code` call is refused `hook-hermes-execute-code-unbound`. A core that predates this key refuses it at the schema, and the whole policy then fails closed (every class `manual`), so a tenant receives this block only after its pinned core knows it.",
204
+ "properties": {
205
+ "code": {
206
+ "type": "object",
207
+ "additionalProperties": false,
208
+ "description": "The `exec.code` class (APRV-510). Each key opens one thing and only the attested policy can set it; an absent key is the closed answer. With `custody: attested`, an inline program or a non-skill interpreter file run is classified `exec.code` and resolved by the policy like any class (an `exec.code` row, an `exec.*` family row, or `defaults.autonomy`), and a record of it binds the program's SHA-256 and length, never its text. The record says the program existed; it authorizes nothing the program then does through other tools, each of which is its own hook event.",
209
+ "properties": {
210
+ "custody": {
211
+ "enum": ["attested", "unattested"],
212
+ "description": "`attested`: the operator states that credential custody is in place for this agent (the code it runs cannot read credentials it should not hold), which is the precondition for opening the class at all, because opaque code run without it bypasses every gate that guards a credential. `unattested`, or the key absent: the class stays closed, and where the policy names `exec.code` anywhere the refusal is `hook-exec-code-custody-unattested`, naming this key."
213
+ },
214
+ "execute_code": {
215
+ "enum": ["hooked", "unhooked"],
216
+ "description": "Hermes `execute_code` only, and only beside `custody: attested`. `hooked`: the operator has observed that a tool call made from inside the `execute_code` kernel re-fires the hook (the lane L1 probe's answer), so the call is classified `exec.code` with the program's hash bound. `unhooked`, or the key absent: `execute_code` is refused `hook-hermes-execute-code-unbound` as before. Set it only on the probe's yes: if inner calls do not re-fire the hook, one `execute_code` call is an unbounded bypass of the whole adapter."
217
+ }
218
+ }
219
+ }
220
+ },
221
+ "examples": [{ "code": { "custody": "attested" } }, { "code": { "custody": "attested", "execute_code": "hooked" } }]
222
+ },
200
223
  "read_scope": {
201
224
  "type": "object",
202
225
  "additionalProperties": false,