approval-md 0.4.0 → 0.4.2

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 (190) hide show
  1. package/SPEC.md +11 -0
  2. package/cli.js +72 -4
  3. package/dist/src/channels/contract.d.ts +53 -0
  4. package/dist/src/channels/contract.js +56 -0
  5. package/dist/src/channels/contract.js.map +1 -1
  6. package/dist/src/channels/tagging.d.ts +25 -0
  7. package/dist/src/channels/tagging.js +57 -5
  8. package/dist/src/channels/tagging.js.map +1 -1
  9. package/dist/src/channels/telegram-minimal.d.ts +251 -0
  10. package/dist/src/channels/telegram-minimal.js +618 -0
  11. package/dist/src/channels/telegram-minimal.js.map +1 -0
  12. package/dist/src/channels/telegram.d.ts +454 -19
  13. package/dist/src/channels/telegram.js +931 -80
  14. package/dist/src/channels/telegram.js.map +1 -1
  15. package/dist/src/cli/amend.js +8 -1
  16. package/dist/src/cli/amend.js.map +1 -1
  17. package/dist/src/cli/attest.js +2 -1
  18. package/dist/src/cli/attest.js.map +1 -1
  19. package/dist/src/cli/audit-card.d.ts +1 -1
  20. package/dist/src/cli/audit-card.js +40 -1
  21. package/dist/src/cli/audit-card.js.map +1 -1
  22. package/dist/src/cli/audit.d.ts +7 -5
  23. package/dist/src/cli/audit.js +25 -12
  24. package/dist/src/cli/audit.js.map +1 -1
  25. package/dist/src/cli/channel-telegram-webhook.d.ts +2 -0
  26. package/dist/src/cli/channel-telegram-webhook.js +7 -0
  27. package/dist/src/cli/channel-telegram-webhook.js.map +1 -1
  28. package/dist/src/cli/channel-telegram.d.ts +75 -2
  29. package/dist/src/cli/channel-telegram.js +172 -16
  30. package/dist/src/cli/channel-telegram.js.map +1 -1
  31. package/dist/src/cli/channel-web.js +2 -1
  32. package/dist/src/cli/channel-web.js.map +1 -1
  33. package/dist/src/cli/channel.js +2 -1
  34. package/dist/src/cli/channel.js.map +1 -1
  35. package/dist/src/cli/doctor.js +1 -1
  36. package/dist/src/cli/doctor.js.map +1 -1
  37. package/dist/src/cli/execute.d.ts +12 -0
  38. package/dist/src/cli/execute.js +50 -4
  39. package/dist/src/cli/execute.js.map +1 -1
  40. package/dist/src/cli/gate.js +2 -1
  41. package/dist/src/cli/gate.js.map +1 -1
  42. package/dist/src/cli/help.d.ts +1 -0
  43. package/dist/src/cli/help.js +39 -18
  44. package/dist/src/cli/help.js.map +1 -1
  45. package/dist/src/cli/hook.d.ts +121 -3
  46. package/dist/src/cli/hook.js +719 -79
  47. package/dist/src/cli/hook.js.map +1 -1
  48. package/dist/src/cli/log-checkpoint.js +2 -1
  49. package/dist/src/cli/log-checkpoint.js.map +1 -1
  50. package/dist/src/cli/log-unlock.d.ts +23 -0
  51. package/dist/src/cli/log-unlock.js +110 -0
  52. package/dist/src/cli/log-unlock.js.map +1 -0
  53. package/dist/src/cli/main.js +23 -6
  54. package/dist/src/cli/main.js.map +1 -1
  55. package/dist/src/cli/setup-common.js +2 -1
  56. package/dist/src/cli/setup-common.js.map +1 -1
  57. package/dist/src/cli/up.js +5 -0
  58. package/dist/src/cli/up.js.map +1 -1
  59. package/dist/src/cli/vault.js +2 -1
  60. package/dist/src/cli/vault.js.map +1 -1
  61. package/dist/src/cli/verb-registry.js +1 -0
  62. package/dist/src/cli/verb-registry.js.map +1 -1
  63. package/dist/src/cli/wordmark.d.ts +2 -2
  64. package/dist/src/cli/wordmark.js +2 -2
  65. package/dist/src/core/attest-bootstrap.js +2 -1
  66. package/dist/src/core/attest-bootstrap.js.map +1 -1
  67. package/dist/src/core/attest.js +5 -4
  68. package/dist/src/core/attest.js.map +1 -1
  69. package/dist/src/core/audit.d.ts +113 -9
  70. package/dist/src/core/audit.js +382 -18
  71. package/dist/src/core/audit.js.map +1 -1
  72. package/dist/src/core/budgets.d.ts +12 -0
  73. package/dist/src/core/budgets.js +54 -1
  74. package/dist/src/core/budgets.js.map +1 -1
  75. package/dist/src/core/checkpoint.js +3 -2
  76. package/dist/src/core/checkpoint.js.map +1 -1
  77. package/dist/src/core/command-class.js +6 -0
  78. package/dist/src/core/command-class.js.map +1 -1
  79. package/dist/src/core/decision-refusal.js +2 -1
  80. package/dist/src/core/decision-refusal.js.map +1 -1
  81. package/dist/src/core/delegation.d.ts +109 -0
  82. package/dist/src/core/delegation.js +244 -0
  83. package/dist/src/core/delegation.js.map +1 -0
  84. package/dist/src/core/execute.js +12 -3
  85. package/dist/src/core/execute.js.map +1 -1
  86. package/dist/src/core/gate-window.js +4 -3
  87. package/dist/src/core/gate-window.js.map +1 -1
  88. package/dist/src/core/gate.d.ts +84 -43
  89. package/dist/src/core/gate.js +114 -6
  90. package/dist/src/core/gate.js.map +1 -1
  91. package/dist/src/core/gesture-refusal.js +2 -1
  92. package/dist/src/core/gesture-refusal.js.map +1 -1
  93. package/dist/src/core/identity.d.ts +105 -0
  94. package/dist/src/core/identity.js +122 -0
  95. package/dist/src/core/identity.js.map +1 -0
  96. package/dist/src/core/log-lock.d.ts +435 -0
  97. package/dist/src/core/log-lock.js +1093 -0
  98. package/dist/src/core/log-lock.js.map +1 -0
  99. package/dist/src/core/log.d.ts +82 -3
  100. package/dist/src/core/log.js +285 -40
  101. package/dist/src/core/log.js.map +1 -1
  102. package/dist/src/core/loop.d.ts +11 -0
  103. package/dist/src/core/loop.js +63 -4
  104. package/dist/src/core/loop.js.map +1 -1
  105. package/dist/src/core/policy-diff.js +20 -1
  106. package/dist/src/core/policy-diff.js.map +1 -1
  107. package/dist/src/core/policy-explain.js +18 -2
  108. package/dist/src/core/policy-explain.js.map +1 -1
  109. package/dist/src/core/policy-load.d.ts +38 -1
  110. package/dist/src/core/policy-load.js +34 -2
  111. package/dist/src/core/policy-load.js.map +1 -1
  112. package/dist/src/core/policy-match.js +30 -5
  113. package/dist/src/core/policy-match.js.map +1 -1
  114. package/dist/src/core/policy-proposal.d.ts +17 -0
  115. package/dist/src/core/policy-proposal.js +32 -16
  116. package/dist/src/core/policy-proposal.js.map +1 -1
  117. package/dist/src/core/prompt-layout.d.ts +186 -1
  118. package/dist/src/core/prompt-layout.js +345 -3
  119. package/dist/src/core/prompt-layout.js.map +1 -1
  120. package/dist/src/core/sender-identity.d.ts +24 -1
  121. package/dist/src/core/sender-identity.js +31 -0
  122. package/dist/src/core/sender-identity.js.map +1 -1
  123. package/dist/src/core/token.js +11 -0
  124. package/dist/src/core/token.js.map +1 -1
  125. package/dist/src/core/tool-map.d.ts +125 -0
  126. package/dist/src/core/tool-map.js +179 -0
  127. package/dist/src/core/tool-map.js.map +1 -0
  128. package/dist/src/core/validate.d.ts +20 -6
  129. package/dist/src/core/validate.js +0 -0
  130. package/dist/src/core/validate.js.map +1 -1
  131. package/dist/src/daemon/git-evidence.d.ts +2 -2
  132. package/dist/src/daemon/git-evidence.js +1 -1
  133. package/dist/src/serve/archive.d.ts +8 -0
  134. package/dist/src/serve/archive.js +11 -2
  135. package/dist/src/serve/archive.js.map +1 -1
  136. package/docs/README-extended.md +15 -0
  137. package/docs/cli-reference.md +571 -28
  138. package/package.json +2 -2
  139. package/schema/event.schema.json +387 -29
  140. package/schema/fixtures/event/invalid/audit-lock-reclaimed-age-without-created.json +19 -0
  141. package/schema/fixtures/event/invalid/audit-lock-reclaimed-agent-actor.json +14 -0
  142. package/schema/fixtures/event/invalid/audit-lock-reclaimed-lockfile-path.json +14 -0
  143. package/schema/fixtures/event/invalid/audit-lock-reclaimed-operator-cleared-system-actor.json +14 -0
  144. package/schema/fixtures/event/invalid/audit-lock-reclaimed-unknown-reason.json +14 -0
  145. package/schema/fixtures/event/invalid/audit-lock-reclaimed-without-age.json +13 -0
  146. package/schema/fixtures/event/invalid/audit-reviewed-agent-actor.json +6 -1
  147. package/schema/fixtures/event/invalid/audit-reviewed-bad-payload-hash.json +21 -0
  148. package/schema/fixtures/event/invalid/audit-reviewed-loved-no-note.json +5 -0
  149. package/schema/fixtures/event/invalid/audit-reviewed-model-actor.json +20 -0
  150. package/schema/fixtures/event/invalid/audit-reviewed-no-subject-hash.json +19 -0
  151. package/schema/fixtures/event/invalid/audit-reviewed-no-subject-seq.json +19 -0
  152. package/schema/fixtures/event/invalid/audit-reviewed-no-verdict-source.json +19 -0
  153. package/schema/fixtures/event/invalid/audit-reviewed-no-verdict.json +19 -0
  154. package/schema/fixtures/event/invalid/audit-reviewed-system-actor.json +6 -1
  155. package/schema/fixtures/event/invalid/audit-reviewed-verdict-source-model.json +20 -0
  156. package/schema/fixtures/event/invalid/audit-sampled-no-policy-hash.json +14 -0
  157. package/schema/fixtures/event/invalid/execution-started-harness-tool-space.json +18 -0
  158. package/schema/fixtures/event/valid/audit-lock-reclaimed-implausible-created.json +18 -0
  159. package/schema/fixtures/event/valid/audit-lock-reclaimed-legacy.json +14 -0
  160. package/schema/fixtures/event/valid/audit-lock-reclaimed-unlock.json +19 -0
  161. package/schema/fixtures/event/valid/audit-lock-reclaimed.json +19 -0
  162. package/schema/fixtures/event/valid/audit-reviewed-reaction.json +5 -0
  163. package/schema/fixtures/event/valid/audit-reviewed.json +6 -1
  164. package/schema/fixtures/event/valid/audit-sampled.json +2 -1
  165. package/schema/fixtures/event/valid/daemon-identity.json +2 -1
  166. package/schema/fixtures/event/valid/execution-started-harness-tool.json +19 -0
  167. package/schema/fixtures/policy/invalid/delegation-advice-string.json +15 -0
  168. package/schema/fixtures/policy/invalid/delegation-class-duplicate.json +18 -0
  169. package/schema/fixtures/policy/invalid/delegation-class-wildcard.json +17 -0
  170. package/schema/fixtures/policy/invalid/delegation-daily-cap-fractional.json +15 -0
  171. package/schema/fixtures/policy/invalid/delegation-daily-cap-negative.json +15 -0
  172. package/schema/fixtures/policy/invalid/delegation-daily-cap-too-high.json +15 -0
  173. package/schema/fixtures/policy/invalid/delegation-escalate-duplicate.json +18 -0
  174. package/schema/fixtures/policy/invalid/delegation-escalate-unknown.json +17 -0
  175. package/schema/fixtures/policy/invalid/delegation-max-autonomy-alias.json +15 -0
  176. package/schema/fixtures/policy/invalid/delegation-max-autonomy-autonomous.json +15 -0
  177. package/schema/fixtures/policy/invalid/delegation-model-no-version.json +15 -0
  178. package/schema/fixtures/policy/invalid/delegation-model-provider-form.json +15 -0
  179. package/schema/fixtures/policy/invalid/delegation-null.json +7 -0
  180. package/schema/fixtures/policy/invalid/delegation-reviewer-agent.json +17 -0
  181. package/schema/fixtures/policy/invalid/delegation-reviewer-bare-id.json +17 -0
  182. package/schema/fixtures/policy/invalid/delegation-unknown-key.json +16 -0
  183. package/schema/fixtures/policy/invalid/tools-class-wildcard.json +9 -0
  184. package/schema/fixtures/policy/invalid/tools-match-double-star.json +9 -0
  185. package/schema/fixtures/policy/invalid/unmapped-tool-unknown.json +7 -0
  186. package/schema/fixtures/policy/valid/delegation-empty.json +7 -0
  187. package/schema/fixtures/policy/valid/delegation-grammar-full.json +41 -0
  188. package/schema/fixtures/policy/valid/delegation-off.json +15 -0
  189. package/schema/fixtures/policy/valid/tools-mapping.json +17 -0
  190. package/schema/policy.schema.json +178 -0
@@ -292,6 +292,7 @@ The closed [policy schema](../schema/policy.schema.json) and [SPEC section 5](..
292
292
  | `channels` | Telegram and web configuration, plus supported prompt-layout choices. |
293
293
  | `vault` | The name of the environment variable holding the vault passphrase. |
294
294
  | `payload_retention` | Optional pruning policy for eligible stored payloads; absence means retain them. |
295
+ | `delegation` | The judge's bounds, reserved in 0.4.2: loads only when off and changes nothing. |
295
296
 
296
297
  Keys ending in `_env` name environment variables, not secret values. Prompt customization cannot hide the fields required for a decision or remove the canonical payload from the authority path.
297
298
 
@@ -312,12 +313,16 @@ semantics: SPEC.md section 5.
312
313
  | `defaults.approval_ttl` | How long a pending request stays actionable. Duration string, `24h`. No default; the scaffolded policy writes one (§5.1). |
313
314
  | `defaults.token_delivery` | How a minted token reaches the process that will spend it. `manual` (the default, and what an absent key means): printed once on the granting surface and carried by a human. `sealed`: sealed to a per-request X25519 key so `approval wait` can hand it back, which addresses the token and never authorizes it (§10.4, APRV-105). |
314
315
  | `defaults.on_expiry` | What happens when the TTL lapses. `reject` is the only value, and absent means `reject` (§5.1). |
316
+ | `defaults.unmapped_tool` | What a harness hook does with a tool call that neither the adapter's own tables nor any `tools` entry claims. `record`: judged under `harness.tool.unmapped`, which resolves `autonomous` unless a `classes` rule says otherwise, so the call runs and its `execution.started` names the tool (such record-only starts are not charged to `daily_actions` and do not accrue to the loop floor). `ask`: the same class resolves `manual`, so a human decides. Absent means the call is not a gate question and leaves no record, as before the key existed (§5.2, APRV-499, proposed). |
315
317
  | `payload_retention` | How long payload bytes are kept after their action is terminal. Absent means nothing is ever pruned (§5.2). |
316
318
  | `protected_paths` | Repo-relative files and directory prefixes whose edit is classified `policy.edit`. A bare string is the whole entry. Additive only, no globs, and absent means the built-in protected set alone (§5.2, APRV-107). |
317
319
  | `protected_paths[].path` | The path half of the object form: the same grammar as the bare string, an exact file (`SPEC.md`) or a directory prefix (`design/`) (§5.2, APRV-266). |
318
320
  | `protected_paths[].class` | The class half: one lowercase segment under `policy.edit`. Four reserved names, `policy.edit.spec`, `policy.edit.harness`, `policy.edit.ci` and `policy.edit.design`, plus any word an author mints beside them. Nothing outside `policy.edit` may be named, and a route below the `policy.edit` line is refused `protected-route-floor`. No default: an entry that wants a sub-class states it (§5.2, APRV-266). |
319
321
  | `read_scope` | Directories an agent's reads may stay inside. A read resolving outside every root is `read.file.out_of_scope`. Additive only: the gate root (this policy's own directory), the session scratchpad and the system temp root are in scope whatever this says, and absent means those alone (§5.2, APRV-347). |
320
322
  | `read_scope.roots` | The extra directories. Absolute, or relative to the gate root. No globs and no negation; each is resolved on disk before it is compared, so a symlink cannot smuggle a read out of one. Absent means no widening (§5.2, APRV-347). |
323
+ | `tools` | Ordered `{match, class}` entries naming the class a harness's own tool calls are judged under, for the calls the adapter's built-in tables do not claim (every MCP tool among them). The first matching entry decides; the adapter's shell, file, read and pass-through tools and Hermes's rule table keep precedence. Absent or empty maps nothing (§5.2, APRV-499, proposed). |
324
+ | `tools[].match` | A glob over the whole tool name, case-sensitive, `*` the only wildcard (any run, including none); otherwise letters, digits, `_`, `.`, `:` and `-`, at most 256. Two `*` in a row, or two entries with one `match`, fail the policy closed. Required (§5.2, APRV-499). |
325
+ | `tools[].class` | The concrete class the call is judged under. Must be declared by `classes`, exactly or under a trailing `<prefix>.*` family, and may not be `harness.tool.unmapped`; either fault fails the policy closed. Required (§5.2, APRV-499). |
321
326
  | `approvers.<name>.channels` | The channels one approver can decide on. At least one: an approver reachable nowhere can never grant. No default (§5.1). |
322
327
  | `approvers.<name>.senders` | The transport account ids this person decides from, per channel: `senders.telegram: "12345678"`, the numeric `callback_query.from.id` and never a `@handle`, which is mutable and would transfer an identity. Optional and additive; absent everywhere means every decision is recorded against the identity the deciding process was launched with, exactly as before the key existed. Declaring the first entry for a channel turns enforcement on for that channel: a sender it authenticates and this block does not name is refused `sender-unmapped` and nothing is recorded. Two approvers claiming one id refuses the whole policy (`sender-ambiguous`), so every class resolves `manual`. Only channels whose transport authenticates a sender may appear, which today is `telegram` alone (§5.2, §10.3, APRV-324). |
323
328
  | `classes.<pattern>.autonomy` | Required on every class rule, so it has no default. Six levels, strictest first: `human-only`, `manual`, `supervised-live`, `supervised-retro`, `autonomous`, and `supervised`, which is the pre-split spelling and the DEPRECATED alias of `supervised-retro`: it still parses, `approval doctor`'s `autonomy-alias` row names every rule that writes it, and a future schema version removes it (§5.2, APRV-127, APRV-185, APRV-335). |
@@ -335,6 +340,14 @@ semantics: SPEC.md section 5.
335
340
  | `audit.sampling_secret_env` | Name of the variable holding the operator's HMAC sampling secret. Unnamed means sampling is off and says so (§5.2, §11). |
336
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). |
337
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
+ | `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
+ | `delegation.model` | The judge identity, `model:<name>@<major>.<minor>.<patch>`, or `null`. Reserved: non-null is `delegation-not-supported` (APRV-500). |
345
+ | `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
+ | `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). |
347
+ | `delegation.daily_cap` | Judge grants per rolling day, 0 to 1000; above 0 requires `escalate_on` to hold `irreversible` and `unknown_class`. Reserved: anything but 0 is `delegation-not-supported` (APRV-500). |
348
+ | `delegation.escalate_on` | Judge outcomes sent to a human: `deny`, `low_confidence`, `irreversible`, `unknown_class`. Reserved: non-empty is `delegation-not-supported` (APRV-500). |
349
+ | `delegation.advice` | Whether the judge advises on manual cards. Reserved: `true` is `delegation-not-supported` (APRV-500). |
350
+ | `delegation.reviewers` | Extra review identities: `human:<approver id>` or `model:<name>@<version>` equal to `model`. The only place a `model:` identity parses; every verb still requires `human:`. Reserved: non-empty is `delegation-not-supported` (APRV-500). |
338
351
  | `daemons` | The daemon instance ids permitted to append to this log, for the case where one party runs the daemon for another. Optional and additive; ABSENT means no restriction, exactly as before the key existed, and an empty list admits no daemon at all. Every record a daemon appends carries its id (`daemon` on the record), which is `APPROVAL_DAEMON_ID` from that process's launch environment or the derived `daemon-<instance id>` that `approval status` and `approval doctor` print. An id this list does not name is refused at the write boundary (`daemon-not-allowed`) with nothing written; an id it DOES name gains nothing else, since the id is self-reported and no verdict, budget, floor, draw or token reads it. Read from the attested policy only (§5.2, APRV-383, `design/hosted-daemon-identity.md`). |
339
352
  | `audit.checkpoint_every` | How long the log may go without a human-signed checkpoint before verification says one is due, and before the listener puts one `CHECKPOINT DUE` prompt on the approver's channel (`approval setup checkpoint` mints the key). Report-only at every layer: a due checkpoint is a warning and never a refusal. Absent means the cadence is off and nothing is ever reported as due (§9, APRV-220, APRV-257). |
340
353
  | `daemon.read_proof` | Which prefix proof a long-lived reader runs before reusing a cached prefix: `full` (the default, re-hash the whole prefix on every read) or `incremental` (hash only the appended bytes, re-proving in full on a cadence). One-shot processes, the Claude Code hook and `approval log verify` prove in full regardless (§5.2, APRV-217). |
@@ -348,6 +361,8 @@ semantics: SPEC.md section 5.
348
361
  | `channels.<name>.prompt.rows` | Order only, for `telegram`, `web` and `cli`: the rows named here render in this order ahead of the rest, which keep their default relative order behind them. Never a whitelist, so a field added later cannot be lost to a list written before it existed. Absent means the layout the channel ships (§5.2, §10.3, APRV-218). |
349
362
  | `channels.<name>.prompt.always` | Rows this channel renders only when abnormal, or not at all, render on every prompt instead. The anomaly mark stays a statement about the value, so a forced-on row shouts only when the value is in fact the reason to look. Absent means the channel's own visibility rules (§5.2, §10.3, APRV-218). |
350
363
  | `channels.<name>.prompt.hide` | Rows this channel never renders. Refused for the rows required for a decision (`action_key`, `class`, `command_breakdown`, `protected_path`, `policy_diff`, `policy_load`) with `prompt-row-required`, and refused for a row `always` also names. Absent means nothing is hidden, and the canonical payload block is out of reach either way (§5.2, §10.3, APRV-218). |
364
+ | `channels.<name>.prompt.style` | `technical` (the default, and what an absent key or an unloaded policy means: the prompt every earlier build sent) or `minimal`: one Telegram message in plain words, with a headline the runtime computes from the class, the payload's own words quoted, a deadline, Approve and Deny, and the whole technical card, canonical rendering included, collapsed under "Full details". A request the minimal card cannot draw honestly (an opaque payload without a `say` entry naming every key, an attestation, a policy edit, a digest, a truncated payload, an abnormal row, a batch, a card over 3800 characters, a card the Bot API refuses) is sent as the technical card, and the decision record says so in `payload.rendering`. Only Telegram draws it; `web` and `cli` ignore the key. An older core refuses it and fails the policy closed, so roll the core out first (§5.2, §10.3, APRV-489). |
365
+ | `channels.<name>.prompt.say` | Per EXACT class name (a pattern such as `files.*` is refused, `prompt-say-wildcard`), the operator's words for the minimal card: `does` (the verb phrase after "Your agent wants to": no sentence punctuation, markup or leading symbol; refused for a class core phrases itself, whose own phrase always wins, and for a class the command classifier emits, `prompt-say-kind`; drawn only over an opaque payload, never over a command, file change or email, whose kind phrase the runtime draws), `quote` (every top-level key an opaque payload may carry, mapped to its label or `~` for deliberately not shown, which the card then names under the box; at least one key shown; any other key sends the technical card) and `note` (`summary` or `none`: whether the agent's own summary is shown, labelled "not checked"). Read only under `style: minimal`, for each card as it is drawn (§5.2, APRV-489). |
351
366
  | `channels.<other>` | An unknown channel name is accepted as an object, so a third-party transport does not fail the whole policy closed (§10.3). A `prompt` block written under such a name is still validated: a layout is checked wherever it appears. |
352
367
 
353
368
  And the values block (SPEC.md §5.3), which is guidance and reaches no