approval-md 0.3.0 → 0.4.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.
- package/README.md +83 -914
- package/SPEC.md +98 -13
- package/cli.js +66 -7
- package/dist/src/adapters/smtp.d.ts +18 -0
- package/dist/src/adapters/smtp.js +24 -3
- package/dist/src/adapters/smtp.js.map +1 -1
- package/dist/src/channels/contract.d.ts +19 -0
- package/dist/src/channels/contract.js +105 -1
- package/dist/src/channels/contract.js.map +1 -1
- package/dist/src/channels/relay-server.d.ts +90 -0
- package/dist/src/channels/relay-server.js +295 -0
- package/dist/src/channels/relay-server.js.map +1 -0
- package/dist/src/channels/relay.d.ts +273 -0
- package/dist/src/channels/relay.js +564 -0
- package/dist/src/channels/relay.js.map +1 -0
- package/dist/src/channels/tagging.js +6 -1
- package/dist/src/channels/tagging.js.map +1 -1
- package/dist/src/channels/telegram-webhook.d.ts +273 -0
- package/dist/src/channels/telegram-webhook.js +536 -0
- package/dist/src/channels/telegram-webhook.js.map +1 -0
- package/dist/src/channels/telegram.d.ts +271 -1
- package/dist/src/channels/telegram.js +403 -45
- package/dist/src/channels/telegram.js.map +1 -1
- package/dist/src/cli/amend.js +350 -4
- package/dist/src/cli/amend.js.map +1 -1
- package/dist/src/cli/attest.js +43 -6
- package/dist/src/cli/attest.js.map +1 -1
- package/dist/src/cli/channel-relay.d.ts +108 -0
- package/dist/src/cli/channel-relay.js +334 -0
- package/dist/src/cli/channel-relay.js.map +1 -0
- package/dist/src/cli/channel-telegram-webhook.d.ts +273 -0
- package/dist/src/cli/channel-telegram-webhook.js +709 -0
- package/dist/src/cli/channel-telegram-webhook.js.map +1 -0
- package/dist/src/cli/channel-telegram.d.ts +272 -26
- package/dist/src/cli/channel-telegram.js +478 -41
- package/dist/src/cli/channel-telegram.js.map +1 -1
- package/dist/src/cli/channel.d.ts +1 -1
- package/dist/src/cli/channel.js +5 -1
- package/dist/src/cli/channel.js.map +1 -1
- package/dist/src/cli/daemon.js +37 -5
- package/dist/src/cli/daemon.js.map +1 -1
- package/dist/src/cli/doctor.d.ts +63 -0
- package/dist/src/cli/doctor.js +856 -111
- package/dist/src/cli/doctor.js.map +1 -1
- package/dist/src/cli/execute.d.ts +17 -1
- package/dist/src/cli/execute.js +341 -20
- package/dist/src/cli/execute.js.map +1 -1
- package/dist/src/cli/exit-codes.d.ts +12 -0
- package/dist/src/cli/exit-codes.js +13 -0
- package/dist/src/cli/exit-codes.js.map +1 -1
- package/dist/src/cli/gate.d.ts +2 -0
- package/dist/src/cli/gate.js +233 -2
- package/dist/src/cli/gate.js.map +1 -1
- package/dist/src/cli/help.d.ts +9 -1
- package/dist/src/cli/help.js +246 -38
- package/dist/src/cli/help.js.map +1 -1
- package/dist/src/cli/hook.d.ts +371 -3
- package/dist/src/cli/hook.js +1926 -44
- package/dist/src/cli/hook.js.map +1 -1
- package/dist/src/cli/init.js +16 -7
- package/dist/src/cli/init.js.map +1 -1
- package/dist/src/cli/instructions.js +24 -3
- package/dist/src/cli/instructions.js.map +1 -1
- package/dist/src/cli/log-anchor.d.ts +55 -0
- package/dist/src/cli/log-anchor.js +199 -60
- package/dist/src/cli/log-anchor.js.map +1 -1
- package/dist/src/cli/main.d.ts +0 -12
- package/dist/src/cli/main.js +51 -0
- package/dist/src/cli/main.js.map +1 -1
- package/dist/src/cli/muse.d.ts +2 -0
- package/dist/src/cli/muse.js +62 -0
- package/dist/src/cli/muse.js.map +1 -0
- package/dist/src/cli/payload.js +79 -1
- package/dist/src/cli/payload.js.map +1 -1
- package/dist/src/cli/scaffold.js +6 -0
- package/dist/src/cli/scaffold.js.map +1 -1
- package/dist/src/cli/serve.d.ts +53 -0
- package/dist/src/cli/serve.js +270 -0
- package/dist/src/cli/serve.js.map +1 -0
- package/dist/src/cli/up.js +21 -2
- package/dist/src/cli/up.js.map +1 -1
- package/dist/src/cli/verb-registry.d.ts +2 -0
- package/dist/src/cli/verb-registry.js +337 -39
- package/dist/src/cli/verb-registry.js.map +1 -1
- package/dist/src/cli/wordmark.d.ts +2 -2
- package/dist/src/cli/wordmark.js +2 -2
- package/dist/src/core/attest-bootstrap.d.ts +95 -0
- package/dist/src/core/attest-bootstrap.js +135 -0
- package/dist/src/core/attest-bootstrap.js.map +1 -0
- package/dist/src/core/attest.d.ts +20 -3
- package/dist/src/core/attest.js +25 -0
- package/dist/src/core/attest.js.map +1 -1
- package/dist/src/core/audit.d.ts +55 -3
- package/dist/src/core/audit.js +52 -5
- package/dist/src/core/audit.js.map +1 -1
- package/dist/src/core/channel-lease.d.ts +322 -0
- package/dist/src/core/channel-lease.js +663 -0
- package/dist/src/core/channel-lease.js.map +1 -0
- package/dist/src/core/command-class.d.ts +51 -0
- package/dist/src/core/command-class.js +760 -8
- package/dist/src/core/command-class.js.map +1 -1
- package/dist/src/core/daemon-host.d.ts +178 -0
- package/dist/src/core/daemon-host.js +228 -0
- package/dist/src/core/daemon-host.js.map +1 -0
- package/dist/src/core/daemon-identity.d.ts +238 -0
- package/dist/src/core/daemon-identity.js +252 -0
- package/dist/src/core/daemon-identity.js.map +1 -0
- package/dist/src/core/gate.d.ts +306 -5
- package/dist/src/core/gate.js +772 -24
- package/dist/src/core/gate.js.map +1 -1
- package/dist/src/core/harness-version.d.ts +128 -4
- package/dist/src/core/harness-version.js +179 -12
- package/dist/src/core/harness-version.js.map +1 -1
- package/dist/src/core/harness-wait.d.ts +82 -3
- package/dist/src/core/harness-wait.js +89 -3
- package/dist/src/core/harness-wait.js.map +1 -1
- package/dist/src/core/log-subscribe.d.ts +21 -0
- package/dist/src/core/log-subscribe.js +16 -6
- package/dist/src/core/log-subscribe.js.map +1 -1
- package/dist/src/core/log-write-layer.d.ts +55 -0
- package/dist/src/core/log-write-layer.js +91 -0
- package/dist/src/core/log-write-layer.js.map +1 -0
- package/dist/src/core/log.d.ts +50 -21
- package/dist/src/core/log.js +197 -19
- package/dist/src/core/log.js.map +1 -1
- package/dist/src/core/payload-census.d.ts +51 -0
- package/dist/src/core/payload-census.js +125 -2
- package/dist/src/core/payload-census.js.map +1 -1
- package/dist/src/core/payload-store.d.ts +31 -0
- package/dist/src/core/payload-store.js +113 -8
- package/dist/src/core/payload-store.js.map +1 -1
- package/dist/src/core/payload.d.ts +18 -3
- package/dist/src/core/payload.js +20 -4
- package/dist/src/core/payload.js.map +1 -1
- package/dist/src/core/policy-diff.d.ts +10 -0
- package/dist/src/core/policy-diff.js +10 -2
- package/dist/src/core/policy-diff.js.map +1 -1
- package/dist/src/core/policy-explain.d.ts +7 -1
- package/dist/src/core/policy-explain.js +16 -1
- package/dist/src/core/policy-explain.js.map +1 -1
- package/dist/src/core/policy-load.d.ts +24 -0
- package/dist/src/core/policy-load.js.map +1 -1
- package/dist/src/core/policy-match.d.ts +38 -0
- package/dist/src/core/policy-match.js +35 -0
- package/dist/src/core/policy-match.js.map +1 -1
- package/dist/src/core/policy-proposal.d.ts +23 -0
- package/dist/src/core/policy-proposal.js +8 -2
- package/dist/src/core/policy-proposal.js.map +1 -1
- package/dist/src/core/read-scope.d.ts +10 -0
- package/dist/src/core/read-scope.js +4 -0
- package/dist/src/core/read-scope.js.map +1 -1
- package/dist/src/core/run-payload.d.ts +160 -0
- package/dist/src/core/run-payload.js +304 -0
- package/dist/src/core/run-payload.js.map +1 -0
- package/dist/src/core/sender-identity.d.ts +35 -10
- package/dist/src/core/sender-identity.js +48 -11
- package/dist/src/core/sender-identity.js.map +1 -1
- package/dist/src/core/state.d.ts +52 -1
- package/dist/src/core/state.js +52 -6
- package/dist/src/core/state.js.map +1 -1
- package/dist/src/core/telegram-config.d.ts +79 -0
- package/dist/src/core/telegram-config.js +108 -0
- package/dist/src/core/telegram-config.js.map +1 -1
- package/dist/src/core/verify.d.ts +23 -1
- package/dist/src/core/verify.js +20 -2
- package/dist/src/core/verify.js.map +1 -1
- package/dist/src/daemon/audit.d.ts +80 -5
- package/dist/src/daemon/audit.js +126 -6
- package/dist/src/daemon/audit.js.map +1 -1
- package/dist/src/daemon/daemon.d.ts +174 -2
- package/dist/src/daemon/daemon.js +298 -7
- package/dist/src/daemon/daemon.js.map +1 -1
- package/dist/src/daemon/git-evidence.d.ts +2 -2
- package/dist/src/daemon/git-evidence.js +1 -1
- package/dist/src/daemon/projection.d.ts +13 -2
- package/dist/src/daemon/projection.js +39 -6
- package/dist/src/daemon/projection.js.map +1 -1
- package/dist/src/mcp/server.d.ts +39 -3
- package/dist/src/mcp/server.js +22 -3
- package/dist/src/mcp/server.js.map +1 -1
- package/dist/src/muse/server.d.ts +16 -0
- package/dist/src/muse/server.js +238 -0
- package/dist/src/muse/server.js.map +1 -0
- package/dist/src/serve/archive.d.ts +158 -0
- package/dist/src/serve/archive.js +354 -0
- package/dist/src/serve/archive.js.map +1 -0
- package/dist/src/serve/arguments.d.ts +93 -0
- package/dist/src/serve/arguments.js +228 -0
- package/dist/src/serve/arguments.js.map +1 -0
- package/dist/src/serve/credentials.d.ts +91 -0
- package/dist/src/serve/credentials.js +143 -0
- package/dist/src/serve/credentials.js.map +1 -0
- package/dist/src/serve/follow.d.ts +86 -0
- package/dist/src/serve/follow.js +101 -0
- package/dist/src/serve/follow.js.map +1 -0
- package/dist/src/serve/hook-thread.d.ts +134 -0
- package/dist/src/serve/hook-thread.js +333 -0
- package/dist/src/serve/hook-thread.js.map +1 -0
- package/dist/src/serve/hook-worker.d.ts +134 -0
- package/dist/src/serve/hook-worker.js +173 -0
- package/dist/src/serve/hook-worker.js.map +1 -0
- package/dist/src/serve/server.d.ts +412 -0
- package/dist/src/serve/server.js +1251 -0
- package/dist/src/serve/server.js.map +1 -0
- package/docs/README-extended.md +766 -0
- package/docs/cli-reference.md +1633 -43
- package/package.json +4 -2
- package/schema/event.schema.json +59 -2
- package/schema/fixtures/event/invalid/daemon-id-malformed.json +15 -0
- package/schema/fixtures/event/invalid/execution-started-truncated-policy-sha256.json +18 -0
- package/schema/fixtures/event/invalid/harness-cap-at-margin.json +16 -0
- package/schema/fixtures/event/invalid/harness-cap-fractional.json +16 -0
- package/schema/fixtures/event/invalid/harness-cap-negative.json +16 -0
- package/schema/fixtures/event/invalid/harness-cap-string.json +16 -0
- package/schema/fixtures/event/invalid/harness-cap-without-harness-execution.json +16 -0
- package/schema/fixtures/event/invalid/harness-cap-zero.json +16 -0
- package/schema/fixtures/event/valid/approval-requested-harness-cap.json +18 -0
- package/schema/fixtures/event/valid/daemon-identity-declared.json +17 -0
- package/schema/fixtures/event/valid/daemon-identity.json +15 -0
- package/schema/fixtures/event/valid/execution-started-policy-sha256.json +18 -0
- package/schema/fixtures/event/valid/harness-kind-hermes.json +23 -0
- package/schema/fixtures/policy/invalid/daemons-malformed-id.json +10 -0
- package/schema/fixtures/policy/valid/daemons-allowlist.json +19 -0
- package/schema/fixtures/policy-md/invalid/schema-invalid-agent-may-request.md +21 -0
- package/schema/fixtures/policy-md/valid/agent-may-request.md +24 -0
- package/schema/policy.schema.json +50 -6
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "approval-md",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"description": "Human approval for agent actions. CLI runtime for the approval.md convention (pre-release).",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -18,6 +18,7 @@
|
|
|
18
18
|
"schema",
|
|
19
19
|
"docs/adapter-api.md",
|
|
20
20
|
"docs/cli-reference.md",
|
|
21
|
+
"docs/README-extended.md",
|
|
21
22
|
"SPEC.md",
|
|
22
23
|
"README.md",
|
|
23
24
|
"LICENSE",
|
|
@@ -57,5 +58,6 @@
|
|
|
57
58
|
"ajv-formats": "3.0.1",
|
|
58
59
|
"better-sqlite3": "12.11.1",
|
|
59
60
|
"yaml": "2.9.0"
|
|
60
|
-
}
|
|
61
|
+
},
|
|
62
|
+
"gitHead": "fb0cf987985c2b9884ebe46b7b3b4419147d057e"
|
|
61
63
|
}
|
package/schema/event.schema.json
CHANGED
|
@@ -92,6 +92,13 @@
|
|
|
92
92
|
}
|
|
93
93
|
]
|
|
94
94
|
},
|
|
95
|
+
"daemon": {
|
|
96
|
+
"type": "string",
|
|
97
|
+
"minLength": 1,
|
|
98
|
+
"maxLength": 64,
|
|
99
|
+
"pattern": "^[a-z0-9][a-z0-9._-]*$",
|
|
100
|
+
"description": "SPEC.md §8 (amended, APRV-383): WHICH daemon instance appended this record. Present exactly on the records a daemon wrote, so a tenant whose daemon is hosted by another party can open their own log and tell which host process acted on their behalf — before this field every daemon-written record carried the same generic `system:daemon` actor and nothing else. The value is either the id declared in the daemon's launch environment (`APPROVAL_DAEMON_ID`) or `daemon-` followed by the eight hex digits of the instance id `approval doctor` prints as its `keychain-scope` suffix, so it is stable across restarts on one machine and keystore without anything being stored.\n\nOPTIONAL and additive: every record written before the field existed still validates and still verifies, and the ABSENCE of the field is absence — it is never read as a claim that no daemon wrote the record, because until this task no daemon could say.\n\nSELF-REPORTED, and bounded by SPEC.md §11.1 invariant 4 in the only way a self-reported field may be: a policy MAY list the ids permitted to write (`daemons` in `policy.schema.json`), an id the list does not name is REFUSED at the write boundary (`daemon-not-allowed`), and an id the list DOES name gains nothing at all — no verdict, autonomy, budget, floor, sampling draw, token or TTL reads this field or that list. Its SHAPE is constrained here for the reason `payload.harness_version`'s is: the value comes from a launch environment and is being written into an append-only log, so a banner, a path, a newline or a shell fragment cannot arrive through it, and two ids differing only in case would be two ids nobody can tell apart on a phone."
|
|
101
|
+
},
|
|
95
102
|
"task": {
|
|
96
103
|
"type": "string",
|
|
97
104
|
"minLength": 1,
|
|
@@ -118,9 +125,10 @@
|
|
|
118
125
|
"cursor",
|
|
119
126
|
"codex",
|
|
120
127
|
"grok",
|
|
121
|
-
"muse"
|
|
128
|
+
"muse",
|
|
129
|
+
"hermes"
|
|
122
130
|
],
|
|
123
|
-
"description": "SPEC.md §10.1 (amended, APRV-227): which harness hook wrote this record. A closed set, extended only by a task that adds the case, because it is the discriminator a reader scopes `harness_version` by: one log holds the records of every harness that ever wrote to it, and a version with no binary named beside it is a string nobody can compare against anything. OPTIONAL and additive — every record written before the field existed still validates and still verifies, and the two fields travel together or not at all. APRV-358 added `grok`, which APRV-243 shipped an adapter for and never added here: a Grok session's manual-class registration failed validation at the write boundary, so the request never reached a human. Closed does not mean hand-maintained any more. This list is pinned set-equal to `HARNESS_KINDS` in `src/core/harness-version.ts` by `tests/harness-enum.test.ts`, along with every other place a harness name is enumerated, so the next adapter cannot land with the enum left behind."
|
|
131
|
+
"description": "SPEC.md §10.1 (amended, APRV-227): which harness hook wrote this record. A closed set, extended only by a task that adds the case, because it is the discriminator a reader scopes `harness_version` by: one log holds the records of every harness that ever wrote to it, and a version with no binary named beside it is a string nobody can compare against anything. OPTIONAL and additive — every record written before the field existed still validates and still verifies, and the two fields travel together or not at all. APRV-358 added `grok`, which APRV-243 shipped an adapter for and never added here: a Grok session's manual-class registration failed validation at the write boundary, so the request never reached a human. Closed does not mean hand-maintained any more. This list is pinned set-equal to `HARNESS_KINDS` in `src/core/harness-version.ts` by `tests/harness-enum.test.ts`, along with every other place a harness name is enumerated, so the next adapter cannot land with the enum left behind. APRV-398 added `hermes`, the Hermes Agent adapter, through that pinning rather than after it."
|
|
124
132
|
},
|
|
125
133
|
"harness_version": {
|
|
126
134
|
"type": "string",
|
|
@@ -833,6 +841,36 @@
|
|
|
833
841
|
}
|
|
834
842
|
}
|
|
835
843
|
},
|
|
844
|
+
{
|
|
845
|
+
"$comment": "SPEC.md §8 (amended, APRV-447, pending sign-off): a harness `execution.started` carries `policy_sha256`, the SHA-256 of the attested policy the write boundary re-checked the action's class against when it recorded the start (on the hook route the verdict was drawn from the hook's own earlier read, so a re-attest landing between the two leaves the stamp naming the policy in force at the append). Before it, a policy-authorized start (autonomous, supervised, an unselected supervised-live draw) named no policy at all, and a reader had to reconstruct the rules in force from the latest `policy.updated` before the record's seq; a granted start could reach its policy through `grant_seq` and carries the stamp too, written by the same code. The runtime assigns the value at the write boundary from its own attestation check, exactly as it assigns `policy_sha256` on `approval.requested` and `ts` on every gate-typed event: no caller parameter carries it, and a caller-supplied value of the same name is ignored. No verdict, budget, draw or refusal reads it back, so §11.1 invariant 4 is untouched. It is covered by the record hash like every other payload field. OPTIONAL and additive: every record written before the field existed still validates and still verifies, an `execution.started` written by any other path records none, and only the SHAPE is constrained here, a 64-character lowercase hex digest. Whether the hash names an attestation the log actually carries is not expressible in a schema that sees one record. The field name is a stable contract: downstream followers of the log use it to join a start to the rules it ran under.",
|
|
846
|
+
"if": {
|
|
847
|
+
"type": "object",
|
|
848
|
+
"properties": {
|
|
849
|
+
"event": {
|
|
850
|
+
"const": "execution.started"
|
|
851
|
+
}
|
|
852
|
+
},
|
|
853
|
+
"required": [
|
|
854
|
+
"event"
|
|
855
|
+
]
|
|
856
|
+
},
|
|
857
|
+
"then": {
|
|
858
|
+
"type": "object",
|
|
859
|
+
"properties": {
|
|
860
|
+
"payload": {
|
|
861
|
+
"type": "object",
|
|
862
|
+
"properties": {
|
|
863
|
+
"policy_sha256": {
|
|
864
|
+
"type": "string",
|
|
865
|
+
"pattern": "^[a-f0-9]{64}$",
|
|
866
|
+
"description": "SPEC.md §8 (amended, APRV-447): SHA-256 (lowercase hex) of the attested policy file's exact bytes the write boundary re-checked this start's class against, stamped by the runtime at the write boundary. Never a caller's parameter and never an input to a verdict.",
|
|
867
|
+
"examples": ["5f1c0de9a3b7e2d4c6f8091a2b3c4d5e6f708192a3b4c5d6e7f8091a2b3c4d5e"]
|
|
868
|
+
}
|
|
869
|
+
}
|
|
870
|
+
}
|
|
871
|
+
}
|
|
872
|
+
}
|
|
873
|
+
},
|
|
836
874
|
{
|
|
837
875
|
"$comment": "SPEC.md §10.3 (amended, APRV-38): a decision collected as part of a batch gesture carries the batch's channel delivery id as a first-class payload field. Optional — a unit decision has no batch — and constrained only when present, so the field can never arrive as an empty string or a number that audit would have to guess at.",
|
|
838
876
|
"if": {
|
|
@@ -1126,6 +1164,25 @@
|
|
|
1126
1164
|
"type": "string",
|
|
1127
1165
|
"pattern": "^[a-f0-9]{64}$",
|
|
1128
1166
|
"description": "SPEC.md §9/§10.3 (APRV-119): SHA-256 (lowercase hex) over the UTF-8 text of the canonical rendering of this request's payload, as produced by the versioned deterministic renderer named inside that text."
|
|
1167
|
+
},
|
|
1168
|
+
"harness_cap_ms": {
|
|
1169
|
+
"type": "integer",
|
|
1170
|
+
"minimum": 60001,
|
|
1171
|
+
"maximum": 86400000,
|
|
1172
|
+
"description": "SPEC.md §6 (APRV-423): the ceiling, in milliseconds, that the harness running the requesting hook imposes on that hook's own process — Hermes caps a `pre_tool_call` entry at 300 s, Claude Code kills the hook at the `timeout` in its settings file. A BOUNDED INPUT and never a deadline of its own: the runtime judges the request against the SHORTER of this minus a fixed margin and the policy's `defaults.approval_ttl`, so a stated cap can only move the lapse EARLIER (§11.1 invariant 4). The minimum is HARNESS_CAP_MARGIN_MS + 1 (src/core/harness-wait.ts, 60 000 ms; tests/harness-cap-ttl.test.ts pins the two equal): a cap at or below the margin leaves a zero-length window, a question that lapses as it is asked, and the hook refuses such a configuration before it appends, so a record carrying one could only come from a caller that skipped the hook and is refused here. Above 24 h is a number no harness contract states."
|
|
1173
|
+
}
|
|
1174
|
+
},
|
|
1175
|
+
"dependentSchemas": {
|
|
1176
|
+
"harness_cap_ms": {
|
|
1177
|
+
"$comment": "APRV-423. A record carrying a harness ceiling MUST also declare `execution: \"harness\"`. The ceiling shortens the window in which this request may be decided, and a request that mints an execution token would otherwise hold an authorization with a longer shelf life than the question that produced it. A schema sees one record, which is exactly enough to see both fields, so the pairing is refused at the write boundary rather than left to a caller's discipline.",
|
|
1178
|
+
"properties": {
|
|
1179
|
+
"execution": {
|
|
1180
|
+
"const": "harness"
|
|
1181
|
+
}
|
|
1182
|
+
},
|
|
1183
|
+
"required": [
|
|
1184
|
+
"execution"
|
|
1185
|
+
]
|
|
1129
1186
|
}
|
|
1130
1187
|
}
|
|
1131
1188
|
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
{
|
|
2
|
+
"seq": 63,
|
|
3
|
+
"ts": "2026-09-20T11:06:02Z",
|
|
4
|
+
"event": "audit.sampled",
|
|
5
|
+
"task": "task-311",
|
|
6
|
+
"action_key": "task-311:digest:2026-09-20",
|
|
7
|
+
"actor": "system:auditor",
|
|
8
|
+
"daemon": "Village Goa 1\nrate: 1.0",
|
|
9
|
+
"payload": {
|
|
10
|
+
"rate": 0.1
|
|
11
|
+
},
|
|
12
|
+
"alg": "sha256/jcs",
|
|
13
|
+
"prev": "07453feb30c2b0b1a2806398cdd17b38d98f9abf52b8a75ca42878c9101d8264",
|
|
14
|
+
"hash": "9d4c0be1a7fb25308c6ef4a1d0b39724ce85f16a2b0d7c39e8145af62b0c73d8"
|
|
15
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
{
|
|
2
|
+
"seq": 9,
|
|
3
|
+
"ts": "2026-10-04T09:19:02Z",
|
|
4
|
+
"event": "execution.started",
|
|
5
|
+
"task": "hook:sess-1:tu-1",
|
|
6
|
+
"action_key": "hook:sess-1:tu-1:files.write.workspace",
|
|
7
|
+
"actor": "agent:claude-code",
|
|
8
|
+
"payload": {
|
|
9
|
+
"class": "files.write.workspace",
|
|
10
|
+
"est_cost_usd": "0",
|
|
11
|
+
"execution": "harness",
|
|
12
|
+
"payload_hash": "9f2b1c4d5e6f708192a3b4c5d6e7f8091a2b3c4d5e6f708192a3b4c5d6e7f809",
|
|
13
|
+
"policy_sha256": "5f1c0de9a3b7"
|
|
14
|
+
},
|
|
15
|
+
"alg": "sha256/jcs",
|
|
16
|
+
"prev": "d973375d6aeec5e6cd2de3a0f7dc8f5bc1d9be77225f9af0f851e7fc519a07c3",
|
|
17
|
+
"hash": "707b79cfa14ec43224c8f71a4fee3ddbff52369f82525aab19bec18c369d5cad"
|
|
18
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
{
|
|
2
|
+
"seq": 76,
|
|
3
|
+
"ts": "2026-09-22T08:34:20Z",
|
|
4
|
+
"event": "approval.requested",
|
|
5
|
+
"task": "hook:hermes-sess-1:tool-12",
|
|
6
|
+
"action_key": "hook:hermes-sess-1:tool-12:deps.add",
|
|
7
|
+
"actor": "agent:hermes",
|
|
8
|
+
"payload": {
|
|
9
|
+
"class": "deps.add",
|
|
10
|
+
"execution": "harness",
|
|
11
|
+
"harness_cap_ms": 60000
|
|
12
|
+
},
|
|
13
|
+
"alg": "sha256/jcs",
|
|
14
|
+
"prev": "1515151515151515151515151515151515151515151515151515151515151515",
|
|
15
|
+
"hash": "1616161616161616161616161616161616161616161616161616161616161616"
|
|
16
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
{
|
|
2
|
+
"seq": 74,
|
|
3
|
+
"ts": "2026-09-22T08:33:19Z",
|
|
4
|
+
"event": "approval.requested",
|
|
5
|
+
"task": "hook:hermes-sess-1:tool-10",
|
|
6
|
+
"action_key": "hook:hermes-sess-1:tool-10:deps.add",
|
|
7
|
+
"actor": "agent:hermes",
|
|
8
|
+
"payload": {
|
|
9
|
+
"class": "deps.add",
|
|
10
|
+
"execution": "harness",
|
|
11
|
+
"harness_cap_ms": 299999.5
|
|
12
|
+
},
|
|
13
|
+
"alg": "sha256/jcs",
|
|
14
|
+
"prev": "1313131313131313131313131313131313131313131313131313131313131313",
|
|
15
|
+
"hash": "1414141414141414141414141414141414141414141414141414141414141414"
|
|
16
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
{
|
|
2
|
+
"seq": 73,
|
|
3
|
+
"ts": "2026-09-22T08:32:44Z",
|
|
4
|
+
"event": "approval.requested",
|
|
5
|
+
"task": "hook:hermes-sess-1:tool-9",
|
|
6
|
+
"action_key": "hook:hermes-sess-1:tool-9:deps.add",
|
|
7
|
+
"actor": "agent:hermes",
|
|
8
|
+
"payload": {
|
|
9
|
+
"class": "deps.add",
|
|
10
|
+
"execution": "harness",
|
|
11
|
+
"harness_cap_ms": -300000
|
|
12
|
+
},
|
|
13
|
+
"alg": "sha256/jcs",
|
|
14
|
+
"prev": "1212121212121212121212121212121212121212121212121212121212121212",
|
|
15
|
+
"hash": "1313131313131313131313131313131313131313131313131313131313131313"
|
|
16
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
{
|
|
2
|
+
"seq": 75,
|
|
3
|
+
"ts": "2026-09-22T08:33:52Z",
|
|
4
|
+
"event": "approval.requested",
|
|
5
|
+
"task": "hook:hermes-sess-1:tool-11",
|
|
6
|
+
"action_key": "hook:hermes-sess-1:tool-11:deps.add",
|
|
7
|
+
"actor": "agent:hermes",
|
|
8
|
+
"payload": {
|
|
9
|
+
"class": "deps.add",
|
|
10
|
+
"execution": "harness",
|
|
11
|
+
"harness_cap_ms": "300s"
|
|
12
|
+
},
|
|
13
|
+
"alg": "sha256/jcs",
|
|
14
|
+
"prev": "1414141414141414141414141414141414141414141414141414141414141414",
|
|
15
|
+
"hash": "1515151515151515151515151515151515151515151515151515151515151515"
|
|
16
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
{
|
|
2
|
+
"seq": 77,
|
|
3
|
+
"ts": "2026-09-22T08:34:51Z",
|
|
4
|
+
"event": "approval.requested",
|
|
5
|
+
"task": "hook:hermes-sess-1:tool-13",
|
|
6
|
+
"action_key": "hook:hermes-sess-1:tool-13:deps.add",
|
|
7
|
+
"actor": "agent:hermes",
|
|
8
|
+
"payload": {
|
|
9
|
+
"class": "deps.add",
|
|
10
|
+
"harness_cap_ms": 300000,
|
|
11
|
+
"payload_hash": "5f1c2a6f3e9d4b8a7c0e1d2b3a4958677889aabbccddeeff0011223344556677"
|
|
12
|
+
},
|
|
13
|
+
"alg": "sha256/jcs",
|
|
14
|
+
"prev": "1616161616161616161616161616161616161616161616161616161616161616",
|
|
15
|
+
"hash": "1717171717171717171717171717171717171717171717171717171717171717"
|
|
16
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
{
|
|
2
|
+
"seq": 72,
|
|
3
|
+
"ts": "2026-09-22T08:32:01Z",
|
|
4
|
+
"event": "approval.requested",
|
|
5
|
+
"task": "hook:hermes-sess-1:tool-8",
|
|
6
|
+
"action_key": "hook:hermes-sess-1:tool-8:deps.add",
|
|
7
|
+
"actor": "agent:hermes",
|
|
8
|
+
"payload": {
|
|
9
|
+
"class": "deps.add",
|
|
10
|
+
"execution": "harness",
|
|
11
|
+
"harness_cap_ms": 0
|
|
12
|
+
},
|
|
13
|
+
"alg": "sha256/jcs",
|
|
14
|
+
"prev": "1111111111111111111111111111111111111111111111111111111111111111",
|
|
15
|
+
"hash": "1212121212121212121212121212121212121212121212121212121212121212"
|
|
16
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
{
|
|
2
|
+
"seq": 71,
|
|
3
|
+
"ts": "2026-09-22T08:31:14Z",
|
|
4
|
+
"event": "approval.requested",
|
|
5
|
+
"task": "hook:hermes-sess-1:tool-7",
|
|
6
|
+
"action_key": "hook:hermes-sess-1:tool-7:deps.add",
|
|
7
|
+
"actor": "agent:hermes",
|
|
8
|
+
"channel": "telegram",
|
|
9
|
+
"payload": {
|
|
10
|
+
"class": "deps.add",
|
|
11
|
+
"execution": "harness",
|
|
12
|
+
"harness_cap_ms": 300000,
|
|
13
|
+
"payload_hash": "5f1c2a6f3e9d4b8a7c0e1d2b3a4958677889aabbccddeeff0011223344556677"
|
|
14
|
+
},
|
|
15
|
+
"alg": "sha256/jcs",
|
|
16
|
+
"prev": "1010101010101010101010101010101010101010101010101010101010101010",
|
|
17
|
+
"hash": "1111111111111111111111111111111111111111111111111111111111111111"
|
|
18
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
{
|
|
2
|
+
"seq": 62,
|
|
3
|
+
"ts": "2026-09-20T11:05:44Z",
|
|
4
|
+
"event": "envelope.drift",
|
|
5
|
+
"task": "task-311",
|
|
6
|
+
"actor": "system:daemon",
|
|
7
|
+
"daemon": "village-goa-1",
|
|
8
|
+
"payload": {
|
|
9
|
+
"file": "backlog/tasks/task-311.md",
|
|
10
|
+
"declared_state": "requested",
|
|
11
|
+
"derived_state": "granted",
|
|
12
|
+
"reason": "state-mismatch"
|
|
13
|
+
},
|
|
14
|
+
"alg": "sha256/jcs",
|
|
15
|
+
"prev": "c0a1dd6b0f5a9b7fe7cf7d90cbb4aa4cf0a1eb2d5c7318cbbbd7a2b9efc33150",
|
|
16
|
+
"hash": "5b2f0c8419a7dd3e6f0b4a1c93de8027f5cbb1a40de92f7c3ab6051d8e4f29c7"
|
|
17
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
{
|
|
2
|
+
"seq": 61,
|
|
3
|
+
"ts": "2026-09-20T11:04:19Z",
|
|
4
|
+
"event": "audit.sampled",
|
|
5
|
+
"task": "task-311",
|
|
6
|
+
"action_key": "task-311:digest:2026-09-20",
|
|
7
|
+
"actor": "system:auditor",
|
|
8
|
+
"daemon": "daemon-3f2a9c11",
|
|
9
|
+
"payload": {
|
|
10
|
+
"rate": 0.1
|
|
11
|
+
},
|
|
12
|
+
"alg": "sha256/jcs",
|
|
13
|
+
"prev": "07453feb30c2b0b1a2806398cdd17b38d98f9abf52b8a75ca42878c9101d8264",
|
|
14
|
+
"hash": "c0a1dd6b0f5a9b7fe7cf7d90cbb4aa4cf0a1eb2d5c7318cbbbd7a2b9efc33150"
|
|
15
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
{
|
|
2
|
+
"seq": 9,
|
|
3
|
+
"ts": "2026-10-04T09:19:02Z",
|
|
4
|
+
"event": "execution.started",
|
|
5
|
+
"task": "hook:sess-1:tu-1",
|
|
6
|
+
"action_key": "hook:sess-1:tu-1:files.write.workspace",
|
|
7
|
+
"actor": "agent:claude-code",
|
|
8
|
+
"payload": {
|
|
9
|
+
"class": "files.write.workspace",
|
|
10
|
+
"est_cost_usd": "0",
|
|
11
|
+
"execution": "harness",
|
|
12
|
+
"payload_hash": "9f2b1c4d5e6f708192a3b4c5d6e7f8091a2b3c4d5e6f708192a3b4c5d6e7f809",
|
|
13
|
+
"policy_sha256": "5f1c0de9a3b7e2d4c6f8091a2b3c4d5e6f708192a3b4c5d6e7f8091a2b3c4d5e"
|
|
14
|
+
},
|
|
15
|
+
"alg": "sha256/jcs",
|
|
16
|
+
"prev": "d973375d6aeec5e6cd2de3a0f7dc8f5bc1d9be77225f9af0f851e7fc519a07c3",
|
|
17
|
+
"hash": "707b79cfa14ec43224c8f71a4fee3ddbff52369f82525aab19bec18c369d5cad"
|
|
18
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
{
|
|
2
|
+
"seq": 56,
|
|
3
|
+
"ts": "2026-09-20T09:41:12Z",
|
|
4
|
+
"event": "task.registered",
|
|
5
|
+
"task": "hook:hermes-sess-1:toolu-16",
|
|
6
|
+
"actor": "agent:hermes",
|
|
7
|
+
"payload": {
|
|
8
|
+
"state": "proposed",
|
|
9
|
+
"actions": [
|
|
10
|
+
{
|
|
11
|
+
"class": "deps.add",
|
|
12
|
+
"summary": "npm install --save-dev oxlint",
|
|
13
|
+
"idempotency_key": "hook:hermes-sess-1:toolu-16:deps.add",
|
|
14
|
+
"payload_hash": "4d0a9c2b7e5f13860ad4c9b217e3f508cc61a7d2394b8e05f7ca61d3b8092e4f"
|
|
15
|
+
}
|
|
16
|
+
],
|
|
17
|
+
"harness": "hermes",
|
|
18
|
+
"harness_version": "hermes 0.21.3"
|
|
19
|
+
},
|
|
20
|
+
"alg": "sha256/jcs",
|
|
21
|
+
"prev": "7d925ec380b14fa6c05e971b3d4287ac6fb015e93c2a480df6b1e7c05a83942f",
|
|
22
|
+
"hash": "e1f6a30c4d7b92815ac0fe43b6d258907cb14fe2839a5d07f4cb62e1a9350b7d"
|
|
23
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": "0.1",
|
|
3
|
+
"defaults": {
|
|
4
|
+
"autonomy": "manual",
|
|
5
|
+
"channel": "telegram",
|
|
6
|
+
"approval_ttl": "30m",
|
|
7
|
+
"on_expiry": "reject"
|
|
8
|
+
},
|
|
9
|
+
"approvers": {
|
|
10
|
+
"carter": {
|
|
11
|
+
"channels": ["telegram", "cli"],
|
|
12
|
+
"senders": { "telegram": "5551234567" }
|
|
13
|
+
}
|
|
14
|
+
},
|
|
15
|
+
"daemons": ["daemon-3f2a9c11", "village-goa-1"],
|
|
16
|
+
"classes": {
|
|
17
|
+
"communicate.email.external": { "autonomy": "manual" }
|
|
18
|
+
}
|
|
19
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# Approval Policy
|
|
2
|
+
|
|
3
|
+
Two invalid spellings of `agent_may_request` (APRV-445): a string where a
|
|
4
|
+
boolean belongs, and `true` on a `human-only` rule, which is inert to agents
|
|
5
|
+
and may not be opened to them. Either alone fails the whole policy closed:
|
|
6
|
+
`schema-invalid`.
|
|
7
|
+
|
|
8
|
+
```yaml approval-policy
|
|
9
|
+
version: "0.1"
|
|
10
|
+
|
|
11
|
+
defaults:
|
|
12
|
+
autonomy: manual
|
|
13
|
+
|
|
14
|
+
classes:
|
|
15
|
+
intent.publish.inferred.index:
|
|
16
|
+
autonomy: manual
|
|
17
|
+
agent_may_request: "yes"
|
|
18
|
+
account.credential:
|
|
19
|
+
autonomy: human-only
|
|
20
|
+
agent_may_request: true
|
|
21
|
+
```
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# Approval Policy
|
|
2
|
+
|
|
3
|
+
`agent_may_request` (APRV-445) opens `approval propose` to classes the operator
|
|
4
|
+
declares by name. The family line opens the branch; each member is still a line
|
|
5
|
+
of its own, and the closed member says no for itself.
|
|
6
|
+
|
|
7
|
+
```yaml approval-policy
|
|
8
|
+
version: "0.1"
|
|
9
|
+
|
|
10
|
+
defaults:
|
|
11
|
+
autonomy: manual
|
|
12
|
+
approval_ttl: 72h
|
|
13
|
+
|
|
14
|
+
classes:
|
|
15
|
+
intent.publish.*:
|
|
16
|
+
autonomy: manual
|
|
17
|
+
agent_may_request: true
|
|
18
|
+
intent.publish.inferred.index: { autonomy: manual }
|
|
19
|
+
intent.publish.stated.index: { autonomy: autonomous }
|
|
20
|
+
intent.publish.closed:
|
|
21
|
+
autonomy: manual
|
|
22
|
+
agent_may_request: false
|
|
23
|
+
communicate.email.external: { autonomy: manual }
|
|
24
|
+
```
|
|
@@ -135,19 +135,43 @@
|
|
|
135
135
|
"12345678",
|
|
136
136
|
"hmac-sha256:3f2a9c11b4d7e6085a1c2f9d8e7b6a5c4d3e2f1a0b9c8d7e6f5a4b3c2d1e0f9a"
|
|
137
137
|
]
|
|
138
|
+
},
|
|
139
|
+
"edgeos": {
|
|
140
|
+
"type": "string",
|
|
141
|
+
"minLength": 1,
|
|
142
|
+
"pattern": "^(?:[A-Za-z0-9][A-Za-z0-9._-]{0,127}|hmac-sha256:[0-9a-f]{64})$",
|
|
143
|
+
"description": "The EdgeOS account this person decides from, in one of two forms (SPEC.md §5.2/§10.3, APRV-455, on the ground APRV-324 laid for `telegram`).\n\nRAW: the stable human id the EdgeOS identity service returns from `/humans/me` for the session the person authenticated in, as a string. The runtime treats it as OPAQUE and compares it byte for byte; the grammar admits what such an id is made of (an ASCII letter or digit, then up to 127 letters, digits, `.`, `_` or `-`), which covers a UUID and a decimal id alike. It holds no `:` and no whitespace, so a raw id can never be mistaken for the keyed form and can never contain the separator the runtime's sender index uses. Never an email address or a display name: those are chosen or changed by the person, and a mapping keyed on one would transfer an identity with an edit.\n\nKEYED: `hmac-sha256:<64 lowercase hex>`, the HMAC-SHA-256 of that same id under the operator-held `APPROVAL_SENDER_KEY`, exactly as for `telegram` and for the same reason: a published policy and a published log otherwise disclose the account.\n\nThe id reaches the runtime through `approval channel relay`, whose authenticated caller (the operator's control plane, holding `APPROVAL_RELAY_SECRET`) reports which EdgeOS session made the gesture. The daemon trusts that report the way it trusts the Telegram Bot API's `from.id`: it is operator trust in a relay, stated as such, and the resident cannot choose the id the relay attributes to them.",
|
|
144
|
+
"examples": [
|
|
145
|
+
"4f1c2a9e-7b3d-4e8a-9c21-5d6e7f8a9b0c",
|
|
146
|
+
"hmac-sha256:3f2a9c11b4d7e6085a1c2f9d8e7b6a5c4d3e2f1a0b9c8d7e6f5a4b3c2d1e0f9a"
|
|
147
|
+
]
|
|
138
148
|
}
|
|
139
149
|
},
|
|
140
150
|
"examples": [
|
|
141
151
|
{ "telegram": "12345678" },
|
|
142
152
|
{
|
|
143
153
|
"telegram": "hmac-sha256:3f2a9c11b4d7e6085a1c2f9d8e7b6a5c4d3e2f1a0b9c8d7e6f5a4b3c2d1e0f9a"
|
|
144
|
-
}
|
|
154
|
+
},
|
|
155
|
+
{ "telegram": "12345678", "edgeos": "4f1c2a9e-7b3d-4e8a-9c21-5d6e7f8a9b0c" }
|
|
145
156
|
]
|
|
146
157
|
}
|
|
147
158
|
}
|
|
148
159
|
}
|
|
149
160
|
}
|
|
150
161
|
},
|
|
162
|
+
"daemons": {
|
|
163
|
+
"type": "array",
|
|
164
|
+
"uniqueItems": true,
|
|
165
|
+
"description": "SPEC.md §5.2 (amended, APRV-383): the daemon instance ids permitted to append to this log. An ALLOWLIST, and the counterpart of `approvers` one layer down: that key says which humans may decide, this one says which daemon processes may write the records of those decisions down.\n\nIt exists for the hosted case (`design/hosted-daemon-identity.md`): when one party runs the daemon for another, records the daemon appends carry `daemon` (see `event.schema.json`) naming the instance that wrote them, and this list is how the tenant's own attested policy states which of them it expects. A daemon whose id this list does not name is refused AT THE WRITE BOUNDARY with `daemon-not-allowed` and writes nothing.\n\nABSENT means NO RESTRICTION, not an empty list: a policy that never declares the key behaves exactly as every policy written before it existed, and the daemon-written records still carry their id. An empty array is a policy that admits no daemon at all, which is a thing an author may say and the runtime will enforce.\n\nWhat being LISTED buys is nothing (SPEC.md §11.1 invariant 4). The id is self-reported by the writing process, so it may only ever cost a daemon the ability to write: no verdict, autonomy, budget, floor, sampling draw, token or TTL reads this key or the record field, and a hostile host holding the tenant's log handle is not what this defends against. What it catches is a daemon started against the wrong tenant's log, a second daemon nobody meant to leave running, and a rebuilt container whose id changed, each of which used to write records indistinguishable from the intended daemon's.\n\nIt is a top-level key rather than a member of the `daemon` block below, and the distinction is worth the two names: `daemon` (singular) is how THIS daemon reads a log it already may write, every key in it a latency setting whose strictest value is the default, while `daemons` (plural) is an ENFORCEMENT key deciding WHICH daemons may write at all. Read only from an ATTESTED policy, like every other fact a refusal is derived from.",
|
|
166
|
+
"items": {
|
|
167
|
+
"type": "string",
|
|
168
|
+
"minLength": 1,
|
|
169
|
+
"maxLength": 64,
|
|
170
|
+
"pattern": "^[a-z0-9][a-z0-9._-]*$",
|
|
171
|
+
"description": "One daemon instance id, in the same grammar the `daemon` record field carries: lowercase letters, digits, `.`, `-` and `_`, starting with a letter or digit. Either an id the operator declared in that daemon's launch environment through `APPROVAL_DAEMON_ID`, or the derived `daemon-<instance id>` form that `approval status` and `approval doctor` print for the instance they are run against."
|
|
172
|
+
},
|
|
173
|
+
"examples": [["daemon-3f2a9c11"], ["village-goa-1", "village-goa-2"]]
|
|
174
|
+
},
|
|
151
175
|
"classes": {
|
|
152
176
|
"type": "object",
|
|
153
177
|
"additionalProperties": false,
|
|
@@ -213,7 +237,7 @@
|
|
|
213
237
|
"daemon": {
|
|
214
238
|
"type": "object",
|
|
215
239
|
"additionalProperties": false,
|
|
216
|
-
"description": "APRV-217 (docs/proposals/incremental-prefix-proof.md): how the long-lived readers of this log prove a cached prefix before they reuse it. Read by `approval daemon run` and `approval up` and by nothing else: one-shot processes, the Claude Code hook included, always prove in full, and `approval log verify` and `approval doctor` never use the cache at all. Every key here is a LATENCY setting whose strictest value is the default, so a policy that omits the block, or the whole block, behaves exactly as the runtime did before it existed.",
|
|
240
|
+
"description": "APRV-217 (docs/proposals/incremental-prefix-proof.md): how the long-lived readers of this log prove a cached prefix before they reuse it. Read by `approval daemon run` and `approval up` and by nothing else: one-shot processes, the Claude Code hook included, always prove in full, and `approval log verify` and `approval doctor` never use the cache at all. Every key here is a LATENCY setting whose strictest value is the default, so a policy that omits the block, or the whole block, behaves exactly as the runtime did before it existed. Not to be confused with the top-level `daemons` list above (APRV-383), which is an enforcement key naming WHICH daemon instances may append to this log; this block is how a daemon that may already write reads.",
|
|
217
241
|
"properties": {
|
|
218
242
|
"read_proof": {
|
|
219
243
|
"type": "string",
|
|
@@ -402,9 +426,9 @@
|
|
|
402
426
|
},
|
|
403
427
|
"senderChannel": {
|
|
404
428
|
"type": "string",
|
|
405
|
-
"enum": ["telegram"],
|
|
406
|
-
"description": "SPEC.md §10.3 (amended, APRV-324): a channel whose transport attributes a gesture to an account id, and therefore the only kind of channel an `approvers.<id>.senders` entry may name. `telegram` qualifies because the Bot API reports `callback_query.from.id`, which the sender cannot choose. `web` and `cli` are deliberately absent: an unauthenticated form post names nobody and a local process names whoever runs it, so a sender mapping for either would be a binding the runtime cannot check and an identity key the channel cannot support.",
|
|
407
|
-
"examples": ["telegram"]
|
|
429
|
+
"enum": ["telegram", "edgeos"],
|
|
430
|
+
"description": "SPEC.md §10.3 (amended, APRV-324; widened, APRV-455): a channel whose transport attributes a gesture to an account id, and therefore the only kind of channel an `approvers.<id>.senders` entry may name. `telegram` qualifies because the Bot API reports `callback_query.from.id`, which the sender cannot choose. `edgeos` qualifies on the same ground: the gesture arrives through `approval channel relay`, authenticated by a shared secret from the daemon's launch environment, from a control plane that attributes it to the EdgeOS `/humans/me` id of the session the person signed in with, which the person cannot choose either. In both cases the daemon trusts the relay's report of who acted, and that is operator trust (APRV-422: no hosted service has authority over decisions; the relay carries a person's gesture and decides nothing). `web` and `cli` are deliberately absent: an unauthenticated form post names nobody and a local process names whoever runs it, so a sender mapping for either would be a binding the runtime cannot check and an identity key the channel cannot support.",
|
|
431
|
+
"examples": ["telegram", "edgeos"]
|
|
408
432
|
},
|
|
409
433
|
"classPattern": {
|
|
410
434
|
"type": "string",
|
|
@@ -415,7 +439,7 @@
|
|
|
415
439
|
"type": "object",
|
|
416
440
|
"additionalProperties": false,
|
|
417
441
|
"required": ["autonomy"],
|
|
418
|
-
"description": "SPEC.md §5.1: a class rule. The shorthand form is `{ autonomy }` alone; the full form may add rates, `allow_irreversible`, `approvers` and `limits`. Both are this one shape — `autonomy` is always REQUIRED, since a rule without it cannot decide anything, and unknown keys are rejected so a typo'd constraint is never silently ignored.",
|
|
442
|
+
"description": "SPEC.md §5.1: a class rule. The shorthand form is `{ autonomy }` alone; the full form may add rates, `allow_irreversible`, `agent_may_request`, `approvers` and `limits`. Both are this one shape — `autonomy` is always REQUIRED, since a rule without it cannot decide anything, and unknown keys are rejected so a typo'd constraint is never silently ignored.",
|
|
419
443
|
"allOf": [
|
|
420
444
|
{
|
|
421
445
|
"$comment": "SPEC.md §5.2 (amended, APRV-127): `live_rate` belongs to `supervised-live` and to nothing else. Required there, because a live mode with no fraction declares a control without saying how much of it runs, and the runtime would have to invent the number an author did not write. Forbidden everywhere else, because a rate sitting on a `manual`, `supervised-retro` or `autonomous` rule is a fraction its author believes is in force that nothing reads — the same silent no-op this schema's closed shape exists to prevent. APRV-185 adds `human-only` to that everywhere-else, and the `else` branch below already carries it with no new clause: a level whose actions no agent may take has no fraction of them to gate, so a `live_rate` written there is a control its author believes is running over a population of size zero.",
|
|
@@ -467,6 +491,22 @@
|
|
|
467
491
|
}
|
|
468
492
|
}
|
|
469
493
|
}
|
|
494
|
+
},
|
|
495
|
+
{
|
|
496
|
+
"$comment": "SPEC.md §5.2 (amended, APRV-445): `agent_may_request: true` opens the `propose` verb to a class an operator declared, so the level it sits on must be one an agent may ask about. Refused on `human-only` as a load error: a human-only class is inert to agents (SPEC.md §11.1 invariant 9), and a flag saying an agent may file a request for one is a door the gate would only slam. False is admitted everywhere as the explicit spelling of the default.",
|
|
497
|
+
"if": {
|
|
498
|
+
"type": "object",
|
|
499
|
+
"properties": { "agent_may_request": { "const": true } },
|
|
500
|
+
"required": ["agent_may_request"]
|
|
501
|
+
},
|
|
502
|
+
"then": {
|
|
503
|
+
"type": "object",
|
|
504
|
+
"properties": {
|
|
505
|
+
"autonomy": {
|
|
506
|
+
"enum": ["supervised-live", "supervised", "supervised-retro", "autonomous", "manual"]
|
|
507
|
+
}
|
|
508
|
+
}
|
|
509
|
+
}
|
|
470
510
|
}
|
|
471
511
|
],
|
|
472
512
|
"properties": {
|
|
@@ -480,6 +520,10 @@
|
|
|
480
520
|
"type": "boolean",
|
|
481
521
|
"description": "SPEC.md §5.2/§7 (amended, APRV-317): explicit permission for truthful `reversible: false` actions matching this rule to retain its nonmanual autonomy. Omission or false preserves the manual floor. Every equally most-specific matching rule must say true; defaults and action metadata cannot supply it."
|
|
482
522
|
},
|
|
523
|
+
"agent_may_request": {
|
|
524
|
+
"type": "boolean",
|
|
525
|
+
"description": "SPEC.md §5.2 (amended, APRV-445): whether an agent may open a request in this class with `approval propose`, naming the class itself and carrying its payload inline. Absent and false mean no. It is read two ways and only these two: on a key that names the class EXACTLY, and on a declared family key `<prefix>.*` the class falls under. `propose` additionally requires the class to be an exact key of `classes`, so a wildcard rule alone never makes a class proposable; the exact key's own value wins when it states one, and otherwise the most specific family that states one decides. Adding or changing it is a policy edit like any other and takes a human re-attestation, which voids pending requests (`policy-drift`)."
|
|
526
|
+
},
|
|
483
527
|
"approvers": {
|
|
484
528
|
"type": "array",
|
|
485
529
|
"minItems": 1,
|