approval-md 0.1.0 → 0.2.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 +584 -553
- package/SPEC.md +42 -13
- package/dist/src/adapters/agentmail.d.ts +426 -0
- package/dist/src/adapters/agentmail.js +2 -2
- package/dist/src/adapters/conformance.d.ts +149 -0
- package/dist/src/adapters/contract.d.ts +628 -0
- package/dist/src/adapters/contract.js +110 -16
- package/dist/src/adapters/contract.js.map +1 -1
- package/dist/src/adapters/email.d.ts +324 -0
- package/dist/src/adapters/env-passphrase.d.ts +93 -0
- package/dist/src/adapters/public.d.ts +11 -0
- package/dist/src/adapters/public.js +11 -0
- package/dist/src/adapters/public.js.map +1 -0
- package/dist/src/adapters/registry.d.ts +59 -0
- package/dist/src/adapters/registry.js +2 -1
- package/dist/src/adapters/registry.js.map +1 -1
- package/dist/src/adapters/smtp.d.ts +213 -0
- package/dist/src/adapters/vault-provider.d.ts +114 -0
- package/dist/src/adapters/vault-provider.js +3 -3
- package/dist/src/adapters/zzz.d.ts +66 -0
- package/dist/src/adapters/zzz.js +299 -0
- package/dist/src/adapters/zzz.js.map +1 -0
- package/dist/src/channels/batch.d.ts +109 -0
- package/dist/src/channels/cli.d.ts +193 -0
- package/dist/src/channels/conformance.d.ts +92 -0
- package/dist/src/channels/contract.d.ts +623 -0
- package/dist/src/channels/payload-view.d.ts +35 -0
- package/dist/src/channels/render-queue.d.ts +149 -0
- package/dist/src/channels/tagging.d.ts +196 -0
- package/dist/src/channels/telegram.d.ts +1832 -0
- package/dist/src/channels/web.d.ts +341 -0
- package/dist/src/cli/adapter.d.ts +90 -0
- package/dist/src/cli/adapter.js +25 -15
- package/dist/src/cli/adapter.js.map +1 -1
- package/dist/src/cli/amend.d.ts +59 -0
- package/dist/src/cli/args.d.ts +43 -0
- package/dist/src/cli/attest.d.ts +41 -0
- package/dist/src/cli/audit-card.d.ts +62 -0
- package/dist/src/cli/audit.d.ts +59 -0
- package/dist/src/cli/channel-telegram.d.ts +806 -0
- package/dist/src/cli/channel-web.d.ts +131 -0
- package/dist/src/cli/channel.d.ts +71 -0
- package/dist/src/cli/checkpoint-tap.d.ts +169 -0
- package/dist/src/cli/codex.d.ts +2 -0
- package/dist/src/cli/codex.js +172 -0
- package/dist/src/cli/codex.js.map +1 -0
- package/dist/src/cli/coverage.d.ts +61 -0
- package/dist/src/cli/daemon.d.ts +120 -0
- package/dist/src/cli/doctor.d.ts +129 -0
- package/dist/src/cli/doctor.js +119 -5
- package/dist/src/cli/doctor.js.map +1 -1
- package/dist/src/cli/env.d.ts +65 -0
- package/dist/src/cli/execute.d.ts +202 -0
- package/dist/src/cli/exit-codes.d.ts +73 -0
- package/dist/src/cli/feedback.d.ts +60 -0
- package/dist/src/cli/gate-window.d.ts +40 -0
- package/dist/src/cli/gate.d.ts +68 -0
- package/dist/src/cli/git-scope.d.ts +190 -0
- package/dist/src/cli/gloss-attach.d.ts +85 -0
- package/dist/src/cli/gloss-codex-child.d.ts +9 -0
- package/dist/src/cli/gloss-codex.d.ts +24 -0
- package/dist/src/cli/gloss-options.d.ts +42 -0
- package/dist/src/cli/gloss.d.ts +265 -0
- package/dist/src/cli/help.d.ts +103 -0
- package/dist/src/cli/help.js +173 -51
- package/dist/src/cli/help.js.map +1 -1
- package/dist/src/cli/hook-codex.d.ts +78 -0
- package/dist/src/cli/hook-codex.js +167 -0
- package/dist/src/cli/hook-codex.js.map +1 -0
- package/dist/src/cli/hook.d.ts +331 -0
- package/dist/src/cli/hook.js +186 -80
- package/dist/src/cli/hook.js.map +1 -1
- package/dist/src/cli/import.d.ts +35 -0
- package/dist/src/cli/init.d.ts +84 -0
- package/dist/src/cli/init.js +2 -2
- package/dist/src/cli/init.js.map +1 -1
- package/dist/src/cli/instructions.d.ts +23 -0
- package/dist/src/cli/journal.d.ts +41 -0
- package/dist/src/cli/log-advance.d.ts +287 -0
- package/dist/src/cli/log-advance.js +102 -11
- package/dist/src/cli/log-advance.js.map +1 -1
- package/dist/src/cli/log-anchor.d.ts +176 -0
- package/dist/src/cli/log-checkpoint.d.ts +22 -0
- package/dist/src/cli/log-sync.d.ts +243 -0
- package/dist/src/cli/log-verbs.d.ts +16 -0
- package/dist/src/cli/log-verbs.js +7 -1
- package/dist/src/cli/log-verbs.js.map +1 -1
- package/dist/src/cli/long-help.d.ts +70 -0
- package/dist/src/cli/main.d.ts +77 -0
- package/dist/src/cli/main.js +155 -5
- package/dist/src/cli/main.js.map +1 -1
- package/dist/src/cli/mcp.d.ts +52 -0
- package/dist/src/cli/paths.d.ts +56 -0
- package/dist/src/cli/payload.d.ts +58 -0
- package/dist/src/cli/policy.d.ts +43 -0
- package/dist/src/cli/preflight.d.ts +363 -0
- package/dist/src/cli/preflight.js +294 -7
- package/dist/src/cli/preflight.js.map +1 -1
- package/dist/src/cli/progress.d.ts +78 -0
- package/dist/src/cli/prompt.d.ts +209 -0
- package/dist/src/cli/quickstart.d.ts +46 -0
- package/dist/src/cli/quickstart.js +297 -0
- package/dist/src/cli/quickstart.js.map +1 -0
- package/dist/src/cli/records.d.ts +34 -0
- package/dist/src/cli/render.d.ts +22 -0
- package/dist/src/cli/sandbox.d.ts +51 -0
- package/dist/src/cli/scaffold.d.ts +79 -0
- package/dist/src/cli/setup-adapter.d.ts +137 -0
- package/dist/src/cli/setup-adapter.js +38 -4
- package/dist/src/cli/setup-adapter.js.map +1 -1
- package/dist/src/cli/setup-channel.d.ts +117 -0
- package/dist/src/cli/setup-checkpoint.d.ts +57 -0
- package/dist/src/cli/setup-common.d.ts +275 -0
- package/dist/src/cli/setup-flow.d.ts +287 -0
- package/dist/src/cli/setup-service.d.ts +96 -0
- package/dist/src/cli/setup.d.ts +202 -0
- package/dist/src/cli/style.d.ts +320 -0
- package/dist/src/cli/token.d.ts +39 -0
- package/dist/src/cli/up.d.ts +155 -0
- package/dist/src/cli/up.js +4 -2
- package/dist/src/cli/up.js.map +1 -1
- package/dist/src/cli/usage.d.ts +37 -0
- package/dist/src/cli/values.d.ts +40 -0
- package/dist/src/cli/vault.d.ts +59 -0
- package/dist/src/cli/vault.js +2 -2
- package/dist/src/cli/vault.js.map +1 -1
- package/dist/src/cli/verb-registry.d.ts +76 -0
- package/dist/src/cli/verb-registry.js +176 -8
- package/dist/src/cli/verb-registry.js.map +1 -1
- package/dist/src/cli/wordmark.d.ts +31 -0
- package/dist/src/cli/wordmark.js +2 -2
- package/dist/src/codex/doctor.d.ts +13 -0
- package/dist/src/codex/doctor.js +41 -0
- package/dist/src/codex/doctor.js.map +1 -0
- package/dist/src/codex/manifest.d.ts +49 -0
- package/dist/src/codex/manifest.js +103 -0
- package/dist/src/codex/manifest.js.map +1 -0
- package/dist/src/codex/templates.d.ts +41 -0
- package/dist/src/codex/templates.js +319 -0
- package/dist/src/codex/templates.js.map +1 -0
- package/dist/src/codex/trust.d.ts +19 -0
- package/dist/src/codex/trust.js +183 -0
- package/dist/src/codex/trust.js.map +1 -0
- package/dist/src/codex/workspace-plan.d.ts +131 -0
- package/dist/src/codex/workspace-plan.js +561 -0
- package/dist/src/codex/workspace-plan.js.map +1 -0
- package/dist/src/core/actor.d.ts +2 -0
- package/dist/src/core/actor.js +5 -0
- package/dist/src/core/actor.js.map +1 -0
- package/dist/src/core/advance-cycle.d.ts +170 -0
- package/dist/src/core/agents-md.d.ts +276 -0
- package/dist/src/core/apply-patch.d.ts +49 -0
- package/dist/src/core/apply-patch.js +266 -0
- package/dist/src/core/apply-patch.js.map +1 -0
- package/dist/src/core/attest.d.ts +420 -0
- package/dist/src/core/attest.js +13 -1
- package/dist/src/core/attest.js.map +1 -1
- package/dist/src/core/audit.d.ts +492 -0
- package/dist/src/core/budgets.d.ts +238 -0
- package/dist/src/core/checkpoint.d.ts +500 -0
- package/dist/src/core/child-env.d.ts +88 -0
- package/dist/src/core/clock.d.ts +52 -0
- package/dist/src/core/command-class.d.ts +543 -0
- package/dist/src/core/command-class.js +43 -8
- package/dist/src/core/command-class.js.map +1 -1
- package/dist/src/core/coverage-sources/adapter.d.ts +40 -0
- package/dist/src/core/coverage-sources/gh.d.ts +48 -0
- package/dist/src/core/coverage-sources/git.d.ts +101 -0
- package/dist/src/core/coverage.d.ts +217 -0
- package/dist/src/core/credential-spec.d.ts +72 -0
- package/dist/src/core/dark-session.d.ts +331 -0
- package/dist/src/core/decision-refusal.d.ts +185 -0
- package/dist/src/core/env-file.d.ts +450 -0
- package/dist/src/core/execute.d.ts +858 -0
- package/dist/src/core/execute.js +44 -6
- package/dist/src/core/execute.js.map +1 -1
- package/dist/src/core/frontmatter.d.ts +78 -0
- package/dist/src/core/gate-window.d.ts +312 -0
- package/dist/src/core/gate.d.ts +1364 -0
- package/dist/src/core/gate.js +68 -13
- package/dist/src/core/gate.js.map +1 -1
- package/dist/src/core/git-run.d.ts +73 -0
- package/dist/src/core/harness-version.d.ts +157 -0
- package/dist/src/core/harness-version.js +2 -1
- package/dist/src/core/harness-version.js.map +1 -1
- package/dist/src/core/harness-wait.d.ts +55 -0
- package/dist/src/core/head-retry.d.ts +107 -0
- package/dist/src/core/instance.d.ts +253 -0
- package/dist/src/core/intake-limits.d.ts +247 -0
- package/dist/src/core/jcs.d.ts +52 -0
- package/dist/src/core/journal.d.ts +144 -0
- package/dist/src/core/live-draw.d.ts +436 -0
- package/dist/src/core/log-reconcile.d.ts +89 -0
- package/dist/src/core/log-subscribe.d.ts +36 -0
- package/dist/src/core/log-subscribe.js +162 -0
- package/dist/src/core/log-subscribe.js.map +1 -0
- package/dist/src/core/log.d.ts +278 -0
- package/dist/src/core/loop.d.ts +274 -0
- package/dist/src/core/loop.js +11 -0
- package/dist/src/core/loop.js.map +1 -1
- package/dist/src/core/md-fence.d.ts +41 -0
- package/dist/src/core/money.d.ts +147 -0
- package/dist/src/core/payload-census.d.ts +74 -0
- package/dist/src/core/payload-store.d.ts +175 -0
- package/dist/src/core/payload.d.ts +71 -0
- package/dist/src/core/policy-diff.d.ts +292 -0
- package/dist/src/core/policy-diff.js +27 -4
- package/dist/src/core/policy-diff.js.map +1 -1
- package/dist/src/core/policy-expectations.d.ts +199 -0
- package/dist/src/core/policy-explain.d.ts +150 -0
- package/dist/src/core/policy-explain.js +31 -3
- package/dist/src/core/policy-explain.js.map +1 -1
- package/dist/src/core/policy-load.d.ts +527 -0
- package/dist/src/core/policy-load.js +15 -3
- package/dist/src/core/policy-load.js.map +1 -1
- package/dist/src/core/policy-match.d.ts +281 -0
- package/dist/src/core/policy-match.js +20 -9
- package/dist/src/core/policy-match.js.map +1 -1
- package/dist/src/core/policy-proposal.d.ts +265 -0
- package/dist/src/core/prompt-layout.d.ts +221 -0
- package/dist/src/core/protected-path-guard.d.ts +453 -0
- package/dist/src/core/protected-path-guard.js +514 -35
- package/dist/src/core/protected-path-guard.js.map +1 -1
- package/dist/src/core/registration.d.ts +25 -0
- package/dist/src/core/reindex.d.ts +99 -0
- package/dist/src/core/sampler.d.ts +313 -0
- package/dist/src/core/sandbox.d.ts +290 -0
- package/dist/src/core/seal.d.ts +165 -0
- package/dist/src/core/state.d.ts +505 -0
- package/dist/src/core/task-file.d.ts +185 -0
- package/dist/src/core/telegram-config.d.ts +93 -0
- package/dist/src/core/token.d.ts +409 -0
- package/dist/src/core/token.js +21 -38
- package/dist/src/core/token.js.map +1 -1
- package/dist/src/core/validate.d.ts +138 -0
- package/dist/src/core/values.d.ts +137 -0
- package/dist/src/core/vault.d.ts +291 -0
- package/dist/src/core/verified-snapshot.d.ts +204 -0
- package/dist/src/core/verify.d.ts +336 -0
- package/dist/src/core/version.d.ts +8 -0
- package/dist/src/core/wysiwys.d.ts +370 -0
- package/dist/src/daemon/advance-child.d.ts +39 -0
- package/dist/src/daemon/advance.d.ts +466 -0
- package/dist/src/daemon/audit.d.ts +87 -0
- package/dist/src/daemon/daemon.d.ts +1180 -0
- package/dist/src/daemon/dark-session.d.ts +64 -0
- package/dist/src/daemon/draw-child.d.ts +36 -0
- package/dist/src/daemon/draw.d.ts +154 -0
- package/dist/src/daemon/git-evidence.d.ts +173 -0
- package/dist/src/daemon/git-evidence.js +1 -1
- package/dist/src/daemon/projection.d.ts +180 -0
- package/dist/src/daemon/prune.d.ts +207 -0
- package/dist/src/mcp/http.d.ts +113 -0
- package/dist/src/mcp/server.d.ts +265 -0
- package/dist/src/mcp/server.js +9 -1
- package/dist/src/mcp/server.js.map +1 -1
- package/docs/adapter-api.md +106 -0
- package/docs/cli-reference.md +389 -36
- package/docs/codex-enforced-session.md +30 -0
- package/package.json +12 -2
- package/schema/codex-instance.schema.json +82 -0
- package/schema/event.schema.json +2 -1
- package/schema/fixtures/codex-instance/invalid/unpinned-codex-version.json +40 -0
- package/schema/fixtures/codex-instance/valid/canonical.json +40 -0
- package/schema/policy.schema.json +21 -1
- package/templates/codex/README.md +9 -0
|
@@ -0,0 +1,281 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Class matching and autonomy resolution (SPEC.md §5.2, §7).
|
|
3
|
+
*
|
|
4
|
+
* Given a loaded policy and an action class, decide the autonomy level that
|
|
5
|
+
* governs the action, and say *why* — which rule matched, which rules were
|
|
6
|
+
* considered, and whether the irreversibility floor overrode the match.
|
|
7
|
+
*
|
|
8
|
+
* This module is pure and deterministic: no I/O, no clock, no randomness, no
|
|
9
|
+
* caching. The same `(load, actionClass, options)` always yields a deeply equal
|
|
10
|
+
* `Resolution`. Every ordering decision below is total, so candidate order and
|
|
11
|
+
* winner selection never depend on object key insertion order beyond what is
|
|
12
|
+
* explicitly documented.
|
|
13
|
+
*
|
|
14
|
+
* ## Matching grammar (SPEC.md §5.2, `schema/policy.schema.json` `classPattern`)
|
|
15
|
+
*
|
|
16
|
+
* Patterns and classes are dot-separated segments. Within a pattern:
|
|
17
|
+
*
|
|
18
|
+
* - a literal segment matches exactly that segment;
|
|
19
|
+
* - `*` in a non-final position matches exactly one segment;
|
|
20
|
+
* - a **trailing** `.*` matches **one or more** remaining segments of any depth.
|
|
21
|
+
*
|
|
22
|
+
* The "one or more" is a deliberate choice: `read.*` matches `read.web` and
|
|
23
|
+
* `read.web.page` but **not** the bare class `read`. The schema's
|
|
24
|
+
* `classPattern` admits `read` and `read.*` as two distinct keys a policy may
|
|
25
|
+
* list separately with different autonomy, so they must not be aliases of one
|
|
26
|
+
* another; and §7 introduces `read.*` as a *namespace*, i.e. the things under
|
|
27
|
+
* `read`, not `read` itself. A policy that wants the bare class covered writes
|
|
28
|
+
* it as its own rule (or relies on `defaults.autonomy`).
|
|
29
|
+
*
|
|
30
|
+
* A bare `*` pattern is a single-segment pattern whose only segment is a
|
|
31
|
+
* wildcard, and it is not "trailing `.*`" — it matches any single-segment class
|
|
32
|
+
* (`read`, `deploy`) and nothing deeper. `*.*` is a wildcard followed by a
|
|
33
|
+
* trailing wildcard, so it matches any class of TWO OR MORE segments. (APRV-137
|
|
34
|
+
* corrects this line, which read "exactly two". The trailing `.*` consumes one
|
|
35
|
+
* or more, so `*.*` matches `a.b` and `a.b.c` alike. `matchesPattern` always
|
|
36
|
+
* behaved this way; only the comment was wrong.)
|
|
37
|
+
*
|
|
38
|
+
* ## Specificity (SPEC.md §5.2, Specificity bullet)
|
|
39
|
+
*
|
|
40
|
+
* Candidates are ordered by the two-part key
|
|
41
|
+
* `(literalSegments DESC, wildcardSegments ASC)`. A trailing `.*` counts as one
|
|
42
|
+
* wildcard segment and contributes no literals. Patterns still tied are equally
|
|
43
|
+
* specific, and then "deny beats allow" decides: the strictest autonomy among
|
|
44
|
+
* the tied rules wins.
|
|
45
|
+
*
|
|
46
|
+
* ## Fail-closed
|
|
47
|
+
*
|
|
48
|
+
* ## The autonomy split (amended SPEC.md §5.2, APRV-127)
|
|
49
|
+
*
|
|
50
|
+
* A rule may declare `supervised-live` (with a `live_rate`) or `supervised-retro`
|
|
51
|
+
* as well as the pre-split `supervised`. Both collapse onto the one enforced
|
|
52
|
+
* `supervised` autonomy, and the difference travels beside it as
|
|
53
|
+
* `Resolution.supervision`: `"live"` means a `live_rate` fraction of the class's
|
|
54
|
+
* actions stop at the human gate BEFORE executing, `"retro"` means every action
|
|
55
|
+
* proceeds and a fraction is reviewed AFTERWARDS. Bare `supervised` is `"retro"`,
|
|
56
|
+
* with a load-time note from `policy-load.ts` naming the alias.
|
|
57
|
+
*
|
|
58
|
+
* Nothing about the SELECTION lives here: this module is pure, and selection
|
|
59
|
+
* needs an operator-held secret. `core/sampler.ts` derives it, and `core/gate.ts`
|
|
60
|
+
* applies it at intake.
|
|
61
|
+
*
|
|
62
|
+
* A not-ok {@link PolicyLoadResult} resolves **every** class to `manual` with
|
|
63
|
+
* provenance `"fail-closed"` — see `policy-load.ts`. An absent
|
|
64
|
+
* `defaults.autonomy` is likewise `manual` (provenance `"default"`): the schema
|
|
65
|
+
* permits omitting `defaults`, and the absence of a grant is not a grant.
|
|
66
|
+
*
|
|
67
|
+
* ## `human-only` (amended SPEC.md §5.2, APRV-185)
|
|
68
|
+
*
|
|
69
|
+
* A fourth level, and the strictest: an action reserved to human hands, taken
|
|
70
|
+
* outside agent execution entirely. It resolves like any other level and
|
|
71
|
+
* nothing here refuses anything — this module is pure — but every enforcement
|
|
72
|
+
* path downstream refuses it with the code `class-human-only`, so a resolution
|
|
73
|
+
* carrying it authorizes no request, no decision, no token and no run.
|
|
74
|
+
*
|
|
75
|
+
* The fail-closed target stays `manual` and deliberately does not follow the
|
|
76
|
+
* new head of the strictness table. A policy that cannot be parsed must remain
|
|
77
|
+
* recoverable through its own gate, and a broken file whose every class became
|
|
78
|
+
* `human-only` would put the repair behind a level that admits no gated repair.
|
|
79
|
+
* Failing closed raises the scrutiny an action gets; it does not remove the
|
|
80
|
+
* path by which a human fixes the file.
|
|
81
|
+
*/
|
|
82
|
+
import type { Autonomy, DeclaredAutonomy, PolicyClassRule, PolicyLoadResult, SupervisionMode } from "./policy-load.js";
|
|
83
|
+
/** Where a {@link Resolution}'s autonomy came from. */
|
|
84
|
+
export type Provenance =
|
|
85
|
+
/** A `classes` rule matched. */
|
|
86
|
+
"rule"
|
|
87
|
+
/** No rule matched; `defaults.autonomy` (or its absent-means-manual form). */
|
|
88
|
+
| "default"
|
|
89
|
+
/**
|
|
90
|
+
* Amended SPEC.md §5.2 (APRV-266): no rule matched a `policy.edit` sub-class,
|
|
91
|
+
* so the `policy.edit` line itself decided it. Distinct from `"rule"` because
|
|
92
|
+
* the pattern that decided does not MATCH this class — a reader of the trace
|
|
93
|
+
* has to be able to see that the class inherited rather than matched — and
|
|
94
|
+
* distinct from `"default"` because `defaults.autonomy` did not decide it.
|
|
95
|
+
*/
|
|
96
|
+
| "inherited"
|
|
97
|
+
/** The policy failed to load; everything is `manual`. */
|
|
98
|
+
| "fail-closed"
|
|
99
|
+
/** The §7 irreversibility floor overrode the resolved autonomy. */
|
|
100
|
+
| "floor";
|
|
101
|
+
/**
|
|
102
|
+
* Specificity key: `[literalSegments, wildcardSegments, totalSegments]`.
|
|
103
|
+
* Ordered on the first two elements only, as literals DESC then wildcards ASC.
|
|
104
|
+
* `totalSegments` is the sum of the other two, so it can never break a tie they
|
|
105
|
+
* did not already break; it is carried for the explain trace, which reports it.
|
|
106
|
+
*/
|
|
107
|
+
export type Specificity = [number, number, number];
|
|
108
|
+
/** A rule whose pattern matched the action class, with its specificity key. */
|
|
109
|
+
export interface Candidate {
|
|
110
|
+
pattern: string;
|
|
111
|
+
rule: PolicyClassRule;
|
|
112
|
+
specificity: Specificity;
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* Outcome of {@link resolve}.
|
|
116
|
+
*
|
|
117
|
+
* `candidates` is every matching rule in descending specificity order, so a
|
|
118
|
+
* decision trace (APRV-12 `explain`) can be rendered without re-deriving the
|
|
119
|
+
* match.
|
|
120
|
+
*/
|
|
121
|
+
export interface Resolution {
|
|
122
|
+
autonomy: Autonomy;
|
|
123
|
+
/**
|
|
124
|
+
* Amended SPEC.md §5.2 (APRV-127): what the winning rule (or the default)
|
|
125
|
+
* actually WROTE, before the split was collapsed onto {@link Autonomy}. A
|
|
126
|
+
* reader that wants to echo the policy's own word — `explain`, the amendment
|
|
127
|
+
* differ, a channel's provenance line — uses this; a reader that wants to
|
|
128
|
+
* know what is enforced uses `autonomy`.
|
|
129
|
+
*/
|
|
130
|
+
declaredAutonomy: DeclaredAutonomy;
|
|
131
|
+
/**
|
|
132
|
+
* `"live"` or `"retro"` when `autonomy` is `supervised`, `null` otherwise.
|
|
133
|
+
* Bare `supervised` is `"retro"` — see `policy-load.ts`'s alias note.
|
|
134
|
+
*/
|
|
135
|
+
supervision: SupervisionMode | null;
|
|
136
|
+
/**
|
|
137
|
+
* The declared `live_rate` for a `supervised-live` class, else `null`.
|
|
138
|
+
*
|
|
139
|
+
* Never `null` for a live class that reached here through the schema, which
|
|
140
|
+
* requires the key. A live class that somehow carries no usable rate resolves
|
|
141
|
+
* to `1` rather than to `null`: see {@link supervisionOf} for why the missing
|
|
142
|
+
* rate is read as "gate all of them".
|
|
143
|
+
*/
|
|
144
|
+
liveRate: number | null;
|
|
145
|
+
/**
|
|
146
|
+
* The declared `retro_rate` for a supervised class, else `null` (amended
|
|
147
|
+
* SPEC.md §5.2, APRV-183).
|
|
148
|
+
*
|
|
149
|
+
* `null` is not "do not sample": it is "this class declared no rate of its
|
|
150
|
+
* own", and the retrospective sampler reads it as the instruction to fall back
|
|
151
|
+
* to `audit.supervised_sample_rate`. A rate the schema would have rejected
|
|
152
|
+
* cannot arrive here, and one that somehow does is read as absent, which puts
|
|
153
|
+
* the class back on the global rate rather than on a number nobody wrote.
|
|
154
|
+
*/
|
|
155
|
+
retroRate: number | null;
|
|
156
|
+
provenance: Provenance;
|
|
157
|
+
matched: {
|
|
158
|
+
pattern: string;
|
|
159
|
+
rule: PolicyClassRule;
|
|
160
|
+
} | null;
|
|
161
|
+
approvers: string[] | null;
|
|
162
|
+
limits: Record<string, number> | null;
|
|
163
|
+
floorApplied: boolean;
|
|
164
|
+
/** Whether every equally most-specific rule explicitly permits irreversibility. */
|
|
165
|
+
allowIrreversible: boolean;
|
|
166
|
+
/** The maximum-specificity rule group governing that permission. */
|
|
167
|
+
irreversiblePatterns: string[];
|
|
168
|
+
candidates: Candidate[];
|
|
169
|
+
}
|
|
170
|
+
/** Options for {@link resolve}. */
|
|
171
|
+
export interface ResolveOptions {
|
|
172
|
+
/**
|
|
173
|
+
* Whether the action can be undone. `false` engages the SPEC.md §7
|
|
174
|
+
* irreversibility floor; `true` and `undefined` do not.
|
|
175
|
+
*/
|
|
176
|
+
reversible?: boolean;
|
|
177
|
+
}
|
|
178
|
+
/**
|
|
179
|
+
* Strictness order, strictest first (SPEC.md §5.2 "deny beats allow"), over the
|
|
180
|
+
* DECLARED vocabulary.
|
|
181
|
+
*
|
|
182
|
+
* `supervised-live` sits between `manual` and the retrospective modes because it
|
|
183
|
+
* is the only supervised mode that can stop an action before it happens: at rate
|
|
184
|
+
* 1 it is `manual`, at any lower rate it is strictly more scrutiny than review
|
|
185
|
+
* after the fact. `supervised` and `supervised-retro` share a rank because they
|
|
186
|
+
* are the same level under two spellings; the lexicographic tie-break in
|
|
187
|
+
* {@link compareCandidates} then decides between two equally specific rules that
|
|
188
|
+
* spell it differently, deterministically and without preferring either word.
|
|
189
|
+
*
|
|
190
|
+
* Exported because `core/policy-explain.ts` needs the same order to name the
|
|
191
|
+
* tie-break in its trace, and a private mirror of this table there is exactly
|
|
192
|
+
* the drift a decision trace must never have from the decision.
|
|
193
|
+
*
|
|
194
|
+
* The RATE is not part of the order. A tie between `supervised-live 0.5` and
|
|
195
|
+
* `supervised-live 0.01` is a tie between two equally specific rules that
|
|
196
|
+
* disagree about a fraction, and ordering by rate would let a policy author move
|
|
197
|
+
* a rule's precedence by editing a number they were only tuning. The
|
|
198
|
+
* lexicographic tie-break settles it, exactly as it settles every other tie.
|
|
199
|
+
*
|
|
200
|
+
* APRV-185 puts `human-only` above `manual` at the head of the table, and this
|
|
201
|
+
* table is the one place the ordering exists: the tie-break below,
|
|
202
|
+
* `core/policy-explain.ts`'s trace, and `core/agents-md.ts`'s draft merge all
|
|
203
|
+
* read it and hold no copy. It sits above `manual` because it is strictly more
|
|
204
|
+
* scrutiny — `manual` says a human decides and an agent then acts, `human-only`
|
|
205
|
+
* says the human acts — so a tie between the two must resolve to the level that
|
|
206
|
+
* lets no agent execute.
|
|
207
|
+
*/
|
|
208
|
+
export declare const STRICTNESS: Readonly<Record<DeclaredAutonomy, number>>;
|
|
209
|
+
/**
|
|
210
|
+
* Collapse a declared level onto the enforced {@link Autonomy} plus its
|
|
211
|
+
* supervision mode and live rate.
|
|
212
|
+
*
|
|
213
|
+
* Pure and total. A `supervised-live` rule whose `live_rate` is absent, or is
|
|
214
|
+
* not a usable proportion, resolves to **1** — every action in the class is
|
|
215
|
+
* gated. `policy.schema.json` requires the key, so this branch is unreachable
|
|
216
|
+
* for a policy that loaded; it is here as the fail-closed backstop, and the
|
|
217
|
+
* direction is the one the rest of this runtime takes everywhere else. The
|
|
218
|
+
* alternative reading, "a rate we could not understand means gate none of them",
|
|
219
|
+
* would turn a typo into a silently disabled control, which is the failure this
|
|
220
|
+
* project exists to prevent.
|
|
221
|
+
*
|
|
222
|
+
* `human-only` (APRV-185) collapses onto itself carrying nothing, exactly as
|
|
223
|
+
* `manual` and `autonomous` do. It names no supervision because it describes no
|
|
224
|
+
* agent execution to supervise, and the schema forbids both rates on it, so
|
|
225
|
+
* there is no fraction here for a reader to misread as live.
|
|
226
|
+
*/
|
|
227
|
+
export declare function supervisionOf(declared: DeclaredAutonomy, rule: PolicyClassRule | null): {
|
|
228
|
+
autonomy: Autonomy;
|
|
229
|
+
supervision: SupervisionMode | null;
|
|
230
|
+
liveRate: number | null;
|
|
231
|
+
retroRate: number | null;
|
|
232
|
+
};
|
|
233
|
+
/**
|
|
234
|
+
* The refusal every enforcement path prints for a `human-only` class (APRV-185).
|
|
235
|
+
*
|
|
236
|
+
* One text, in one place, for the same reason `STRICTNESS` is one table: the
|
|
237
|
+
* code `class-human-only` is frozen in four separate unions (`core/gate.ts`,
|
|
238
|
+
* `core/token.ts`, `core/execute.ts`, and the hook's own), and four hand-written
|
|
239
|
+
* explanations of one condition would disagree about what a caller should do the
|
|
240
|
+
* first time one of them was edited.
|
|
241
|
+
*
|
|
242
|
+
* `whatWasRefused` is the verb's own half of the sentence, so the message names
|
|
243
|
+
* the thing that did not happen as well as the reason it cannot.
|
|
244
|
+
*/
|
|
245
|
+
export declare function humanOnlyRefusal(actionClass: string, whatWasRefused: string): string;
|
|
246
|
+
/**
|
|
247
|
+
* Does `pattern` match `actionClass`?
|
|
248
|
+
*
|
|
249
|
+
* See the module header for the grammar. Both are split on `.`; the pattern's
|
|
250
|
+
* final segment is the only one that may span more than one class segment, and
|
|
251
|
+
* only when the pattern has more than one segment (a bare `*` is single-span).
|
|
252
|
+
*/
|
|
253
|
+
export declare function matchesPattern(pattern: string, actionClass: string): boolean;
|
|
254
|
+
/**
|
|
255
|
+
* Specificity key of a pattern (SPEC.md §5.2). A trailing `.*` is one wildcard
|
|
256
|
+
* segment contributing no literals — which is exactly how it is already counted
|
|
257
|
+
* by segment splitting, so no special case is needed here.
|
|
258
|
+
*/
|
|
259
|
+
export declare function specificityOf(pattern: string): Specificity;
|
|
260
|
+
/**
|
|
261
|
+
* Resolve the autonomy governing `actionClass` under `load`.
|
|
262
|
+
*
|
|
263
|
+
* 1. A not-ok load resolves `manual` / `"fail-closed"` for every class.
|
|
264
|
+
* 2. Otherwise collect matching `classes` rules, order them by specificity, and
|
|
265
|
+
* take the most specific; among a full specificity tie the strictest
|
|
266
|
+
* autonomy wins, and among equally strict tied rules the lexicographically
|
|
267
|
+
* smallest pattern is chosen so the outcome is deterministic.
|
|
268
|
+
* 3. With no matching rule, a class in the `policy.edit.*` namespace (APRV-266)
|
|
269
|
+
* inherits the `policy.edit` line when that line is a rule, with provenance
|
|
270
|
+
* `"inherited"`; otherwise the result is `defaults.autonomy` — or `manual`
|
|
271
|
+
* when `defaults` or `defaults.autonomy` is absent — with provenance
|
|
272
|
+
* `"default"`.
|
|
273
|
+
* 4. Finally, `options.reversible === false` engages the §7 floor: a resolved
|
|
274
|
+
* `autonomous` or `supervised` becomes `manual` with `floorApplied: true`
|
|
275
|
+
* and provenance `"floor"`. An already-`manual` outcome is untouched, and
|
|
276
|
+
* keeps its original provenance, because the floor did not decide it.
|
|
277
|
+
*
|
|
278
|
+
* `approvers` and `limits` are carried from the matched rule only; they are
|
|
279
|
+
* `null` when the rule omits them or when no rule matched.
|
|
280
|
+
*/
|
|
281
|
+
export declare function resolve(load: PolicyLoadResult, actionClass: string, options?: ResolveOptions): Resolution;
|
|
@@ -264,6 +264,8 @@ const FAIL_CLOSED = {
|
|
|
264
264
|
approvers: null,
|
|
265
265
|
limits: null,
|
|
266
266
|
floorApplied: false,
|
|
267
|
+
allowIrreversible: false,
|
|
268
|
+
irreversiblePatterns: [],
|
|
267
269
|
candidates: [],
|
|
268
270
|
};
|
|
269
271
|
/**
|
|
@@ -365,6 +367,8 @@ function fromDefaults(load, candidates) {
|
|
|
365
367
|
approvers: null,
|
|
366
368
|
limits: null,
|
|
367
369
|
floorApplied: false,
|
|
370
|
+
allowIrreversible: false,
|
|
371
|
+
irreversiblePatterns: [],
|
|
368
372
|
candidates,
|
|
369
373
|
};
|
|
370
374
|
}
|
|
@@ -379,9 +383,11 @@ function fromRules(candidates) {
|
|
|
379
383
|
// lexicographically smallest strictest — deterministic regardless of the
|
|
380
384
|
// policy file's key order.
|
|
381
385
|
let winner = best;
|
|
386
|
+
const governing = [];
|
|
382
387
|
for (const candidate of candidates) {
|
|
383
388
|
if (compareSpecificity(candidate.specificity, best.specificity) !== 0)
|
|
384
389
|
break;
|
|
390
|
+
governing.push(candidate);
|
|
385
391
|
if (STRICTNESS[candidate.rule.autonomy] < STRICTNESS[winner.rule.autonomy]) {
|
|
386
392
|
winner = candidate;
|
|
387
393
|
}
|
|
@@ -394,23 +400,26 @@ function fromRules(candidates) {
|
|
|
394
400
|
approvers: winner.rule.approvers ?? null,
|
|
395
401
|
limits: winner.rule.limits ?? null,
|
|
396
402
|
floorApplied: false,
|
|
403
|
+
// APRV-317: lexicographic order and strictness still select the ordinary
|
|
404
|
+
// winner, but neither may silently discard a tied rule's refusal to waive
|
|
405
|
+
// the floor. The capability is the intersection of the governing group.
|
|
406
|
+
allowIrreversible: governing.every((candidate) => candidate.rule.allow_irreversible === true),
|
|
407
|
+
irreversiblePatterns: governing.map((candidate) => candidate.pattern),
|
|
397
408
|
candidates,
|
|
398
409
|
};
|
|
399
410
|
}
|
|
400
411
|
/**
|
|
401
412
|
* SPEC.md §7 irreversibility floor, applied *after* class resolution: an action
|
|
402
|
-
* declared `reversible: false`
|
|
403
|
-
*
|
|
404
|
-
* the floor
|
|
405
|
-
* rule, determined the outcome.
|
|
413
|
+
* declared `reversible: false` resolves to `manual` unless the attested class
|
|
414
|
+
* rule group explicitly permits it. When no permission exists, the resolution
|
|
415
|
+
* records that the floor, rather than the matched rule, determined the outcome.
|
|
406
416
|
*
|
|
407
417
|
* ## The floor is a floor, not a proof (amended SPEC.md §7, APRV-127)
|
|
408
418
|
*
|
|
409
|
-
* This
|
|
410
|
-
*
|
|
411
|
-
*
|
|
412
|
-
*
|
|
413
|
-
* trail, and the grammar must not offer it.
|
|
419
|
+
* This remains the enforcement point for APRV-127's default: a supervised rule
|
|
420
|
+
* with no APRV-317 opt-in sends a truthful irreversible action to manual. The
|
|
421
|
+
* only exception is the unanimous capability computed from operator policy
|
|
422
|
+
* above; action metadata has no field that can supply it.
|
|
414
423
|
*
|
|
415
424
|
* What the floor is NOT is evidence that anything else is reversible.
|
|
416
425
|
* `reversible` is SELF-REPORTED by the action's own declaration. A truthful
|
|
@@ -445,6 +454,8 @@ function applyFloor(resolution, options) {
|
|
|
445
454
|
return resolution;
|
|
446
455
|
if (resolution.autonomy === "manual" || resolution.autonomy === "human-only")
|
|
447
456
|
return resolution;
|
|
457
|
+
if (resolution.allowIrreversible)
|
|
458
|
+
return resolution;
|
|
448
459
|
return {
|
|
449
460
|
...resolution,
|
|
450
461
|
autonomy: "manual",
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"policy-match.js","sourceRoot":"","sources":["../../../src/core/policy-match.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgFG;
|
|
1
|
+
{"version":3,"file":"policy-match.js","sourceRoot":"","sources":["../../../src/core/policy-match.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,265 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The attestation ceremony, collected through a channel (APRV-109, amended
|
|
3
|
+
* SPEC.md §10.1/§10.3/§11).
|
|
4
|
+
*
|
|
5
|
+
* ## The problem
|
|
6
|
+
*
|
|
7
|
+
* Attestation is human-only (`core/attest.ts`), and identity is
|
|
8
|
+
* config-declared, so the two policy ceremonies — `policy attest` and `policy
|
|
9
|
+
* amend` — required the human to be at a terminal. Every other decision in this
|
|
10
|
+
* system had already been reduced to a tap on a phone; the one act that decides
|
|
11
|
+
* which rules are in force had not. This module is the missing half: an agent
|
|
12
|
+
* prepares the policy edit and appends a *proposal*, a channel puts it in front
|
|
13
|
+
* of the approver like any other manual prompt, and the tap appends the
|
|
14
|
+
* `policy.updated` attestation under the human identity the listener holds,
|
|
15
|
+
* exactly as a grant lands today.
|
|
16
|
+
*
|
|
17
|
+
* ## What is computed, and why it has to be
|
|
18
|
+
*
|
|
19
|
+
* A proposal carries three things a channel renders: the SHA-256 of the policy
|
|
20
|
+
* file's exact bytes, the semantic diff of what those bytes change about class
|
|
21
|
+
* resolution, and the load advisory. **All three are derived here from the
|
|
22
|
+
* bytes** — none is accepted from the proposing agent. {@link ProposeInput}
|
|
23
|
+
* has no field for a hash, a diff or a verdict, so the refusal of a
|
|
24
|
+
* caller-authored value is structural in the same way the refusal of a
|
|
25
|
+
* caller-supplied `ts` is (amended SPEC.md §8, A2). An agent that could author
|
|
26
|
+
* the diff summary could show an approver one story and attest another file.
|
|
27
|
+
*
|
|
28
|
+
* The BASELINE the diff is taken against is the one place a caller supplies
|
|
29
|
+
* material, and it is checked rather than trusted: bytes whose own SHA-256 is
|
|
30
|
+
* not the latest attested hash are refused as a baseline and the proposal falls
|
|
31
|
+
* to hash-only mode. That is `cli/amend.ts`'s rule ("a baseline nobody can
|
|
32
|
+
* verify is not a baseline"), enforced here so every caller inherits it.
|
|
33
|
+
*
|
|
34
|
+
* ## Fail closed, in this order
|
|
35
|
+
*
|
|
36
|
+
* - A policy file that cannot be read proposes nothing.
|
|
37
|
+
* - A live file that already matches its attestation proposes nothing: there is
|
|
38
|
+
* no amendment to sign, and a prompt for one would ask a human to re-attest
|
|
39
|
+
* bytes already in force.
|
|
40
|
+
* - A rendered diff larger than {@link ATTESTATION_DIFF_MAX_CHARS} REFUSES
|
|
41
|
+
* (`diff-too-large`) rather than truncating. A phone that shows two thirds of
|
|
42
|
+
* a policy change collects a signature for the third it did not show, and the
|
|
43
|
+
* repair — read it at a terminal and run `approval policy amend` there — is a
|
|
44
|
+
* real repair rather than a smaller lie.
|
|
45
|
+
* - A tap whose proposal's bytes are no longer the bytes on disk refuses
|
|
46
|
+
* `proposal-stale` and attests nothing. The hash the human was shown is the
|
|
47
|
+
* hash that gets attested, or nothing does.
|
|
48
|
+
* - A decline, a supersession and a lapsed deadline all attest nothing. Only
|
|
49
|
+
* {@link decideAttestation}`(…, "attest", …)` ever appends a `policy.updated`.
|
|
50
|
+
*
|
|
51
|
+
* Everything that writes here goes through `core/log.ts`'s `appendEvent` with a
|
|
52
|
+
* compare-and-append precondition and a runtime-assigned timestamp, so the new
|
|
53
|
+
* records inherit the same write-boundary discipline as the rest of the gate.
|
|
54
|
+
*/
|
|
55
|
+
import { type ClockOptions } from "./clock.js";
|
|
56
|
+
import { type AppendOptions, type EventRecord } from "./log.js";
|
|
57
|
+
import type { GateRefusal } from "./gate.js";
|
|
58
|
+
/**
|
|
59
|
+
* The event an agent appends to ask for an attestation.
|
|
60
|
+
*
|
|
61
|
+
* Named `proposed` rather than `requested` deliberately: `approval.requested`
|
|
62
|
+
* is the approval lifecycle, with its own TTL, its own budgets and its own
|
|
63
|
+
* grant. This is a different question with a different answer event, and giving
|
|
64
|
+
* it the same word would invite a reader — or a projection — to treat a policy
|
|
65
|
+
* attestation as an ordinary authorization.
|
|
66
|
+
*/
|
|
67
|
+
export declare const PROPOSAL_EVENT = "policy.proposed";
|
|
68
|
+
/** The event a decline appends. An attestation appends `policy.updated`. */
|
|
69
|
+
export declare const DECLINE_EVENT = "policy.declined";
|
|
70
|
+
/**
|
|
71
|
+
* The idempotency key an attestation prompt is rendered under.
|
|
72
|
+
*
|
|
73
|
+
* `policy.attest:<sha256>` — derived from the proposed bytes and from nothing
|
|
74
|
+
* else, so two proposals of the same policy text carry the same key and a tap
|
|
75
|
+
* on either answers the same question. It is also what
|
|
76
|
+
* `channels/contract.ts` routes on: a gesture whose key starts with this prefix
|
|
77
|
+
* becomes an attestation, never a grant.
|
|
78
|
+
*/
|
|
79
|
+
export declare const ATTESTATION_KEY_PREFIX = "policy.attest:";
|
|
80
|
+
/** The action class an attestation prompt is resolved and rendered under. */
|
|
81
|
+
export declare const ATTESTATION_CLASS = "policy.edit";
|
|
82
|
+
/** `policy.attest:<sha256>` for the given policy bytes. */
|
|
83
|
+
export declare function attestationActionKey(sha256: string): string;
|
|
84
|
+
/** Is this an attestation prompt's key rather than an ordinary action's? */
|
|
85
|
+
export declare function isAttestationActionKey(actionKey: string): boolean;
|
|
86
|
+
/** The proposed policy hash named by an attestation key, or `null`. */
|
|
87
|
+
export declare function attestationKeySha256(actionKey: string): string | null;
|
|
88
|
+
/**
|
|
89
|
+
* The largest rendered diff a channel prompt may carry, in characters.
|
|
90
|
+
*
|
|
91
|
+
* Telegram's hard limit is 4096 characters for one message and the prompt
|
|
92
|
+
* carries a dozen other lines besides the diff, so this is the budget the
|
|
93
|
+
* *smallest* supported channel can show whole. It is a refusal threshold and
|
|
94
|
+
* never a truncation point: see the module header.
|
|
95
|
+
*/
|
|
96
|
+
export declare const ATTESTATION_DIFF_MAX_CHARS = 2400;
|
|
97
|
+
/** The same bound, in lines: a diff nobody will scroll is a diff nobody reads. */
|
|
98
|
+
export declare const ATTESTATION_DIFF_MAX_LINES = 60;
|
|
99
|
+
/** The load advisory a channel renders beside the diff. */
|
|
100
|
+
export interface LoadAdvisory {
|
|
101
|
+
ok: boolean;
|
|
102
|
+
/** The load failure's machine-readable code, or `null` on a clean load. */
|
|
103
|
+
code: string | null;
|
|
104
|
+
message: string | null;
|
|
105
|
+
}
|
|
106
|
+
/** The semantic diff summary a channel renders, as the log records it. */
|
|
107
|
+
export interface DiffSummary {
|
|
108
|
+
/** False in hash-only mode: no verifiable baseline, so no semantic diff. */
|
|
109
|
+
available: boolean;
|
|
110
|
+
/** Why the diff is unavailable; `null` when it is available. */
|
|
111
|
+
reason: string | null;
|
|
112
|
+
/** The rendered diff, line by line, exactly as a channel prints it. */
|
|
113
|
+
lines: string[];
|
|
114
|
+
/** The one-line headline (`3 class resolution(s), 1 default(s)`). */
|
|
115
|
+
headline: string;
|
|
116
|
+
/** The SHA-256 of the baseline the diff was taken against, when there was one. */
|
|
117
|
+
baseline_sha256: string | null;
|
|
118
|
+
}
|
|
119
|
+
/** What {@link proposeAttestation} was asked to propose. */
|
|
120
|
+
export interface ProposeInput {
|
|
121
|
+
/** The policy file whose bytes are being proposed. */
|
|
122
|
+
policyPath: string;
|
|
123
|
+
/**
|
|
124
|
+
* The previously-attested policy TEXT, for the semantic diff.
|
|
125
|
+
*
|
|
126
|
+
* Checked, never trusted: bytes whose SHA-256 is not the latest attestation's
|
|
127
|
+
* are refused as a baseline and the proposal falls to hash-only mode with the
|
|
128
|
+
* reason recorded. Callers recover them from `HEAD:<path>` (`cli/amend.ts`);
|
|
129
|
+
* a caller with nothing to offer passes nothing.
|
|
130
|
+
*/
|
|
131
|
+
baseline?: Uint8Array | null;
|
|
132
|
+
/**
|
|
133
|
+
* The proposer's own words about the amendment. CLAIMED, and rendered as
|
|
134
|
+
* such: it is the one field on the prompt the runtime does not stand behind.
|
|
135
|
+
*/
|
|
136
|
+
note?: string;
|
|
137
|
+
/**
|
|
138
|
+
* When the proposing process stops waiting (RFC 3339). Display only, and it
|
|
139
|
+
* can only raise urgency: see `ChannelRequest.waiting`.
|
|
140
|
+
*/
|
|
141
|
+
waitUntil?: string;
|
|
142
|
+
}
|
|
143
|
+
/** Options for both verbs: the append's, plus the clock and the schema dir. */
|
|
144
|
+
export interface ProposalOptions extends ClockOptions {
|
|
145
|
+
schemaDir?: string;
|
|
146
|
+
append?: AppendOptions;
|
|
147
|
+
/** Where the full policy text is stored for the channel to display. */
|
|
148
|
+
payloadStoreDir?: string;
|
|
149
|
+
}
|
|
150
|
+
export type ProposeResult = {
|
|
151
|
+
ok: true;
|
|
152
|
+
record: EventRecord;
|
|
153
|
+
sha256: string;
|
|
154
|
+
diff: DiffSummary;
|
|
155
|
+
load: LoadAdvisory;
|
|
156
|
+
} | GateRefusal;
|
|
157
|
+
export type AttestationDecision = "attest" | "decline";
|
|
158
|
+
export type DecideAttestationResult = {
|
|
159
|
+
ok: true;
|
|
160
|
+
decision: AttestationDecision;
|
|
161
|
+
record: EventRecord;
|
|
162
|
+
sha256: string;
|
|
163
|
+
} | GateRefusal;
|
|
164
|
+
/**
|
|
165
|
+
* The policy file a proposal concerns, discovered the way `loadPolicy` does so
|
|
166
|
+
* the proposed file and the enforced file are never two different files.
|
|
167
|
+
*/
|
|
168
|
+
export declare function proposalPolicyPath(dir: string): string;
|
|
169
|
+
/** The load advisory for policy text, as a channel renders it. */
|
|
170
|
+
export declare function adviseLoad(policyPath: string, text: string, schemaDir?: string): LoadAdvisory;
|
|
171
|
+
/** The rendered size of a diff summary, as the channel budget counts it. */
|
|
172
|
+
export declare function diffSize(summary: DiffSummary): {
|
|
173
|
+
chars: number;
|
|
174
|
+
lines: number;
|
|
175
|
+
};
|
|
176
|
+
/**
|
|
177
|
+
* The semantic diff between the attested baseline and the proposed bytes.
|
|
178
|
+
*
|
|
179
|
+
* Hash-only mode (`available: false`) whenever the baseline cannot be *proved*
|
|
180
|
+
* to be the attested text: no baseline was offered, none was ever attested, or
|
|
181
|
+
* the offered bytes hash to something other than the attestation. The reason is
|
|
182
|
+
* recorded so a channel can print it instead of a diff, which is the honest
|
|
183
|
+
* rendering of "we cannot show you what this changes".
|
|
184
|
+
*/
|
|
185
|
+
export declare function summarizeDiff(policyPath: string, baseline: Uint8Array | null | undefined, live: Uint8Array, attestedSha256: string | null, schemaDir?: string): DiffSummary;
|
|
186
|
+
/** The stored value a channel displays as the prompt's full payload. */
|
|
187
|
+
export declare function proposalPayloadValue(policyPath: string, text: string): Record<string, unknown>;
|
|
188
|
+
/**
|
|
189
|
+
* Append a `policy.proposed` asking a human to attest `policyPath`'s bytes.
|
|
190
|
+
*
|
|
191
|
+
* The record's payload is the prompt: `sha256`, `diff` and `load` are all
|
|
192
|
+
* derived here, `note` and `wait_until` are the proposer's and are labelled
|
|
193
|
+
* claimed by every channel. `payload_hash` binds the full policy text stored
|
|
194
|
+
* beside the log, so the approver can read the whole file rather than only the
|
|
195
|
+
* summary (SPEC.md §10.4).
|
|
196
|
+
*
|
|
197
|
+
* No attestation is required to append one. That is the point of the verb: the
|
|
198
|
+
* live policy is mid-amendment and therefore unattested, which is exactly the
|
|
199
|
+
* state in which every other gate operation refuses. This one asks a human to
|
|
200
|
+
* end that state.
|
|
201
|
+
*/
|
|
202
|
+
export declare function proposeAttestation(logPath: string, input: ProposeInput, actor: string, options?: ProposalOptions): ProposeResult;
|
|
203
|
+
/** What became of a proposal, derived from the log and from nothing else. */
|
|
204
|
+
export type ProposalState =
|
|
205
|
+
/** Nobody has answered, nothing supersedes it, and its deadline has not passed. */
|
|
206
|
+
"open"
|
|
207
|
+
/** A human attested these bytes: a `policy.updated` carries the same hash. */
|
|
208
|
+
| "attested"
|
|
209
|
+
/** A human declined: a `policy.declined` names this proposal. */
|
|
210
|
+
| "declined"
|
|
211
|
+
/** A newer proposal for the same policy path replaced it. */
|
|
212
|
+
| "superseded"
|
|
213
|
+
/** Its `wait_until` passed with no answer. Nothing was attested. */
|
|
214
|
+
| "expired";
|
|
215
|
+
export interface ProposalDerivation {
|
|
216
|
+
seq: number;
|
|
217
|
+
actionKey: string;
|
|
218
|
+
sha256: string;
|
|
219
|
+
state: ProposalState;
|
|
220
|
+
record: EventRecord;
|
|
221
|
+
}
|
|
222
|
+
/** Every `policy.proposed` record in the log, in append order. */
|
|
223
|
+
export declare function proposalRecords(records: readonly EventRecord[]): EventRecord[];
|
|
224
|
+
/**
|
|
225
|
+
* Derive one proposal's state at `now`.
|
|
226
|
+
*
|
|
227
|
+
* Terminal states are read off the log; `expired` is arithmetic on the
|
|
228
|
+
* proposer's own `wait_until` and materialises no event, because a lapsed
|
|
229
|
+
* attestation prompt has nothing to record: nothing was attested, and the
|
|
230
|
+
* proposal record already says everything a reader needs. That is the
|
|
231
|
+
* fail-closed reading — a prompt nobody answered leaves the policy exactly as
|
|
232
|
+
* unattested as it was.
|
|
233
|
+
*/
|
|
234
|
+
export declare function proposalState(records: readonly EventRecord[], seq: number, now: string): ProposalDerivation | null;
|
|
235
|
+
/** Every proposal still awaiting a human answer at `now`, in log order. */
|
|
236
|
+
export declare function openProposals(records: readonly EventRecord[], now: string): ProposalDerivation[];
|
|
237
|
+
/** The open proposal whose bytes hash to `sha256`, or `null`. */
|
|
238
|
+
export declare function openProposalFor(records: readonly EventRecord[], sha256: string, now: string): ProposalDerivation | null;
|
|
239
|
+
/** Options for {@link decideAttestation}. */
|
|
240
|
+
export interface DecideAttestationOptions extends ProposalOptions {
|
|
241
|
+
/** The approver's free-text note, recorded on the answer. */
|
|
242
|
+
note?: string;
|
|
243
|
+
/** Where `APPROVAL.md` lives, when it is not the proposal's own directory. */
|
|
244
|
+
policyPath?: string;
|
|
245
|
+
/** The channel delivery id this gesture answered, for audit. */
|
|
246
|
+
batchDeliveryId?: string;
|
|
247
|
+
}
|
|
248
|
+
/**
|
|
249
|
+
* Answer a proposal: attest the bytes the prompt displayed, or decline them.
|
|
250
|
+
*
|
|
251
|
+
* Human-only, in code — the same rule `core/attest.ts` enforces, restated here
|
|
252
|
+
* because this is a second door onto the same act and the rule must hold for
|
|
253
|
+
* every caller and not only for the CLI's.
|
|
254
|
+
*
|
|
255
|
+
* **The attested hash is the hash the prompt displayed.** Before anything is
|
|
256
|
+
* appended the live file is re-read and re-hashed, and any difference refuses
|
|
257
|
+
* `proposal-stale`: the human signed for bytes they were shown, and bytes that
|
|
258
|
+
* changed underneath the prompt are a different policy that has to be proposed
|
|
259
|
+
* again. This is the one check that makes "the phone shows the diff and the
|
|
260
|
+
* hash" mean anything.
|
|
261
|
+
*
|
|
262
|
+
* A decline appends `policy.declined` and attests nothing. So does a lapsed
|
|
263
|
+
* deadline, by appending nothing at all.
|
|
264
|
+
*/
|
|
265
|
+
export declare function decideAttestation(logPath: string, seq: number, decision: AttestationDecision, actor: string, options?: DecideAttestationOptions): DecideAttestationResult;
|