approval-md 0.3.0 → 0.4.1
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 +101 -13
- package/cli.js +134 -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 +72 -0
- package/dist/src/channels/contract.js +161 -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.d.ts +25 -0
- package/dist/src/channels/tagging.js +63 -6
- package/dist/src/channels/tagging.js.map +1 -1
- package/dist/src/channels/telegram-minimal.d.ts +251 -0
- package/dist/src/channels/telegram-minimal.js +618 -0
- package/dist/src/channels/telegram-minimal.js.map +1 -0
- 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 +725 -20
- package/dist/src/channels/telegram.js +1332 -123
- package/dist/src/channels/telegram.js.map +1 -1
- package/dist/src/cli/amend.js +358 -5
- 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/audit-card.d.ts +1 -1
- package/dist/src/cli/audit-card.js +40 -1
- package/dist/src/cli/audit-card.js.map +1 -1
- package/dist/src/cli/audit.d.ts +7 -5
- package/dist/src/cli/audit.js +22 -10
- package/dist/src/cli/audit.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 +275 -0
- package/dist/src/cli/channel-telegram-webhook.js +716 -0
- package/dist/src/cli/channel-telegram-webhook.js.map +1 -0
- package/dist/src/cli/channel-telegram.d.ts +347 -28
- package/dist/src/cli/channel-telegram.js +648 -56
- 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 +29 -1
- package/dist/src/cli/execute.js +387 -21
- 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 +10 -1
- package/dist/src/cli/help.js +283 -54
- package/dist/src/cli/help.js.map +1 -1
- package/dist/src/cli/hook.d.ts +485 -6
- package/dist/src/cli/hook.js +2370 -70
- 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/log-unlock.d.ts +23 -0
- package/dist/src/cli/log-unlock.js +109 -0
- package/dist/src/cli/log-unlock.js.map +1 -0
- package/dist/src/cli/main.d.ts +0 -12
- package/dist/src/cli/main.js +72 -4
- 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 +26 -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 +338 -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 +153 -12
- package/dist/src/core/audit.js +415 -20
- 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 +766 -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 +364 -6
- package/dist/src/core/gate.js +802 -27
- 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-lock.d.ts +435 -0
- package/dist/src/core/log-lock.js +1093 -0
- package/dist/src/core/log-lock.js.map +1 -0
- 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 +130 -22
- package/dist/src/core/log.js +473 -50
- 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 +40 -0
- package/dist/src/core/policy-proposal.js +37 -16
- package/dist/src/core/policy-proposal.js.map +1 -1
- package/dist/src/core/prompt-layout.d.ts +186 -1
- package/dist/src/core/prompt-layout.js +345 -3
- package/dist/src/core/prompt-layout.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 +59 -11
- package/dist/src/core/sender-identity.js +79 -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/validate.d.ts +20 -6
- package/dist/src/core/validate.js +0 -0
- package/dist/src/core/validate.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 +166 -0
- package/dist/src/serve/archive.js +363 -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 +768 -0
- package/docs/cli-reference.md +2139 -67
- package/package.json +4 -2
- package/schema/event.schema.json +413 -30
- package/schema/fixtures/event/invalid/audit-lock-reclaimed-age-without-created.json +19 -0
- package/schema/fixtures/event/invalid/audit-lock-reclaimed-agent-actor.json +14 -0
- package/schema/fixtures/event/invalid/audit-lock-reclaimed-lockfile-path.json +14 -0
- package/schema/fixtures/event/invalid/audit-lock-reclaimed-operator-cleared-system-actor.json +14 -0
- package/schema/fixtures/event/invalid/audit-lock-reclaimed-unknown-reason.json +14 -0
- package/schema/fixtures/event/invalid/audit-lock-reclaimed-without-age.json +13 -0
- package/schema/fixtures/event/invalid/audit-reviewed-agent-actor.json +6 -1
- package/schema/fixtures/event/invalid/audit-reviewed-bad-payload-hash.json +21 -0
- package/schema/fixtures/event/invalid/audit-reviewed-loved-no-note.json +5 -0
- package/schema/fixtures/event/invalid/audit-reviewed-no-subject-hash.json +19 -0
- package/schema/fixtures/event/invalid/audit-reviewed-no-subject-seq.json +19 -0
- package/schema/fixtures/event/invalid/audit-reviewed-no-verdict-source.json +19 -0
- package/schema/fixtures/event/invalid/audit-reviewed-no-verdict.json +19 -0
- package/schema/fixtures/event/invalid/audit-reviewed-system-actor.json +6 -1
- package/schema/fixtures/event/invalid/audit-sampled-no-policy-hash.json +14 -0
- 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/audit-lock-reclaimed-implausible-created.json +18 -0
- package/schema/fixtures/event/valid/audit-lock-reclaimed-legacy.json +14 -0
- package/schema/fixtures/event/valid/audit-lock-reclaimed-unlock.json +19 -0
- package/schema/fixtures/event/valid/audit-lock-reclaimed.json +19 -0
- package/schema/fixtures/event/valid/audit-reviewed-reaction.json +5 -0
- package/schema/fixtures/event/valid/audit-reviewed.json +6 -1
- package/schema/fixtures/event/valid/audit-sampled.json +2 -1
- package/schema/fixtures/event/valid/daemon-identity-declared.json +17 -0
- package/schema/fixtures/event/valid/daemon-identity.json +16 -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 +102 -6
|
@@ -73,6 +73,29 @@ function object(properties, required) {
|
|
|
73
73
|
/** An object whose keys are not enumerated here (a log record, a policy rule). */
|
|
74
74
|
const OPEN_OBJECT = { type: "object" };
|
|
75
75
|
const arrayOf = (items) => ({ type: "array", items });
|
|
76
|
+
/**
|
|
77
|
+
* One refusal family in `approval status` (APRV-376): how many records the log
|
|
78
|
+
* carries, and the newest few of them, newest first.
|
|
79
|
+
*
|
|
80
|
+
* `code` is the surface's own refusal code, copied verbatim from the record, and
|
|
81
|
+
* it is spelled here as a plain string rather than an enum on purpose. The two
|
|
82
|
+
* closed vocabularies live at the write boundary (`schema/event.schema.json`) and
|
|
83
|
+
* in `core/decision-refusal.ts` and `core/gesture-refusal.ts`; a third copy in a
|
|
84
|
+
* REPORT schema would refuse to describe a log it was older than, which is the
|
|
85
|
+
* wrong failure direction for a field that authorizes nothing.
|
|
86
|
+
*
|
|
87
|
+
* `sender` is the account the gesture arrived from, when the record carries one,
|
|
88
|
+
* in the form the record carries it: `hashed` marks APRV-370's keyed digest, and
|
|
89
|
+
* it is present only when the record says so.
|
|
90
|
+
*/
|
|
91
|
+
const REFUSAL_FAMILY = object({
|
|
92
|
+
count: INTEGER,
|
|
93
|
+
recent: arrayOf(object({
|
|
94
|
+
seq: INTEGER,
|
|
95
|
+
code: STRING,
|
|
96
|
+
sender: object({ channel: STRING, id: STRING, hashed: BOOLEAN }, ["channel", "id"]),
|
|
97
|
+
}, ["seq", "code"])),
|
|
98
|
+
}, ["count", "recent"]);
|
|
76
99
|
/**
|
|
77
100
|
* Positional arguments, named in order. `items: false` closes the tail: an
|
|
78
101
|
* unexpected argument is a usage error in the CLI and is one here too.
|
|
@@ -91,18 +114,44 @@ function positionals(names, requiredCount) {
|
|
|
91
114
|
};
|
|
92
115
|
}
|
|
93
116
|
const NO_POSITIONALS = { type: "array", maxItems: 0 };
|
|
94
|
-
/** The argv after `--`, for the
|
|
117
|
+
/** The argv after `--`, for the verbs that take one. */
|
|
95
118
|
const TRAILING = {
|
|
96
119
|
type: "array",
|
|
97
120
|
items: STRING,
|
|
98
121
|
minItems: 1,
|
|
99
122
|
description: "everything after the first `--`, passed through untouched",
|
|
100
123
|
};
|
|
101
|
-
/**
|
|
124
|
+
/**
|
|
125
|
+
* Path-typed flags, by the input schema they belong to (APRV-421).
|
|
126
|
+
*
|
|
127
|
+
* A flag declared `"path"` PUBLISHES as an ordinary `{"type":"string"}` — the
|
|
128
|
+
* wire contract is unchanged and no consumer sees a new keyword — while the
|
|
129
|
+
* registry retains the fact that its value NAMES A FILE. `approval serve`
|
|
130
|
+
* reads that through {@link pathFlagsOf} to confine such a value to the store
|
|
131
|
+
* the server was started for, so a flag added to this file tomorrow is
|
|
132
|
+
* confined on the day it appears rather than on the day somebody remembers to
|
|
133
|
+
* add it to a list kept somewhere else.
|
|
134
|
+
*
|
|
135
|
+
* A WeakMap keyed by the schema object, rather than a field on {@link VerbSpec},
|
|
136
|
+
* because `input()` returns that schema and all hundred-odd call sites would
|
|
137
|
+
* otherwise have to change to carry a second value.
|
|
138
|
+
*/
|
|
139
|
+
const PATH_FLAGS_BY_INPUT = new WeakMap();
|
|
140
|
+
/** The flags of `spec` whose value names a filesystem path. */
|
|
141
|
+
export function pathFlagsOf(spec) {
|
|
142
|
+
return PATH_FLAGS_BY_INPUT.get(spec.input) ?? [];
|
|
143
|
+
}
|
|
144
|
+
/**
|
|
145
|
+
* Flags are `string | boolean` (see `args.ts`); nothing else can be parsed.
|
|
146
|
+
* `path` is a `string` the registry knows more about — see above.
|
|
147
|
+
*/
|
|
102
148
|
function input(parts) {
|
|
103
149
|
const flagProperties = {};
|
|
150
|
+
const pathFlags = [];
|
|
104
151
|
for (const [flag, kind] of Object.entries(parts.flags)) {
|
|
105
|
-
|
|
152
|
+
if (kind === "path")
|
|
153
|
+
pathFlags.push(flag);
|
|
154
|
+
flagProperties[flag] = kind === "boolean" ? BOOLEAN : STRING;
|
|
106
155
|
}
|
|
107
156
|
const properties = {
|
|
108
157
|
positionals: parts.positionals ?? NO_POSITIONALS,
|
|
@@ -115,18 +164,20 @@ function input(parts) {
|
|
|
115
164
|
};
|
|
116
165
|
if (parts.trailing !== undefined)
|
|
117
166
|
properties["trailing"] = parts.trailing;
|
|
118
|
-
|
|
167
|
+
const schema = {
|
|
119
168
|
type: "object",
|
|
120
169
|
properties,
|
|
121
170
|
required: [],
|
|
122
171
|
additionalProperties: false,
|
|
123
172
|
};
|
|
173
|
+
PATH_FLAGS_BY_INPUT.set(schema, pathFlags);
|
|
174
|
+
return schema;
|
|
124
175
|
}
|
|
125
176
|
/** Flags every command accepts. */
|
|
126
177
|
const HELP_FLAGS = { "--help": "boolean", "-h": "boolean" };
|
|
127
178
|
const JSON_FLAG = { "--json": "boolean" };
|
|
128
|
-
const LOG_FLAG = { "--log": "
|
|
129
|
-
const POLICY_FLAGS = { "--policy": "
|
|
179
|
+
const LOG_FLAG = { "--log": "path" };
|
|
180
|
+
const POLICY_FLAGS = { "--policy": "path", "--dir": "path" };
|
|
130
181
|
const AS_FLAG = { "--as": "string" };
|
|
131
182
|
/**
|
|
132
183
|
* The shared failure shape. Usage and I/O failures print
|
|
@@ -253,6 +304,8 @@ const POLICY_RESOLUTION_OUTPUT = object({
|
|
|
253
304
|
winner: BOOLEAN,
|
|
254
305
|
tieBreak: STRING,
|
|
255
306
|
}, ["pattern", "specificity", "autonomy", "winner", "tieBreak"])),
|
|
307
|
+
// APRV-445: may an agent open a request in this class with `propose`.
|
|
308
|
+
agentRequest: object({ allowed: BOOLEAN, explicit: BOOLEAN, pattern: nullable(STRING), humanOnly: BOOLEAN }, ["allowed", "explicit", "pattern", "humanOnly"]),
|
|
256
309
|
decisionPath: arrayOf(STRING),
|
|
257
310
|
}, [
|
|
258
311
|
"class",
|
|
@@ -266,6 +319,7 @@ const POLICY_RESOLUTION_OUTPUT = object({
|
|
|
266
319
|
"irreversibility",
|
|
267
320
|
"irreversiblePatterns",
|
|
268
321
|
"candidates",
|
|
322
|
+
"agentRequest",
|
|
269
323
|
"decisionPath",
|
|
270
324
|
]);
|
|
271
325
|
// ---------------------------------------------------------------------------
|
|
@@ -289,7 +343,7 @@ const VERBS = [
|
|
|
289
343
|
purpose: "Scaffold a working directory: APPROVAL.md (SPEC.md §5.1's canonical policy, to be read and edited), the empty .approval/log/ directory, .approval/QUEUE.md in its empty state, and the .gitignore lines for the index, the vault, the environment source map and the atomic-write temp files. It appends nothing, attests nothing, and overwrites nothing; a re-run writes nothing and reports what already exists.",
|
|
290
344
|
human_only: true,
|
|
291
345
|
human_only_note: "It writes the policy file a human must then read and attest, in a directory a human chose. Nothing it writes is operative, but a scaffold an agent could drop into a tree is a policy file nobody decided to have.",
|
|
292
|
-
input: input({ flags: { "--dir": "
|
|
346
|
+
input: input({ flags: { "--dir": "path", ...JSON_FLAG, ...HELP_FLAGS } }),
|
|
293
347
|
output: object({
|
|
294
348
|
ok: { const: true },
|
|
295
349
|
dir: STRING,
|
|
@@ -305,7 +359,7 @@ const VERBS = [
|
|
|
305
359
|
purpose: "Interactive solo setup: asks for a human id, terminal or Telegram, and five manual class families; writes a new policy and identity configuration, shows the exact bytes, requires typed understood, then attests. It classifies policy.core, refuses non-terminal input and --json, and never overwrites an existing policy.",
|
|
306
360
|
human_only: true,
|
|
307
361
|
human_only_note: "This command authors and attests APPROVAL.md and declares APPROVAL_HUMAN. It is reserved to a person at a terminal and is never exposed through MCP.",
|
|
308
|
-
input: input({ flags: { "--dir": "
|
|
362
|
+
input: input({ flags: { "--dir": "path", "--api-base": "string", ...HELP_FLAGS } }),
|
|
309
363
|
output: null,
|
|
310
364
|
error: ERROR_SCHEMA,
|
|
311
365
|
exit_codes: BASE_EXIT_CODES,
|
|
@@ -323,6 +377,9 @@ const VERBS = [
|
|
|
323
377
|
records: nullable(INTEGER),
|
|
324
378
|
head: HEAD,
|
|
325
379
|
intactThroughSeq: INTEGER,
|
|
380
|
+
tear: { enum: ["nul-filled", "partial-line"] },
|
|
381
|
+
tornBytes: INTEGER,
|
|
382
|
+
intactBytes: INTEGER,
|
|
326
383
|
firstBadSeq: nullable(INTEGER),
|
|
327
384
|
reason: STRING,
|
|
328
385
|
message: STRING,
|
|
@@ -533,13 +590,14 @@ const VERBS = [
|
|
|
533
590
|
{
|
|
534
591
|
name: "policy",
|
|
535
592
|
subcommand: "attest",
|
|
536
|
-
purpose: "Record a human's sign-off on the policy file's exact bytes, as one policy.updated event carrying their SHA-256. Gate operations refuse while the live file is unattested or has changed since the last attestation, so an edited policy is inoperative until a human re-attests it. With --organ <path> it attests one of the gate's ORGANS instead — the harness files that install the hook — as one gate.organ.attested event no gate operation reads: those paths are human-only, so no grant for a hand edit to one can exist and this record is the only evidence the protected-path guard can accept (APRV-272). With --path <path> it signs off an ordinary PROTECTED path (policy.edit or a policy.edit.* sub-class) as one gate.path.signed_off event, which is what resolves SPEC.md's `(Amended APRV-n, pending sign-off.)` suffix: whole-file evidence that a human read those bytes, weaker than the hunk a grant binds, and read by the protected-path guard only after its grant search has failed (APRV-338). All three routes are human-only, refuse an agent actor with a machine-readable code, and compute their digest in the runtime.",
|
|
593
|
+
purpose: "Record a human's sign-off on the policy file's exact bytes, as one policy.updated event carrying their SHA-256. Gate operations refuse while the live file is unattested or has changed since the last attestation, so an edited policy is inoperative until a human re-attests it. With --organ <path> it attests one of the gate's ORGANS instead — the harness files that install the hook — as one gate.organ.attested event no gate operation reads: those paths are human-only, so no grant for a hand edit to one can exist and this record is the only evidence the protected-path guard can accept (APRV-272). With --path <path> it signs off an ordinary PROTECTED path (policy.edit or a policy.edit.* sub-class) as one gate.path.signed_off event, which is what resolves SPEC.md's `(Amended APRV-n, pending sign-off.)` suffix: whole-file evidence that a human read those bytes, weaker than the hunk a grant binds, and read by the protected-path guard only after its grant search has failed (APRV-338). All three routes are human-only, refuse an agent actor with a machine-readable code, and compute their digest in the runtime. With --bootstrap it attests a store's FIRST policy only, reading the verified log first: a re-run over the same bytes refuses policy-already-attested and changed bytes refuse policy-amendment-required, both at exit 1 with nothing appended, which is the operator's provisioning step for a tenant with no shell (APRV-449).",
|
|
537
594
|
human_only: true,
|
|
538
595
|
input: input({
|
|
539
596
|
flags: {
|
|
540
597
|
...POLICY_FLAGS,
|
|
541
598
|
"--organ": "string",
|
|
542
599
|
"--path": "string",
|
|
600
|
+
"--bootstrap": "boolean",
|
|
543
601
|
...AS_FLAG,
|
|
544
602
|
...LOG_FLAG,
|
|
545
603
|
...JSON_FLAG,
|
|
@@ -668,7 +726,7 @@ const VERBS = [
|
|
|
668
726
|
flags: {
|
|
669
727
|
"--action": "string",
|
|
670
728
|
...AS_FLAG,
|
|
671
|
-
"--payload": "
|
|
729
|
+
"--payload": "path",
|
|
672
730
|
...POLICY_FLAGS,
|
|
673
731
|
...LOG_FLAG,
|
|
674
732
|
...JSON_FLAG,
|
|
@@ -688,6 +746,66 @@ const VERBS = [
|
|
|
688
746
|
error: ERROR_SCHEMA,
|
|
689
747
|
exit_codes: BASE_EXIT_CODES,
|
|
690
748
|
},
|
|
749
|
+
{
|
|
750
|
+
name: "propose",
|
|
751
|
+
purpose: "Register and request ONE action in one call, with the payload inline as a JSON object (--payload-json, at most 262144 bytes) rather than a file, for a requester with no file on the gate's machine. Unlike request, the caller names the class, and that is bounded by the operator: the class must be an EXACT key of the policy's classes (a wildcard match does not count) and that key or a declared <prefix>.* family must set agent_may_request: true, else class-not-agent-requestable and nothing is written. --key MUST begin with <class>: (key-class-mismatch otherwise); consumers read the class from the record. --key is at most 1024 bytes and --summary at most 4096; lone surrogates and non-finite numbers are usage errors. The task id is derived from the actor, class and key (propose:<32 hex>), so a retry with the same class, key and bytes appends nothing and answers idempotent:true while the request is pending, usably granted, refused or executed (state executed); a request that can no longer be answered usefully (withdrawn, expired, a grant whose window lapsed, or one voided by a re-attested policy) is asked again by the same call. The same key with different bytes refuses duplicate-request while a request is live and payload-mismatch otherwise. decision requested: a human is asked, poll with wait <task> --timeout 0. decision autonomous or supervised: nothing is asked, act and then record it with start. The summary is stored in the log in cleartext; the payload is stored in the payload store, never in the log.",
|
|
752
|
+
human_only: false,
|
|
753
|
+
input: input({
|
|
754
|
+
flags: {
|
|
755
|
+
"--class": "string",
|
|
756
|
+
"--key": "string",
|
|
757
|
+
"--summary": "string",
|
|
758
|
+
"--payload-json": "string",
|
|
759
|
+
...AS_FLAG,
|
|
760
|
+
...POLICY_FLAGS,
|
|
761
|
+
...LOG_FLAG,
|
|
762
|
+
...JSON_FLAG,
|
|
763
|
+
...HELP_FLAGS,
|
|
764
|
+
},
|
|
765
|
+
}),
|
|
766
|
+
output: object({
|
|
767
|
+
ok: { const: true },
|
|
768
|
+
task: STRING,
|
|
769
|
+
action_key: STRING,
|
|
770
|
+
class: STRING,
|
|
771
|
+
payload_hash: SHA256,
|
|
772
|
+
decision: { enum: ["requested", "autonomous", "supervised"] },
|
|
773
|
+
state: nullable({
|
|
774
|
+
enum: ["requested", "granted", "rejected", "revoked", "expired", "withdrawn", "executed"],
|
|
775
|
+
}),
|
|
776
|
+
seq: nullable(INTEGER),
|
|
777
|
+
idempotent: BOOLEAN,
|
|
778
|
+
}, ["ok", "task", "action_key", "class", "payload_hash", "decision", "state", "seq", "idempotent"]),
|
|
779
|
+
error: ERROR_SCHEMA,
|
|
780
|
+
exit_codes: BASE_EXIT_CODES,
|
|
781
|
+
},
|
|
782
|
+
{
|
|
783
|
+
name: "start",
|
|
784
|
+
purpose: "Record execution.started for a proposed action you are about to carry out yourself, once per key. Proposals only (task-not-proposal otherwise). REQUESTER-ONLY: the actor must be the one that registered the task. --payload-json is the bytes you are about to act on and must hash to the registered payload_hash. A key whose request is pending or granted-and-unspent spends its harness grant (refused unless granted, unexpired, unspent and under the policy it was asked under); a key with no such request (none, or one withdrawn, expired, rejected or revoked) is recorded as authorized by the policy (refused for a manual or human-only class). It grants nothing: it writes only what the grant or the policy already authorized, and a second start of the same key is already-executed.",
|
|
785
|
+
human_only: false,
|
|
786
|
+
input: input({
|
|
787
|
+
positionals: positionals([{ name: "task", description: "the task id propose returned" }], 1),
|
|
788
|
+
flags: {
|
|
789
|
+
"--action": "string",
|
|
790
|
+
"--payload-json": "string",
|
|
791
|
+
...AS_FLAG,
|
|
792
|
+
...POLICY_FLAGS,
|
|
793
|
+
...LOG_FLAG,
|
|
794
|
+
...JSON_FLAG,
|
|
795
|
+
...HELP_FLAGS,
|
|
796
|
+
},
|
|
797
|
+
}),
|
|
798
|
+
output: object({
|
|
799
|
+
ok: { const: true },
|
|
800
|
+
task: STRING,
|
|
801
|
+
action_key: STRING,
|
|
802
|
+
class: STRING,
|
|
803
|
+
authorization: { enum: ["grant", "policy"] },
|
|
804
|
+
seq: INTEGER,
|
|
805
|
+
}, ["ok", "task", "action_key", "class", "authorization", "seq"]),
|
|
806
|
+
error: ERROR_SCHEMA,
|
|
807
|
+
exit_codes: BASE_EXIT_CODES,
|
|
808
|
+
},
|
|
691
809
|
{
|
|
692
810
|
name: "grant",
|
|
693
811
|
purpose: "Record a human approval of a pending request and mint the single-use execution token for its action. The raw token is PRINTED ONCE here and stored nowhere: the log holds only its SHA-256, so nothing can recover it afterwards. Budgets are re-evaluated at grant time, because the moment that matters for a commitment is the moment the human commits.",
|
|
@@ -1091,6 +1209,7 @@ const VERBS = [
|
|
|
1091
1209
|
], 1),
|
|
1092
1210
|
flags: {
|
|
1093
1211
|
"--note": "string",
|
|
1212
|
+
"--ok": "boolean",
|
|
1094
1213
|
"--deny": "boolean",
|
|
1095
1214
|
...AS_FLAG,
|
|
1096
1215
|
...LOG_FLAG,
|
|
@@ -1163,7 +1282,7 @@ const VERBS = [
|
|
|
1163
1282
|
},
|
|
1164
1283
|
{
|
|
1165
1284
|
name: "wait",
|
|
1166
|
-
purpose: "Block until every approval.requested of a task has a decision, or the timeout elapses. THE EXIT CODE IS THE DECISION: 0 granted, 1 rejected, revoked or
|
|
1285
|
+
purpose: "Block until every approval.requested of a task has a decision, or the timeout elapses. THE EXIT CODE IS THE DECISION: 0 granted (or nothing-to-wait-for, which `status` names and which is no grant), 1 rejected, revoked, withdrawn or not-registered, 3 expired (a grant whose request window lapsed included), 6 timeout, 7 void (a re-attest voided a pending request or a harness grant: ask again). It writes nothing by default, not even the expiry it may derive; --withdraw-on-timeout is the one exception, appending approval.withdrawn for the requests this actor opened so a question nobody can answer to does not sit in a human's queue. Only the manual path produces requests to wait for, so a registered task with none, or whose every grant an execution has already spent, returns immediately at exit 0 with status nothing-to-wait-for, NEVER granted; a task the log has never registered is refused not-registered at exit 1 (APRV-428). --timeout 0 reads the current state once and never sleeps (an undecided request answers timeout, exit 6, at once; it refuses --withdraw-on-timeout): use it to poll through approval serve, which runs every call through one queue that a sleeping wait would hold. Under policy token_delivery: sealed, a granted action's --json entry also carries the raw execution token, opened from the grant's ciphertext with the private key this machine kept when it opened the request; that removes the terminal paste and works across machines. Recovering a minted token is not minting one: it still exists only because a human granted it, still binds to the payload bytes, and is still single-use.",
|
|
1167
1286
|
human_only: false,
|
|
1168
1287
|
input: input({
|
|
1169
1288
|
positionals: positionals([{ name: "task", description: "the task id" }], 1),
|
|
@@ -1181,7 +1300,18 @@ const VERBS = [
|
|
|
1181
1300
|
output: object({
|
|
1182
1301
|
ok: BOOLEAN,
|
|
1183
1302
|
task: STRING,
|
|
1184
|
-
status: {
|
|
1303
|
+
status: {
|
|
1304
|
+
enum: [
|
|
1305
|
+
"granted",
|
|
1306
|
+
"executed",
|
|
1307
|
+
"rejected",
|
|
1308
|
+
"withdrawn",
|
|
1309
|
+
"void",
|
|
1310
|
+
"expired",
|
|
1311
|
+
"nothing-to-wait-for",
|
|
1312
|
+
"timeout",
|
|
1313
|
+
],
|
|
1314
|
+
},
|
|
1185
1315
|
actions: arrayOf(object({
|
|
1186
1316
|
action_key: STRING,
|
|
1187
1317
|
state: STRING,
|
|
@@ -1195,10 +1325,13 @@ const VERBS = [
|
|
|
1195
1325
|
}, ["ok", "task", "status", "actions"]),
|
|
1196
1326
|
error: ERROR_SCHEMA,
|
|
1197
1327
|
exit_codes: [
|
|
1198
|
-
{
|
|
1328
|
+
{
|
|
1329
|
+
code: 0,
|
|
1330
|
+
meaning: "granted; or executed on the policy's own authority after its request ended; or nothing-to-wait-for, a registered task with no requests or whose every grant is already spent. `status` says which, and only `granted` is a grant",
|
|
1331
|
+
},
|
|
1199
1332
|
{
|
|
1200
1333
|
code: 1,
|
|
1201
|
-
meaning: "NOT AUTHORIZED and terminal — a human said no (rejected/revoked), or the requester withdrew the request; or the
|
|
1334
|
+
meaning: "NOT AUTHORIZED and terminal — a human said no (rejected/revoked), or the requester withdrew the request (`status` says which); or the task has no task.registered record (refusal `not-registered`); or the log is corrupt",
|
|
1202
1335
|
},
|
|
1203
1336
|
USAGE,
|
|
1204
1337
|
{ code: 3, meaning: "EXPIRED — the TTL lapsed before a decision landed; or a torn tail" },
|
|
@@ -1207,6 +1340,10 @@ const VERBS = [
|
|
|
1207
1340
|
code: 6,
|
|
1208
1341
|
meaning: "TIMEOUT — the wait elapsed with request(s) undecided. Nothing was appended, they are still live, and waiting again is legitimate",
|
|
1209
1342
|
},
|
|
1343
|
+
{
|
|
1344
|
+
code: 7,
|
|
1345
|
+
meaning: "VOID — the policy was re-attested since the request was routed, and the answer can never be used: a PENDING request (any task; no decision may land on it) or a HARNESS grant (execution: harness, every proposal included; its spend refuses policy-drift). A token grant for approval run is not void and reads granted. Ask again: for a proposal, the same propose call re-files",
|
|
1346
|
+
},
|
|
1210
1347
|
],
|
|
1211
1348
|
},
|
|
1212
1349
|
{
|
|
@@ -1224,7 +1361,20 @@ const VERBS = [
|
|
|
1224
1361
|
requested_ts: STRING,
|
|
1225
1362
|
seq: INTEGER,
|
|
1226
1363
|
ttl_remaining_ms: nullable(INTEGER),
|
|
1227
|
-
|
|
1364
|
+
// APRV-423: the whole window `ttl_remaining_ms` counts down from,
|
|
1365
|
+
// which is the policy's TTL narrowed by the requesting hook's
|
|
1366
|
+
// harness cap; null only when nothing bounds the request.
|
|
1367
|
+
ttl_ms: nullable(INTEGER),
|
|
1368
|
+
}, [
|
|
1369
|
+
"action_key",
|
|
1370
|
+
"task",
|
|
1371
|
+
"class",
|
|
1372
|
+
"est_cost_usd",
|
|
1373
|
+
"requested_ts",
|
|
1374
|
+
"seq",
|
|
1375
|
+
"ttl_remaining_ms",
|
|
1376
|
+
"ttl_ms",
|
|
1377
|
+
])),
|
|
1228
1378
|
}, ["ok", "pending"]),
|
|
1229
1379
|
error: ERROR_SCHEMA,
|
|
1230
1380
|
exit_codes: [OK, USAGE, TORN, IO],
|
|
@@ -1312,13 +1462,15 @@ const VERBS = [
|
|
|
1312
1462
|
},
|
|
1313
1463
|
{
|
|
1314
1464
|
name: "status",
|
|
1315
|
-
purpose: "System HEALTH, from the log: attestation state, the latest chain verdict, dangling executions, indeterminate executions, budget headroom from a zero-cost probe, loop escalations, and the payload store's size. Exit 1 when any of those needs attention. `dangling` is executions the runtime meant to watch and did not, and never harness executions, which are terminal by design and gain no outcome; `indeterminate` is side effects that were attempted and whose fate nobody has established, and it appears only when there are some. This is what an operator must fix; `queue` is what a human must answer, and neither carries the other's content. Writes nothing.",
|
|
1465
|
+
purpose: "System HEALTH, from the log: attestation state, the latest chain verdict, dangling executions, indeterminate executions, budget headroom from a zero-cost probe, loop escalations, and the payload store's size. Exit 1 when any of those needs attention. `dangling` is executions the runtime meant to watch and did not, and never harness executions, which are terminal by design and gain no outcome; `indeterminate` is side effects that were attempted and whose fate nobody has established, and it appears only when there are some. `refusals` counts the decisions and the gestures a decision surface would not take, with the newest few of each, and it appears only when the log carries one: it is informational, like `coverage`, because a refusal is the gate having worked. This is what an operator must fix; `queue` is what a human must answer, and neither carries the other's content. Writes nothing.",
|
|
1316
1466
|
human_only: false,
|
|
1317
1467
|
input: input({ flags: { ...POLICY_FLAGS, ...LOG_FLAG, ...JSON_FLAG, ...HELP_FLAGS } }),
|
|
1318
1468
|
output: object({
|
|
1319
1469
|
ok: { const: true },
|
|
1320
1470
|
healthy: BOOLEAN,
|
|
1321
|
-
|
|
1471
|
+
// APRV-449: `attested_by` is the actor of the attesting record at `seq`,
|
|
1472
|
+
// read from the verified log, `null` when there is none. Always present.
|
|
1473
|
+
attestation: object({ state: STRING, seq: nullable(INTEGER), attested_by: nullable(STRING) }, ["state", "seq", "attested_by"]),
|
|
1322
1474
|
verification: object({ status: STRING, records: nullable(INTEGER) }, ["status", "records"]),
|
|
1323
1475
|
dangling: arrayOf(OPEN_OBJECT),
|
|
1324
1476
|
indeterminate: arrayOf(OPEN_OBJECT),
|
|
@@ -1346,10 +1498,34 @@ const VERBS = [
|
|
|
1346
1498
|
reconciliation: arrayOf(OPEN_OBJECT),
|
|
1347
1499
|
payload_store: OPEN_OBJECT,
|
|
1348
1500
|
anomalies: arrayOf(OPEN_OBJECT),
|
|
1501
|
+
// APRV-376: the two refusal families, counted, with the newest few of
|
|
1502
|
+
// each. INFORMATIONAL and outside `healthy` and the exit code, exactly
|
|
1503
|
+
// as `anomalies` and `coverage` are: a refusal is the gate having
|
|
1504
|
+
// worked. Present only when the log carries one of either family, and
|
|
1505
|
+
// each family present only when it has one, so a repository where
|
|
1506
|
+
// nothing has been refused emits the object it always emitted. Neither
|
|
1507
|
+
// is reported anywhere else short of `approval log tail`.
|
|
1508
|
+
refusals: object({
|
|
1509
|
+
decision: REFUSAL_FAMILY,
|
|
1510
|
+
gesture: REFUSAL_FAMILY,
|
|
1511
|
+
}, []),
|
|
1349
1512
|
// APRV-214: present only while a window stands, so a repository with
|
|
1350
1513
|
// none emits the object it always emitted. It counts toward `healthy`,
|
|
1351
1514
|
// which is why it is reported here rather than only by `gate status`.
|
|
1352
1515
|
gate_window: OPEN_OBJECT,
|
|
1516
|
+
// APRV-383: the daemon identity this instance resolves — the id a daemon
|
|
1517
|
+
// started HERE would write onto every record it appends, where the id
|
|
1518
|
+
// came from, and the `daemons` allowlist in force for it. ALWAYS present,
|
|
1519
|
+
// like `coverage`, so every consumer sees the same three keys on every
|
|
1520
|
+
// log. INFORMATIONAL and outside `healthy` and the exit code: nothing
|
|
1521
|
+
// here is a fact about this log's integrity. `id: null` is an
|
|
1522
|
+
// `APPROVAL_DAEMON_ID` that is not an id; `allowed: null` is no
|
|
1523
|
+
// restriction, which is not the same fact as an empty list.
|
|
1524
|
+
daemon: object({
|
|
1525
|
+
id: nullable(STRING),
|
|
1526
|
+
source: nullable(STRING),
|
|
1527
|
+
allowed: { anyOf: [{ type: "array", items: STRING }, { type: "null" }] },
|
|
1528
|
+
}, ["id", "source", "allowed"]),
|
|
1353
1529
|
}, [
|
|
1354
1530
|
"ok",
|
|
1355
1531
|
"healthy",
|
|
@@ -1362,6 +1538,7 @@ const VERBS = [
|
|
|
1362
1538
|
"coverage",
|
|
1363
1539
|
"reconciliation",
|
|
1364
1540
|
"payload_store",
|
|
1541
|
+
"daemon",
|
|
1365
1542
|
]),
|
|
1366
1543
|
error: ERROR_SCHEMA,
|
|
1367
1544
|
exit_codes: [OK, { code: 1, meaning: "something needs attention" }, USAGE, IO],
|
|
@@ -1377,7 +1554,7 @@ const VERBS = [
|
|
|
1377
1554
|
"--since": "string",
|
|
1378
1555
|
"--until": "string",
|
|
1379
1556
|
"--source": "string",
|
|
1380
|
-
"--vault": "
|
|
1557
|
+
"--vault": "path",
|
|
1381
1558
|
...POLICY_FLAGS,
|
|
1382
1559
|
...LOG_FLAG,
|
|
1383
1560
|
...JSON_FLAG,
|
|
@@ -1426,9 +1603,9 @@ const VERBS = [
|
|
|
1426
1603
|
flags: {
|
|
1427
1604
|
...LOG_FLAG,
|
|
1428
1605
|
...POLICY_FLAGS,
|
|
1429
|
-
"--tasks": "
|
|
1606
|
+
"--tasks": "path",
|
|
1430
1607
|
"--api-base": "string",
|
|
1431
|
-
"--root": "
|
|
1608
|
+
"--root": "path",
|
|
1432
1609
|
...JSON_FLAG,
|
|
1433
1610
|
...HELP_FLAGS,
|
|
1434
1611
|
},
|
|
@@ -1460,7 +1637,7 @@ const VERBS = [
|
|
|
1460
1637
|
flags: {
|
|
1461
1638
|
...LOG_FLAG,
|
|
1462
1639
|
"--policy-dir": "string",
|
|
1463
|
-
"--policy": "
|
|
1640
|
+
"--policy": "path",
|
|
1464
1641
|
"--payload-dir": "string",
|
|
1465
1642
|
...AS_FLAG,
|
|
1466
1643
|
"--interactive": "boolean",
|
|
@@ -1539,6 +1716,28 @@ const VERBS = [
|
|
|
1539
1716
|
error: ERROR_SCHEMA,
|
|
1540
1717
|
exit_codes: [OK, { code: 1, meaning: "a credential variable is unset" }, USAGE],
|
|
1541
1718
|
},
|
|
1719
|
+
{
|
|
1720
|
+
name: "channel",
|
|
1721
|
+
subcommand: "relay",
|
|
1722
|
+
purpose: "Serve POST /relay/gesture on loopback for an operator's control plane (APRV-455): propose, attest or decline a policy by sha256, or grant or reject a request by action key, attributed to the EdgeOS account the attested policy maps. Authenticated by APPROVAL_RELAY_SECRET from the launch environment; a post without it is refused and never logged. It holds no human identity: an unmapped account is refused sender-unmapped. The raw execution token of a grant never leaves the process.",
|
|
1723
|
+
human_only: true,
|
|
1724
|
+
human_only_note: "It records human decisions and attestations on the word of whoever holds a secret its launcher chose, so starting it is the operator's ceremony: no wrapper publishes it, neither serve credential reaches it, and the hook classifier holds the command line `policy.core` (human-only in the reference policy). The attribution it records is exactly as strong as control over who launches it.",
|
|
1725
|
+
input: input({
|
|
1726
|
+
flags: {
|
|
1727
|
+
"--listen": "string",
|
|
1728
|
+
"--port": "string",
|
|
1729
|
+
"--allow-non-loopback": "boolean",
|
|
1730
|
+
"--proposer": "string",
|
|
1731
|
+
...POLICY_FLAGS,
|
|
1732
|
+
...LOG_FLAG,
|
|
1733
|
+
...JSON_FLAG,
|
|
1734
|
+
...HELP_FLAGS,
|
|
1735
|
+
},
|
|
1736
|
+
}),
|
|
1737
|
+
output: null,
|
|
1738
|
+
error: ERROR_SCHEMA,
|
|
1739
|
+
exit_codes: BASE_EXIT_CODES,
|
|
1740
|
+
},
|
|
1542
1741
|
{
|
|
1543
1742
|
name: "daemon",
|
|
1544
1743
|
subcommand: "run",
|
|
@@ -1548,8 +1747,8 @@ const VERBS = [
|
|
|
1548
1747
|
input: input({
|
|
1549
1748
|
flags: {
|
|
1550
1749
|
...LOG_FLAG,
|
|
1551
|
-
"--tasks": "
|
|
1552
|
-
"--out": "
|
|
1750
|
+
"--tasks": "path",
|
|
1751
|
+
"--out": "path",
|
|
1553
1752
|
...POLICY_FLAGS,
|
|
1554
1753
|
"--interval": "string",
|
|
1555
1754
|
"--debounce": "string",
|
|
@@ -1575,8 +1774,8 @@ const VERBS = [
|
|
|
1575
1774
|
input: input({
|
|
1576
1775
|
flags: {
|
|
1577
1776
|
...LOG_FLAG,
|
|
1578
|
-
"--tasks": "
|
|
1579
|
-
"--out": "
|
|
1777
|
+
"--tasks": "path",
|
|
1778
|
+
"--out": "path",
|
|
1580
1779
|
...POLICY_FLAGS,
|
|
1581
1780
|
"--interval": "string",
|
|
1582
1781
|
"--debounce": "string",
|
|
@@ -1616,6 +1815,33 @@ const VERBS = [
|
|
|
1616
1815
|
error: ERROR_SCHEMA,
|
|
1617
1816
|
exit_codes: READ_ONLY_EXIT_CODES,
|
|
1618
1817
|
},
|
|
1818
|
+
{
|
|
1819
|
+
name: "payload",
|
|
1820
|
+
subcommand: "run",
|
|
1821
|
+
purpose: "Print the payload `approval run` will recompute for a command before it spawns it: the argv, the cwd, and — since APRV-401 — the size and SHA-256 of the script that argv names, so a grant over a script binds the SCRIPT'S BYTES and not its path. A script is a known interpreter (the shells, node, python3 and kin) followed by a path operand, or a path at argv[0] the kernel reads a shebang from. With --hash it prints the payload_hash instead, the value a task file's declaration carries. An argv naming no script carries no `script` key and hashes exactly as it did before the rule existed. An inline program is already in the argv; the interpreter itself, a launcher this runtime does not name, whatever the script itself reads or runs, and the window between this hash and the spawn are all outside the binding. Reads that one file and nothing else: no log, no policy, no network, no token, and it executes nothing.",
|
|
1822
|
+
human_only: false,
|
|
1823
|
+
human_only_note: "Agent-facing by construction: it describes a command an agent is about to ask permission for, before any approval exists, and produces no authority of any kind. The bytes it prints are checked at intake and recomputed at execution, so a wrong answer here refuses rather than widens.",
|
|
1824
|
+
input: input({
|
|
1825
|
+
positionals: NO_POSITIONALS,
|
|
1826
|
+
flags: {
|
|
1827
|
+
// Path-typed, and it matters: the value is the directory every relative
|
|
1828
|
+
// argv word is resolved against, so a caller who could name any
|
|
1829
|
+
// directory could ask this verb to digest any file under it.
|
|
1830
|
+
"--cwd": "path",
|
|
1831
|
+
"--hash": "boolean",
|
|
1832
|
+
...JSON_FLAG,
|
|
1833
|
+
...HELP_FLAGS,
|
|
1834
|
+
},
|
|
1835
|
+
trailing: TRAILING,
|
|
1836
|
+
}),
|
|
1837
|
+
// The canonical payload itself on stdout, as `agentmail-draft` does it: the
|
|
1838
|
+
// bytes ARE the result, and an envelope around them would be a second thing
|
|
1839
|
+
// to strip before hashing. `--hash --json` prints {ok, hash}, which is the
|
|
1840
|
+
// one shape here that is a RESULT rather than the payload.
|
|
1841
|
+
output: null,
|
|
1842
|
+
error: ERROR_SCHEMA,
|
|
1843
|
+
exit_codes: READ_ONLY_EXIT_CODES,
|
|
1844
|
+
},
|
|
1619
1845
|
{
|
|
1620
1846
|
name: "payload",
|
|
1621
1847
|
subcommand: "agentmail-draft",
|
|
@@ -1667,7 +1893,7 @@ const VERBS = [
|
|
|
1667
1893
|
"--message": "string",
|
|
1668
1894
|
"--task": "string",
|
|
1669
1895
|
"--session": "string",
|
|
1670
|
-
"--journal": "
|
|
1896
|
+
"--journal": "path",
|
|
1671
1897
|
...AS_FLAG,
|
|
1672
1898
|
...JSON_FLAG,
|
|
1673
1899
|
...HELP_FLAGS,
|
|
@@ -1743,7 +1969,7 @@ const VERBS = [
|
|
|
1743
1969
|
flags: {
|
|
1744
1970
|
"--limit": "string",
|
|
1745
1971
|
"--since": "string",
|
|
1746
|
-
"--journal": "
|
|
1972
|
+
"--journal": "path",
|
|
1747
1973
|
...JSON_FLAG,
|
|
1748
1974
|
...HELP_FLAGS,
|
|
1749
1975
|
},
|
|
@@ -1919,7 +2145,7 @@ const VERBS = [
|
|
|
1919
2145
|
"--logs": "string",
|
|
1920
2146
|
"--env-file": "string",
|
|
1921
2147
|
"--exec": "string",
|
|
1922
|
-
"--out": "
|
|
2148
|
+
"--out": "path",
|
|
1923
2149
|
"--uninstall": "boolean",
|
|
1924
2150
|
...AS_FLAG,
|
|
1925
2151
|
...LOG_FLAG,
|
|
@@ -1940,7 +2166,7 @@ const VERBS = [
|
|
|
1940
2166
|
positionals: positionals([{ name: "name", description: "the credential name" }], 1),
|
|
1941
2167
|
flags: {
|
|
1942
2168
|
"--value-env": "string",
|
|
1943
|
-
"--vault": "
|
|
2169
|
+
"--vault": "path",
|
|
1944
2170
|
...LOG_FLAG,
|
|
1945
2171
|
...POLICY_FLAGS,
|
|
1946
2172
|
...AS_FLAG,
|
|
@@ -1960,7 +2186,7 @@ const VERBS = [
|
|
|
1960
2186
|
human_only_note: "Names are not values, but the name set is a map of the machine's reach, and all three vault verbs resolve identity exactly as `policy attest` does. Keeping the noun human-only is what stops an agent's tooling from touching the credential store in passing.",
|
|
1961
2187
|
input: input({
|
|
1962
2188
|
flags: {
|
|
1963
|
-
"--vault": "
|
|
2189
|
+
"--vault": "path",
|
|
1964
2190
|
...LOG_FLAG,
|
|
1965
2191
|
...POLICY_FLAGS,
|
|
1966
2192
|
...AS_FLAG,
|
|
@@ -1986,7 +2212,7 @@ const VERBS = [
|
|
|
1986
2212
|
input: input({
|
|
1987
2213
|
positionals: positionals([{ name: "name", description: "the credential name" }], 1),
|
|
1988
2214
|
flags: {
|
|
1989
|
-
"--vault": "
|
|
2215
|
+
"--vault": "path",
|
|
1990
2216
|
...LOG_FLAG,
|
|
1991
2217
|
...POLICY_FLAGS,
|
|
1992
2218
|
...AS_FLAG,
|
|
@@ -2013,9 +2239,9 @@ const VERBS = [
|
|
|
2013
2239
|
positionals: positionals([{ name: "action-key", description: "the action's idempotency_key" }], 1),
|
|
2014
2240
|
flags: {
|
|
2015
2241
|
"--token": "string",
|
|
2016
|
-
"--payload": "
|
|
2242
|
+
"--payload": "path",
|
|
2017
2243
|
...AS_FLAG,
|
|
2018
|
-
"--vault": "
|
|
2244
|
+
"--vault": "path",
|
|
2019
2245
|
...POLICY_FLAGS,
|
|
2020
2246
|
...LOG_FLAG,
|
|
2021
2247
|
"--timeout": "string",
|
|
@@ -2070,9 +2296,9 @@ const VERBS = [
|
|
|
2070
2296
|
positionals: positionals([{ name: "action-key", description: "the action's idempotency_key" }], 1),
|
|
2071
2297
|
flags: {
|
|
2072
2298
|
"--token": "string",
|
|
2073
|
-
"--payload": "
|
|
2299
|
+
"--payload": "path",
|
|
2074
2300
|
...AS_FLAG,
|
|
2075
|
-
"--vault": "
|
|
2301
|
+
"--vault": "path",
|
|
2076
2302
|
...POLICY_FLAGS,
|
|
2077
2303
|
...LOG_FLAG,
|
|
2078
2304
|
"--timeout": "string",
|
|
@@ -2126,7 +2352,7 @@ const VERBS = [
|
|
|
2126
2352
|
input: input({
|
|
2127
2353
|
positionals: positionals([{ name: "action-key", description: "the action's idempotency_key" }], 1),
|
|
2128
2354
|
flags: {
|
|
2129
|
-
"--token": "string", "--payload": "
|
|
2355
|
+
"--token": "string", "--payload": "path", ...AS_FLAG, "--vault": "path",
|
|
2130
2356
|
...POLICY_FLAGS, ...LOG_FLAG, "--timeout": "string", ...JSON_FLAG, ...HELP_FLAGS,
|
|
2131
2357
|
},
|
|
2132
2358
|
}),
|
|
@@ -2150,6 +2376,7 @@ const VERBS = [
|
|
|
2150
2376
|
...AS_FLAG,
|
|
2151
2377
|
"--timeout": "string",
|
|
2152
2378
|
"--interval": "string",
|
|
2379
|
+
"--harness-cap": "string",
|
|
2153
2380
|
...POLICY_FLAGS,
|
|
2154
2381
|
...LOG_FLAG,
|
|
2155
2382
|
...HELP_FLAGS,
|
|
@@ -2173,6 +2400,7 @@ const VERBS = [
|
|
|
2173
2400
|
...AS_FLAG,
|
|
2174
2401
|
"--timeout": "string",
|
|
2175
2402
|
"--interval": "string",
|
|
2403
|
+
"--harness-cap": "string",
|
|
2176
2404
|
...POLICY_FLAGS,
|
|
2177
2405
|
...LOG_FLAG,
|
|
2178
2406
|
...HELP_FLAGS,
|
|
@@ -2197,6 +2425,7 @@ const VERBS = [
|
|
|
2197
2425
|
"--timeout": "string",
|
|
2198
2426
|
"--interval": "string",
|
|
2199
2427
|
"--retry-grace": "string",
|
|
2428
|
+
"--harness-cap": "string",
|
|
2200
2429
|
...POLICY_FLAGS,
|
|
2201
2430
|
...LOG_FLAG,
|
|
2202
2431
|
...HELP_FLAGS,
|
|
@@ -2221,6 +2450,7 @@ const VERBS = [
|
|
|
2221
2450
|
"--timeout": "string",
|
|
2222
2451
|
"--interval": "string",
|
|
2223
2452
|
"--retry-grace": "string",
|
|
2453
|
+
"--harness-cap": "string",
|
|
2224
2454
|
...POLICY_FLAGS,
|
|
2225
2455
|
...LOG_FLAG,
|
|
2226
2456
|
...HELP_FLAGS,
|
|
@@ -2245,6 +2475,7 @@ const VERBS = [
|
|
|
2245
2475
|
"--timeout": "string",
|
|
2246
2476
|
"--interval": "string",
|
|
2247
2477
|
"--retry-grace": "string",
|
|
2478
|
+
"--harness-cap": "string",
|
|
2248
2479
|
...POLICY_FLAGS,
|
|
2249
2480
|
...LOG_FLAG,
|
|
2250
2481
|
...HELP_FLAGS,
|
|
@@ -2257,6 +2488,31 @@ const VERBS = [
|
|
|
2257
2488
|
{ code: 2, meaning: "the hook is misconfigured or a PostToolUse outcome was not recorded; the diagnostic is on stderr" },
|
|
2258
2489
|
],
|
|
2259
2490
|
},
|
|
2491
|
+
{
|
|
2492
|
+
name: "hook",
|
|
2493
|
+
subcommand: "hermes",
|
|
2494
|
+
purpose: "Read one Hermes Agent pre_tool_call or post_tool_call event on stdin (snake_case, and the event names are its own rather than Claude Code's), classify its terminal command or its write_file and patch paths and its read paths, resolve the class against APPROVAL.md, and answer allow or deny — waiting on a real decision when the class is manual. THE VERDICT IS NEVER 'ask'. It answers in ONE dialect and nothing else, Hermes's own {action, message} object: a deny is that object AT EXIT 2, which Hermes documents as an unconditional block whose message it reads from the stdout directive first (so body and code agree by the harness's own rule, and a refusal whose body never made it out still blocks); an allow is {} at exit 0, because Hermes has no allow directive and no directive is the allow, so the allow's reason goes to stderr. The Claude-compatible {decision, reason} form is never emitted beside it, because Muse established that a refusal hedged across dialects is read as a broken hook rather than as a stronger no. execute_code is refused outright (hook-hermes-execute-code-unbound): the call carries a program and no path, no argv and no working directory, so no class can be resolved and no payload can bind what it would do. A terminal call that names no absolute workdir, and a write_file, patch, read_file or search_files call whose path is relative or absent, are refused too (hook-unsupported-execution-context, APRV-415): the live probe found that Hermes resolves both against a per-session recorded directory that a cd moves and that no field of the event carries, so the verdict would bind bytes in a directory the hook cannot see, and the refusal names the retry — an absolute path — so the session repairs itself. A per-entry fail_closed makes a hook crash, timeout or unparseable output BLOCK, which the same probe OBSERVED on Hermes main at 118984d7 in three trials of three, making this the first harness since Claude Code whose hook is a gate rather than a backstop; its default is false and v0.21.3 (build 2026.9.14) ignores the key silently, so docs/hermes-hook.md states the version floor before it states anything else and approval doctor's harness-version row reports a build below it.",
|
|
2495
|
+
human_only: false,
|
|
2496
|
+
human_only_note: "The agent harness surface, so agent-facing by construction: Hermes invokes it around the agent's own tool calls. It proposes and waits; it never records a decision.",
|
|
2497
|
+
input: input({
|
|
2498
|
+
flags: {
|
|
2499
|
+
...AS_FLAG,
|
|
2500
|
+
"--timeout": "string",
|
|
2501
|
+
"--interval": "string",
|
|
2502
|
+
"--retry-grace": "string",
|
|
2503
|
+
"--harness-cap": "string",
|
|
2504
|
+
...POLICY_FLAGS,
|
|
2505
|
+
...LOG_FLAG,
|
|
2506
|
+
...HELP_FLAGS,
|
|
2507
|
+
},
|
|
2508
|
+
}),
|
|
2509
|
+
output: null,
|
|
2510
|
+
error: ERROR_SCHEMA,
|
|
2511
|
+
exit_codes: [
|
|
2512
|
+
{ code: 0, meaning: "an allow ({} on stdout, the reason on stderr), and every post_tool_call report, which exits 0 whatever it found because Hermes reads exit 2 as a block and the call has already run" },
|
|
2513
|
+
{ code: 2, meaning: "a deny: the {action:'block', message} object on stdout is the message and the exit code is the unconditional block under it; also a usage error before any event was read, which prints no verdict" },
|
|
2514
|
+
],
|
|
2515
|
+
},
|
|
2260
2516
|
{
|
|
2261
2517
|
name: "hook",
|
|
2262
2518
|
subcommand: "classify",
|
|
@@ -2278,7 +2534,7 @@ const VERBS = [
|
|
|
2278
2534
|
human_only: false,
|
|
2279
2535
|
input: input({
|
|
2280
2536
|
positionals: positionals([{ name: "file", description: "the markdown file to parse" }], 1),
|
|
2281
|
-
flags: { "--out": "
|
|
2537
|
+
flags: { "--out": "path", ...JSON_FLAG, ...HELP_FLAGS },
|
|
2282
2538
|
}),
|
|
2283
2539
|
output: object({
|
|
2284
2540
|
ok: { const: true },
|
|
@@ -2459,6 +2715,49 @@ const VERBS = [
|
|
|
2459
2715
|
{ code: 4, meaning: "the transport did not close cleanly" },
|
|
2460
2716
|
],
|
|
2461
2717
|
},
|
|
2718
|
+
{
|
|
2719
|
+
name: "serve",
|
|
2720
|
+
purpose: "Serve the verbs of this registry over HTTP, in the foreground, for a harness in a sandbox that has no local log and no local policy. The published surface is `mcp serve`'s exactly — the registry filtered by human_only false, `--as` deleted from every schema, the launch identity appended last, no grant — plus the three things the MCP transport withheld for transport reasons: `hook/<harness>` taking the harness envelope as the request body and answering the bytes the stdin form prints, `log/follow` paged by an exclusive (seq, hash) cursor, and `export`, the store as an archive carrying the policy, the log, the payload bytes behind every payload_hash and the projections, and never the keys, the environment source map or the vault. TWO bearer credentials come from the launch environment and authorization is PER VERB: the agent one opens the hook and an allowlist of what a harness under oversight needs in order to ask and to act on a grant, the tenant one opens everything else, so the agent never reads the log it is judged by and a verb added to this registry is the tenant's until somebody widens the list. Binds loopback unless an explicit flag widens it; it terminates no TLS.",
|
|
2721
|
+
human_only: true,
|
|
2722
|
+
human_only_note: "An OPERATOR process, for `mcp serve`'s reason and one more. It is long-lived, launched by a person, and holds the agent identity every call is recorded under; on top of that it holds BOTH credentials, and the party that decides which door an agent gets is not the agent.",
|
|
2723
|
+
input: input({
|
|
2724
|
+
flags: {
|
|
2725
|
+
...AS_FLAG,
|
|
2726
|
+
"--port": "string",
|
|
2727
|
+
"--listen": "string",
|
|
2728
|
+
"--allow-non-loopback": "boolean",
|
|
2729
|
+
"--hook-timeout": "string",
|
|
2730
|
+
"--hook-harness-cap": "string",
|
|
2731
|
+
"--hook-threads": "string",
|
|
2732
|
+
"--hook-queue": "string",
|
|
2733
|
+
...POLICY_FLAGS,
|
|
2734
|
+
...LOG_FLAG,
|
|
2735
|
+
...JSON_FLAG,
|
|
2736
|
+
...HELP_FLAGS,
|
|
2737
|
+
},
|
|
2738
|
+
}),
|
|
2739
|
+
output: null,
|
|
2740
|
+
error: ERROR_SCHEMA,
|
|
2741
|
+
exit_codes: [
|
|
2742
|
+
{ code: 0, meaning: "the server was interrupted and closed cleanly" },
|
|
2743
|
+
USAGE,
|
|
2744
|
+
{ code: 4, meaning: "the listener could not bind, or did not close cleanly" },
|
|
2745
|
+
],
|
|
2746
|
+
},
|
|
2747
|
+
{
|
|
2748
|
+
name: "muse",
|
|
2749
|
+
purpose: "Start the single-tenant local Muse consumer prototype. It exposes bounded register and request proposals plus a verified pending inbox, status and canonical request detail, with distinct read and proposal launch credentials. It has no human decision, token, execution, export or generic verb route; the configured Telegram channel remains the human decision path.",
|
|
2750
|
+
human_only: true,
|
|
2751
|
+
human_only_note: "An operator launches this credential-holding listener and pins its tenant and store before accepting calls.",
|
|
2752
|
+
input: input({ flags: { "--dir": "path", "--port": "string", ...HELP_FLAGS } }),
|
|
2753
|
+
output: null,
|
|
2754
|
+
error: ERROR_SCHEMA,
|
|
2755
|
+
exit_codes: [
|
|
2756
|
+
{ code: 0, meaning: "the local listener was interrupted and closed cleanly" },
|
|
2757
|
+
USAGE,
|
|
2758
|
+
{ code: 4, meaning: "the local listener could not bind" },
|
|
2759
|
+
],
|
|
2760
|
+
},
|
|
2462
2761
|
{
|
|
2463
2762
|
name: "reindex",
|
|
2464
2763
|
purpose: "Rebuild the SQLite index projection from the log. The database is a cache and the log is the truth: the index is rebuilt from scratch at a temporary path and renamed into place. A corrupt log is refused outright and a torn tail is refused without --force. The log is never written to.",
|
|
@@ -2466,7 +2765,7 @@ const VERBS = [
|
|
|
2466
2765
|
input: input({
|
|
2467
2766
|
flags: {
|
|
2468
2767
|
...LOG_FLAG,
|
|
2469
|
-
"--index": "
|
|
2768
|
+
"--index": "path",
|
|
2470
2769
|
"--force": "boolean",
|
|
2471
2770
|
...JSON_FLAG,
|
|
2472
2771
|
...HELP_FLAGS,
|
|
@@ -2481,7 +2780,7 @@ const VERBS = [
|
|
|
2481
2780
|
purpose: "Regenerate .approval/QUEUE.md, the read-only markdown queue projection of SPEC.md §9.1, whole, from the verified log. This is the screenshot and never the truth: editing the file authorizes nothing and the next render overwrites it. Writes exactly one file, atomically; a log that does not verify refuses and writes nothing.",
|
|
2482
2781
|
human_only: false,
|
|
2483
2782
|
input: input({
|
|
2484
|
-
flags: { ...LOG_FLAG, "--out": "
|
|
2783
|
+
flags: { ...LOG_FLAG, "--out": "path", ...POLICY_FLAGS, ...JSON_FLAG, ...HELP_FLAGS },
|
|
2485
2784
|
}),
|
|
2486
2785
|
output: object({
|
|
2487
2786
|
ok: { const: true },
|