approval-md 0.4.1 → 0.4.3

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 (118) hide show
  1. package/SPEC.md +8 -0
  2. package/dist/src/cli/attest.js +2 -1
  3. package/dist/src/cli/attest.js.map +1 -1
  4. package/dist/src/cli/audit.js +3 -2
  5. package/dist/src/cli/audit.js.map +1 -1
  6. package/dist/src/cli/channel-telegram.js +2 -1
  7. package/dist/src/cli/channel-telegram.js.map +1 -1
  8. package/dist/src/cli/channel-web.js +2 -1
  9. package/dist/src/cli/channel-web.js.map +1 -1
  10. package/dist/src/cli/channel.js +2 -1
  11. package/dist/src/cli/channel.js.map +1 -1
  12. package/dist/src/cli/doctor.js +1 -1
  13. package/dist/src/cli/doctor.js.map +1 -1
  14. package/dist/src/cli/execute.js +4 -3
  15. package/dist/src/cli/execute.js.map +1 -1
  16. package/dist/src/cli/gate.js +2 -1
  17. package/dist/src/cli/gate.js.map +1 -1
  18. package/dist/src/cli/hook.d.ts +32 -2
  19. package/dist/src/cli/hook.js +510 -36
  20. package/dist/src/cli/hook.js.map +1 -1
  21. package/dist/src/cli/log-checkpoint.js +2 -1
  22. package/dist/src/cli/log-checkpoint.js.map +1 -1
  23. package/dist/src/cli/log-unlock.js +2 -1
  24. package/dist/src/cli/log-unlock.js.map +1 -1
  25. package/dist/src/cli/setup-common.js +2 -1
  26. package/dist/src/cli/setup-common.js.map +1 -1
  27. package/dist/src/cli/vault.js +2 -1
  28. package/dist/src/cli/vault.js.map +1 -1
  29. package/dist/src/cli/wordmark.d.ts +2 -2
  30. package/dist/src/cli/wordmark.js +2 -2
  31. package/dist/src/core/attest-bootstrap.js +2 -1
  32. package/dist/src/core/attest-bootstrap.js.map +1 -1
  33. package/dist/src/core/attest.js +5 -4
  34. package/dist/src/core/attest.js.map +1 -1
  35. package/dist/src/core/audit.d.ts +15 -0
  36. package/dist/src/core/audit.js +19 -3
  37. package/dist/src/core/audit.js.map +1 -1
  38. package/dist/src/core/budgets.d.ts +12 -0
  39. package/dist/src/core/budgets.js +54 -1
  40. package/dist/src/core/budgets.js.map +1 -1
  41. package/dist/src/core/checkpoint.js +3 -2
  42. package/dist/src/core/checkpoint.js.map +1 -1
  43. package/dist/src/core/command-class.d.ts +57 -0
  44. package/dist/src/core/command-class.js +188 -10
  45. package/dist/src/core/command-class.js.map +1 -1
  46. package/dist/src/core/decision-refusal.js +2 -1
  47. package/dist/src/core/decision-refusal.js.map +1 -1
  48. package/dist/src/core/delegation.d.ts +109 -0
  49. package/dist/src/core/delegation.js +244 -0
  50. package/dist/src/core/delegation.js.map +1 -0
  51. package/dist/src/core/execute.js +12 -3
  52. package/dist/src/core/execute.js.map +1 -1
  53. package/dist/src/core/gate-window.js +4 -3
  54. package/dist/src/core/gate-window.js.map +1 -1
  55. package/dist/src/core/gate.d.ts +26 -42
  56. package/dist/src/core/gate.js +84 -3
  57. package/dist/src/core/gate.js.map +1 -1
  58. package/dist/src/core/gesture-refusal.js +2 -1
  59. package/dist/src/core/gesture-refusal.js.map +1 -1
  60. package/dist/src/core/identity.d.ts +105 -0
  61. package/dist/src/core/identity.js +122 -0
  62. package/dist/src/core/identity.js.map +1 -0
  63. package/dist/src/core/live-draw.d.ts +12 -2
  64. package/dist/src/core/live-draw.js +12 -2
  65. package/dist/src/core/live-draw.js.map +1 -1
  66. package/dist/src/core/loop.d.ts +11 -0
  67. package/dist/src/core/loop.js +63 -4
  68. package/dist/src/core/loop.js.map +1 -1
  69. package/dist/src/core/policy-diff.js +20 -1
  70. package/dist/src/core/policy-diff.js.map +1 -1
  71. package/dist/src/core/policy-explain.js +18 -2
  72. package/dist/src/core/policy-explain.js.map +1 -1
  73. package/dist/src/core/policy-load.d.ts +38 -1
  74. package/dist/src/core/policy-load.js +34 -2
  75. package/dist/src/core/policy-load.js.map +1 -1
  76. package/dist/src/core/policy-match.js +30 -5
  77. package/dist/src/core/policy-match.js.map +1 -1
  78. package/dist/src/core/policy-proposal.js +3 -2
  79. package/dist/src/core/policy-proposal.js.map +1 -1
  80. package/dist/src/core/token.js +11 -0
  81. package/dist/src/core/token.js.map +1 -1
  82. package/dist/src/core/tool-map.d.ts +125 -0
  83. package/dist/src/core/tool-map.js +179 -0
  84. package/dist/src/core/tool-map.js.map +1 -0
  85. package/dist/src/daemon/git-evidence.d.ts +2 -2
  86. package/dist/src/daemon/git-evidence.js +1 -1
  87. package/docs/README-extended.md +13 -0
  88. package/docs/cli-reference.md +64 -3
  89. package/package.json +2 -2
  90. package/schema/event.schema.json +34 -2
  91. package/schema/fixtures/event/invalid/audit-reviewed-model-actor.json +20 -0
  92. package/schema/fixtures/event/invalid/audit-reviewed-verdict-source-model.json +20 -0
  93. package/schema/fixtures/event/invalid/execution-started-harness-tool-space.json +18 -0
  94. package/schema/fixtures/event/valid/execution-started-harness-tool.json +19 -0
  95. package/schema/fixtures/policy/invalid/delegation-advice-string.json +15 -0
  96. package/schema/fixtures/policy/invalid/delegation-class-duplicate.json +18 -0
  97. package/schema/fixtures/policy/invalid/delegation-class-wildcard.json +17 -0
  98. package/schema/fixtures/policy/invalid/delegation-daily-cap-fractional.json +15 -0
  99. package/schema/fixtures/policy/invalid/delegation-daily-cap-negative.json +15 -0
  100. package/schema/fixtures/policy/invalid/delegation-daily-cap-too-high.json +15 -0
  101. package/schema/fixtures/policy/invalid/delegation-escalate-duplicate.json +18 -0
  102. package/schema/fixtures/policy/invalid/delegation-escalate-unknown.json +17 -0
  103. package/schema/fixtures/policy/invalid/delegation-max-autonomy-alias.json +15 -0
  104. package/schema/fixtures/policy/invalid/delegation-max-autonomy-autonomous.json +15 -0
  105. package/schema/fixtures/policy/invalid/delegation-model-no-version.json +15 -0
  106. package/schema/fixtures/policy/invalid/delegation-model-provider-form.json +15 -0
  107. package/schema/fixtures/policy/invalid/delegation-null.json +7 -0
  108. package/schema/fixtures/policy/invalid/delegation-reviewer-agent.json +17 -0
  109. package/schema/fixtures/policy/invalid/delegation-reviewer-bare-id.json +17 -0
  110. package/schema/fixtures/policy/invalid/delegation-unknown-key.json +16 -0
  111. package/schema/fixtures/policy/invalid/tools-class-wildcard.json +9 -0
  112. package/schema/fixtures/policy/invalid/tools-match-double-star.json +9 -0
  113. package/schema/fixtures/policy/invalid/unmapped-tool-unknown.json +7 -0
  114. package/schema/fixtures/policy/valid/delegation-empty.json +7 -0
  115. package/schema/fixtures/policy/valid/delegation-grammar-full.json +41 -0
  116. package/schema/fixtures/policy/valid/delegation-off.json +15 -0
  117. package/schema/fixtures/policy/valid/tools-mapping.json +17 -0
  118. package/schema/policy.schema.json +126 -0
@@ -41,6 +41,12 @@
41
41
  "default": "manual",
42
42
  "examples": ["manual", "sealed"]
43
43
  },
44
+ "unmapped_tool": {
45
+ "type": "string",
46
+ "enum": ["record", "ask"],
47
+ "description": "SPEC.md §5.2 (APRV-499, proposed): what a harness hook does with a tool call that neither the harness adapter's own tables (its shell, file, read and pass-through tools and its hard-coded rule table) nor any `tools` entry claims. `record`: the call is classified `harness.tool.unmapped`, which resolves `autonomous` when no `classes` rule matches it, so it proceeds and leaves an `execution.started` naming the tool. `ask`: the same class resolves `manual` when no rule matches it, so the call waits for a human. A `classes` rule matching `harness.tool.unmapped` decides it instead, exactly as a rule decides any class over `defaults.autonomy`. ABSENT, the behaviour of every policy written before the key existed: such a call is not a gate question, is allowed, and leaves no record.",
48
+ "examples": ["record", "ask"]
49
+ },
44
50
  "on_expiry": {
45
51
  "type": "string",
46
52
  "enum": ["reject"],
@@ -85,6 +91,112 @@
85
91
  ]
86
92
  ]
87
93
  },
94
+ "tools": {
95
+ "type": "array",
96
+ "description": "SPEC.md §5.2 (APRV-499, proposed): the classes a harness's OWN tool calls are judged under, by tool name, for the calls the harness adapter's built-in tables do not claim (every MCP tool, `mcp__<server>__<tool>` on Claude Code, among them). ORDERED: the first entry whose `match` matches the tool name decides, and later entries are not read. The adapter's own tables keep precedence over every entry, because they read the call's arguments and an entry reads only its name: an entry matching `terminal`, `Bash` or a Hermes `cronjob_manage` is inert. An entry's `class` must be declared by `classes`, as an exact key or under a trailing family key `<prefix>.*` whose literal prefix it extends (checked at load); an undeclared class fails the policy closed, because a mapping to a class nothing prices would resolve by `defaults.autonomy` and read as a decision nobody made. Absent or empty: no tool is mapped.",
97
+ "items": {
98
+ "type": "object",
99
+ "additionalProperties": false,
100
+ "required": ["match", "class"],
101
+ "properties": {
102
+ "match": {
103
+ "type": "string",
104
+ "minLength": 1,
105
+ "maxLength": 256,
106
+ "pattern": "^(?:[A-Za-z0-9_.:-]|\\*(?!\\*))+$",
107
+ "description": "The harness tool name this entry claims, compared case-sensitively against the WHOLE name. `*` is the only wildcard and matches any run of characters, including none, so `mcp__contextsling__*` claims every tool of that MCP server and `*` claims every tool nothing earlier claimed. Two `*` in a row, and every character outside letters, digits, `_`, `.`, `:` and `-`, are schema violations: a pattern language half-implemented here would be a mapping the author believed was in force and the runtime did not apply.",
108
+ "examples": ["mcp__contextsling__*", "mcp__zzz__post", "web_search"]
109
+ },
110
+ "class": {
111
+ "type": "string",
112
+ "minLength": 1,
113
+ "maxLength": 256,
114
+ "pattern": "^[a-z0-9][a-z0-9_-]*(?:\\.[a-z0-9][a-z0-9_-]*)*$",
115
+ "description": "The concrete class (no wildcard) the call is judged under. It must be an exact key of `classes` or extend the literal prefix of a declared trailing family key (`marketplace.*` covers `marketplace.contextsling.publish`), and it may not be `harness.tool.unmapped`, which names the calls no entry claimed (both checked at load).",
116
+ "examples": ["marketplace.contextsling.publish", "marketplace.app.read"]
117
+ }
118
+ }
119
+ },
120
+ "examples": [
121
+ [
122
+ { "match": "mcp__contextsling__publish", "class": "marketplace.contextsling.publish" },
123
+ { "match": "mcp__contextsling__*", "class": "marketplace.app.read" }
124
+ ]
125
+ ]
126
+ },
127
+ "delegation": {
128
+ "type": "object",
129
+ "additionalProperties": false,
130
+ "description": "SPEC.md §5.2 (APRV-500, proposed; RESERVED in 0.4.2, no behaviour): the judge, a pinned model that may later review supervised samples, advise on manual cards, and decide manual cards in named classes within bounds. This release parses and validates the block and does nothing with it. It loads only in its OFF form (`model` null or absent, `classes` empty, `max_autonomy: manual`, `daily_cap: 0`, `escalate_on` empty, `advice: false`, `reviewers` empty; an empty mapping is the same thing), and the off form changes no resolution, no request and no record. ANY other value fails the policy closed with the load code `delegation-not-supported`, so every class resolves `manual`: a core that accepted `daily_cap: 10` and did nothing would be a setting its author believed was in force and the runtime silently ignored, which this closed schema exists to prevent. The relationships between keys (classes are exact keys of `classes`, the `max_autonomy` pin, the escalation floors, reviewer identities) are checked at load before the off-form check and fail closed as `schema-invalid`. Absent: the behaviour of every policy written before the key existed.",
131
+ "properties": {
132
+ "model": {
133
+ "anyOf": [{ "$ref": "#/$defs/modelIdentity" }, { "type": "null" }],
134
+ "description": "The judge identity whose records a later core would accept, `model:<name>@<major>.<minor>.<patch>`. The version pins the judge release (prompt, rubric and model snapshot). `null` or absent: no judge, and every other key is inert. RESERVED: any non-null value is `delegation-not-supported` in 0.4.2.",
135
+ "examples": [null, "model:judge@0.3.0"]
136
+ },
137
+ "classes": {
138
+ "type": "array",
139
+ "uniqueItems": true,
140
+ "items": {
141
+ "type": "string",
142
+ "minLength": 1,
143
+ "maxLength": 256,
144
+ "pattern": "^[a-z0-9][a-z0-9_-]*(?:\\.[a-z0-9][a-z0-9_-]*)*$"
145
+ },
146
+ "description": "The classes the judge may act on. Each entry is a concrete class (no wildcard, no `*`) and must be an EXACT key of `classes` whose own autonomy is neither `human-only` nor `autonomous` (checked at load): the delegated set is a set of lines the operator wrote by name, the reasoning `agent_may_request` uses. RESERVED: a non-empty list is `delegation-not-supported` in 0.4.2.",
147
+ "examples": [[], ["intent.publish.inferred.index"]]
148
+ },
149
+ "max_autonomy": {
150
+ "type": "string",
151
+ "enum": ["manual", "supervised-live", "supervised-retro"],
152
+ "default": "manual",
153
+ "description": "A non-loosening PIN between this block and the class rows. Every class listed in `classes` must declare an autonomy at least as strict as this value in SPEC.md §5.2's strictness order (checked at load, `schema-invalid` otherwise). It is a tamper pin rather than a dial: loosening a delegated class's row without rewriting this block fails the load, so the judge's scope never grows as a side effect of an unrelated edit. Absent means `manual`. RESERVED: any value other than `manual` is `delegation-not-supported` in 0.4.2.",
154
+ "examples": ["manual"]
155
+ },
156
+ "daily_cap": {
157
+ "type": "integer",
158
+ "minimum": 0,
159
+ "maximum": 1000,
160
+ "default": 0,
161
+ "description": "The most judge grants in any rolling 24 hours, across every delegated class together. 0 means the delegated approver is off. A value above 0 requires `escalate_on` to contain `irreversible` and `unknown_class` and requires `model` (checked at load). RESERVED: any value other than 0 is `delegation-not-supported` in 0.4.2.",
162
+ "examples": [0]
163
+ },
164
+ "escalate_on": {
165
+ "type": "array",
166
+ "uniqueItems": true,
167
+ "items": {
168
+ "type": "string",
169
+ "enum": ["deny", "low_confidence", "irreversible", "unknown_class"]
170
+ },
171
+ "description": "The judge outcomes that go to a human instead of standing, from a fixed set. RESERVED: a non-empty list is `delegation-not-supported` in 0.4.2.",
172
+ "examples": [[], ["deny", "low_confidence", "irreversible", "unknown_class"]]
173
+ },
174
+ "advice": {
175
+ "type": "boolean",
176
+ "default": false,
177
+ "description": "Whether the judge writes advice on manual cards in `classes`. `true` requires `model` (checked at load). RESERVED: `true` is `delegation-not-supported` in 0.4.2.",
178
+ "examples": [false]
179
+ },
180
+ "reviewers": {
181
+ "type": "array",
182
+ "uniqueItems": true,
183
+ "items": { "$ref": "#/$defs/delegationReviewer" },
184
+ "description": "Identities admitted to review samples of `classes`, in addition to each class's `approvers` roster: `human:<approver id>`, naming a key of `approvers`, or `model:<name>@<version>`, which must equal `model` exactly (both checked at load). This list is the ONLY place a `model:` identity is admitted: grants, attestations, reviews and channel decisions still require `human:`. RESERVED: a non-empty list is `delegation-not-supported` in 0.4.2.",
185
+ "examples": [[], ["model:judge@0.3.0", "human:alice"]]
186
+ }
187
+ },
188
+ "examples": [
189
+ {
190
+ "model": null,
191
+ "classes": [],
192
+ "max_autonomy": "manual",
193
+ "daily_cap": 0,
194
+ "escalate_on": [],
195
+ "advice": false,
196
+ "reviewers": []
197
+ }
198
+ ]
199
+ },
88
200
  "read_scope": {
89
201
  "type": "object",
90
202
  "additionalProperties": false,
@@ -333,6 +445,20 @@
333
445
  }
334
446
  },
335
447
  "$defs": {
448
+ "modelIdentity": {
449
+ "type": "string",
450
+ "maxLength": 128,
451
+ "pattern": "^model:[a-z0-9][a-z0-9-]{0,63}@[0-9]+\\.[0-9]+\\.[0-9]+$",
452
+ "description": "SPEC.md §5.2 (APRV-500, proposed; RESERVED): a judge identity, `model:<name>@<major>.<minor>.<patch>`. `<name>` is the judge service (lowercase alphanumerics and `-`, at most 64 characters) and the version pins one judge release. No record written by this release carries one: the event schema's actor patterns are unchanged, so a `model:` actor is refused at the write boundary.",
453
+ "examples": ["model:judge@0.3.0"]
454
+ },
455
+ "delegationReviewer": {
456
+ "type": "string",
457
+ "maxLength": 128,
458
+ "pattern": "^(?:human:[a-z0-9][a-z0-9_-]*|model:[a-z0-9][a-z0-9-]{0,63}@[0-9]+\\.[0-9]+\\.[0-9]+)$",
459
+ "description": "SPEC.md §5.2 (APRV-500, proposed; RESERVED): a reviewer identity in `delegation.reviewers`. `human:<approver id>` (the approver identifier grammar) or a `modelIdentity`.",
460
+ "examples": ["human:alice", "model:judge@0.3.0"]
461
+ },
336
462
  "promptRow": {
337
463
  "type": "string",
338
464
  "enum": [