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,623 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The channel plugin contract (SPEC.md §9, §10.3, §10.4, §11).
|
|
3
|
+
*
|
|
4
|
+
* A channel is **transport**. It takes a render-ready request from the runtime,
|
|
5
|
+
* puts it in front of a human, and hands back the gesture the human made. It
|
|
6
|
+
* holds no state (§10.3: "Decisions become log events; channels hold no state"),
|
|
7
|
+
* it never reads the log, it never writes the log, and it never decides
|
|
8
|
+
* anything. Everything a channel could get wrong about *authority* is therefore
|
|
9
|
+
* removed from a channel by construction:
|
|
10
|
+
*
|
|
11
|
+
* - **It cannot invent a field.** Every displayable datum on {@link
|
|
12
|
+
* ChannelRequest} is a {@link TaggedField}: either `computed` (the runtime
|
|
13
|
+
* derived it, and `source` names the derivation) or `claimed` (the party under
|
|
14
|
+
* oversight authored it, and `author` names them). There is no untagged
|
|
15
|
+
* member, so "render this string" is not a thing a channel can do without
|
|
16
|
+
* first knowing which side of the verification boundary the string came from.
|
|
17
|
+
* SPEC.md §9 and §10.3 both say the same sentence twice: rendering claimed
|
|
18
|
+
* fields with the visual authority of computed fields is a conformance
|
|
19
|
+
* failure. This type makes the mistake require deliberate effort.
|
|
20
|
+
* - **It cannot approve anything.** {@link ChannelDecision} is a *report* of a
|
|
21
|
+
* human gesture. Turning it into an `approval.granted` is
|
|
22
|
+
* {@link recordChannelDecision}'s job, and that function does exactly one
|
|
23
|
+
* thing: call `core/gate.ts`'s human-only `decide()`. Every gate rule — human
|
|
24
|
+
* actor, attestation on grant, TTL lapse, budget re-check, compare-and-append,
|
|
25
|
+
* idempotency — applies unchanged, because there is no second path.
|
|
26
|
+
* - **It cannot hold a secret.** A grant mints a single-use execution token.
|
|
27
|
+
* {@link recordChannelDecision} returns it to *its caller* (the runtime) in a
|
|
28
|
+
* field that is deliberately not part of {@link DecisionOutcome}, which is the
|
|
29
|
+
* only value handed back to the channel. A channel that logged its own
|
|
30
|
+
* outcomes would log nothing sensitive.
|
|
31
|
+
*
|
|
32
|
+
* ## Manual requests and the full payload (SPEC.md §10.4)
|
|
33
|
+
*
|
|
34
|
+
* > For `manual` actions, channels MUST present the full payload or a faithful
|
|
35
|
+
* > rendering of it, clearly delineated from any agent-written summary, before
|
|
36
|
+
* > collecting a decision.
|
|
37
|
+
*
|
|
38
|
+
* {@link createChannelRequest} refuses to build a `manual` request whose
|
|
39
|
+
* `fullPayload` is null. A channel therefore cannot be handed a manual request
|
|
40
|
+
* that has nothing to show; the failure surfaces at construction, inside the
|
|
41
|
+
* runtime, rather than as a channel silently rendering a summary alone.
|
|
42
|
+
*
|
|
43
|
+
* ## What is deliberately not here
|
|
44
|
+
*
|
|
45
|
+
* No I/O beyond {@link recordChannelDecision}'s delegation to the gate; no
|
|
46
|
+
* clock; no rendering. Building a {@link ChannelRequest} from a log is
|
|
47
|
+
* `channels/tagging.ts`, batching is `channels/batch.ts`, and the shared
|
|
48
|
+
* pass/fail suite every channel implementation must survive is
|
|
49
|
+
* `channels/conformance.ts`.
|
|
50
|
+
*/
|
|
51
|
+
import type { AttestationStatus } from "../core/attest.js";
|
|
52
|
+
import type { BudgetVerdict } from "../core/budgets.js";
|
|
53
|
+
import { type DecideOptions, type GateRefusal } from "../core/gate.js";
|
|
54
|
+
import type { EventRecord } from "../core/log.js";
|
|
55
|
+
import type { Autonomy } from "../core/policy-load.js";
|
|
56
|
+
import type { Provenance } from "../core/policy-match.js";
|
|
57
|
+
import { type RequestState } from "../core/state.js";
|
|
58
|
+
/**
|
|
59
|
+
* The canonical `source` values the runtime tagger stamps on computed fields.
|
|
60
|
+
*
|
|
61
|
+
* `TaggedField`'s `source` is typed `string` rather than this union on purpose:
|
|
62
|
+
* an adapter or a satellite runtime may derive a field from a derivation this
|
|
63
|
+
* repo has not named, and forcing it to lie about the provenance would be worse
|
|
64
|
+
* than admitting a name we do not recognize. These are the names
|
|
65
|
+
* `channels/tagging.ts` uses, and the ones a reviewer should expect to see.
|
|
66
|
+
*/
|
|
67
|
+
export declare const COMPUTED_SOURCES: readonly [
|
|
68
|
+
/** Read from a verified log record (`core/state.ts`). */
|
|
69
|
+
"log",
|
|
70
|
+
/** Resolved against the attested policy (`core/policy-match.ts`, `policy-explain.ts`). */
|
|
71
|
+
"policy-match",
|
|
72
|
+
/** Evaluated by `core/budgets.ts` at display time. */
|
|
73
|
+
"budgets",
|
|
74
|
+
/** Compared against the latest attestation (`core/attest.ts`). */
|
|
75
|
+
"attestation",
|
|
76
|
+
/**
|
|
77
|
+
* Loaded and validated by `core/policy-load.ts` at display time (APRV-109) —
|
|
78
|
+
* the advisory on an attestation prompt, recomputed from the live policy
|
|
79
|
+
* bytes rather than read back off the proposal that recorded one.
|
|
80
|
+
*/
|
|
81
|
+
"policy-load",
|
|
82
|
+
/** Recomputed from the payload bytes and checked against the bound hash. */
|
|
83
|
+
"payload-binding",
|
|
84
|
+
/**
|
|
85
|
+
* Re-derived from the hash-checked payload bytes by `core/command-class.ts`
|
|
86
|
+
* — the same module whose answer selected the class (APRV-143).
|
|
87
|
+
*
|
|
88
|
+
* Distinct from `payload-binding`, which names the hash agreement itself.
|
|
89
|
+
* This names the classifier's reading OF those bytes, which is a second
|
|
90
|
+
* derivation over the same material and can be wrong in ways the hash cannot
|
|
91
|
+
* catch: a channel that labelled it `payload-binding` would be borrowing the
|
|
92
|
+
* binding's authority for an answer the binding does not cover.
|
|
93
|
+
*/
|
|
94
|
+
"classifier",
|
|
95
|
+
/** Arithmetic on the display-time instant (TTL remaining). */
|
|
96
|
+
"clock"];
|
|
97
|
+
export type ComputedSource = (typeof COMPUTED_SOURCES)[number];
|
|
98
|
+
/**
|
|
99
|
+
* One displayable datum, labelled with which side of the verification boundary
|
|
100
|
+
* it came from (SPEC.md §9).
|
|
101
|
+
*
|
|
102
|
+
* `computed`: the runtime derived it from the log, the policy, or the payload
|
|
103
|
+
* bytes. `source` names *which* derivation, so a channel can say "class:
|
|
104
|
+
* financial.spend (policy)" rather than implying the runtime vouches for a
|
|
105
|
+
* string an agent typed.
|
|
106
|
+
*
|
|
107
|
+
* `claimed`: the party under oversight authored it. `author` is their actor id.
|
|
108
|
+
* SPEC.md §11's ratchet applies to every one of these: they may raise scrutiny
|
|
109
|
+
* and never lower it, so a channel may render them prominently, but never as
|
|
110
|
+
* fact.
|
|
111
|
+
*/
|
|
112
|
+
export type TaggedField<T> = {
|
|
113
|
+
kind: "computed";
|
|
114
|
+
value: T;
|
|
115
|
+
source: string;
|
|
116
|
+
} | {
|
|
117
|
+
kind: "claimed";
|
|
118
|
+
value: T;
|
|
119
|
+
author: string;
|
|
120
|
+
};
|
|
121
|
+
/**
|
|
122
|
+
* The words that ride beside a rendered `gloss`, on every channel that renders
|
|
123
|
+
* one (APRV-144 for Telegram, APRV-197 for the terminal).
|
|
124
|
+
*
|
|
125
|
+
* Belt and braces with the `(author)` parenthetical: a reader skimming a wall
|
|
126
|
+
* of lines sees the word "model" inside the sentence they are about to believe,
|
|
127
|
+
* not only in the small print at the end of it. It lives here, next to the
|
|
128
|
+
* field it labels, because two channels rendering the same field under two
|
|
129
|
+
* different labels is how one of them ends up looking authoritative.
|
|
130
|
+
*/
|
|
131
|
+
export declare const GLOSS_UNVERIFIED_SUFFIX = "(model, unverified)";
|
|
132
|
+
/**
|
|
133
|
+
* What a person is told, in one line, when the gate would not take their
|
|
134
|
+
* decision (APRV-235).
|
|
135
|
+
*
|
|
136
|
+
* One function, every surface. A Telegram message edit, a web page and a
|
|
137
|
+
* terminal are three renderings of one fact, and before this they were three
|
|
138
|
+
* sentences — or, on the terminal that watched a `policy-drift` refusal on
|
|
139
|
+
* 2026-09-02, one sentence in a place the person who had tapped could not see.
|
|
140
|
+
* A human who taps on their phone and then reads the operator's terminal should
|
|
141
|
+
* find the same words in both, because the alternative is deciding which one to
|
|
142
|
+
* believe.
|
|
143
|
+
*
|
|
144
|
+
* Written to the approver, not to the log: it says what happened to THEIR
|
|
145
|
+
* answer and what, if anything, they should do next. The machine-readable fact
|
|
146
|
+
* is `code`, which every caller has and none of them should be paraphrasing.
|
|
147
|
+
* The wording of the first three is APRV-206's, moved here unchanged.
|
|
148
|
+
*/
|
|
149
|
+
export declare function refusedDecisionLine(code: string): string;
|
|
150
|
+
/** Tag `value` as runtime-derived, naming the derivation that produced it. */
|
|
151
|
+
export declare function computed<T>(value: T, source: ComputedSource | string): TaggedField<T>;
|
|
152
|
+
/** Tag `value` as authored by `author`, the party under oversight. */
|
|
153
|
+
export declare function claimed<T>(value: T, author: string): TaggedField<T>;
|
|
154
|
+
/** Is `value` a well-formed {@link TaggedField}? Used by the conformance suite. */
|
|
155
|
+
export declare function isTaggedField(value: unknown): value is TaggedField<unknown>;
|
|
156
|
+
/**
|
|
157
|
+
* The payload bytes a manual approval binds to, plus a faithful rendering of
|
|
158
|
+
* them (SPEC.md §10.4).
|
|
159
|
+
*
|
|
160
|
+
* `hash` is recomputed here from `value` by `core/payload.ts` and checked
|
|
161
|
+
* against the `payload_hash` the log recorded, which is what makes this a
|
|
162
|
+
* *computed* field rather than one more agent claim: material that does not
|
|
163
|
+
* hash to the bound value never reaches a channel at all.
|
|
164
|
+
*
|
|
165
|
+
* `truncated` is the honest admission that `text` is shorter than `value`. A
|
|
166
|
+
* truncated rendering is still legal for a single request (a channel may offer
|
|
167
|
+
* "show more"), but it is what {@link ../channels/batch.js assembleBatch}
|
|
168
|
+
* refuses to fold into a batch — see the B7 operationalization there.
|
|
169
|
+
*/
|
|
170
|
+
export interface PayloadRendering {
|
|
171
|
+
/** The concrete payload value, exactly as it will be executed. */
|
|
172
|
+
value: unknown;
|
|
173
|
+
/** A faithful text rendering of `value`, for channels that display text. */
|
|
174
|
+
text: string;
|
|
175
|
+
/** SHA-256/JCS of `value`, recomputed; equal to the log's `payload_hash`. */
|
|
176
|
+
hash: string;
|
|
177
|
+
/** `text` omits part of `value`. */
|
|
178
|
+
truncated: boolean;
|
|
179
|
+
}
|
|
180
|
+
/** Where the request sits in the hash chain (SPEC.md §8, §9 "chain position"). */
|
|
181
|
+
export interface ChainPosition {
|
|
182
|
+
/** `seq` of the `approval.requested` record. */
|
|
183
|
+
seq: number;
|
|
184
|
+
/** That record's hash. */
|
|
185
|
+
hash: string;
|
|
186
|
+
/** `seq` of the log head at the moment this request was built. */
|
|
187
|
+
head_seq: number;
|
|
188
|
+
}
|
|
189
|
+
/**
|
|
190
|
+
* A pending manual request, ready to render — and nothing else.
|
|
191
|
+
*
|
|
192
|
+
* Every member is a {@link TaggedField}. That is the point of the type: a
|
|
193
|
+
* channel iterating this object cannot reach a bare value, so the question
|
|
194
|
+
* "computed or claimed?" is answered before the question "how do I display it?"
|
|
195
|
+
* can be asked. Adding an untagged member to this interface would be the defect
|
|
196
|
+
* this whole module exists to prevent.
|
|
197
|
+
*/
|
|
198
|
+
export interface ChannelRequest {
|
|
199
|
+
/** The action's idempotency key (`core/gate.ts`). */
|
|
200
|
+
action_key: TaggedField<string>;
|
|
201
|
+
/** The Backlog.md task id, or `null` when the log records none. */
|
|
202
|
+
task: TaggedField<string | null>;
|
|
203
|
+
/** The declared side-effect class, as the log records it (SPEC.md §7). */
|
|
204
|
+
class: TaggedField<string>;
|
|
205
|
+
/** Autonomy resolved against the attested policy at display time. */
|
|
206
|
+
autonomy: TaggedField<Autonomy>;
|
|
207
|
+
/** How that resolution was reached (`rule`, `default`, `fail-closed`, …). */
|
|
208
|
+
provenance: TaggedField<Provenance>;
|
|
209
|
+
/** The agent's cost estimate. Claimed: it is a promise, not a measurement. */
|
|
210
|
+
est_cost_usd: TaggedField<number>;
|
|
211
|
+
/** The agent's one-line description of the effect. Claimed. */
|
|
212
|
+
summary: TaggedField<string | null>;
|
|
213
|
+
/** `route.rationale`, when the log carries one. Claimed. */
|
|
214
|
+
rationale?: TaggedField<string>;
|
|
215
|
+
/** `route.confidence`, when the log carries one. Claimed, and never a gate. */
|
|
216
|
+
confidence?: TaggedField<number>;
|
|
217
|
+
/**
|
|
218
|
+
* What a compound shell command does, segment by segment (APRV-144):
|
|
219
|
+
* `git add … · git commit · git push origin main:records-…`.
|
|
220
|
+
*
|
|
221
|
+
* **Computed**, and from the payload bytes alone: it is derived by
|
|
222
|
+
* `core/command-class.ts`'s own tokenizer, the one whose reading chose the
|
|
223
|
+
* class, so a channel showing it cannot describe a command differently from
|
|
224
|
+
* the module that gated it. Present only for a command-shaped payload the
|
|
225
|
+
* tokenizer can read; absent, never guessed, for every other shape.
|
|
226
|
+
*/
|
|
227
|
+
command_breakdown?: TaggedField<string>;
|
|
228
|
+
/**
|
|
229
|
+
* The protected path that selected `policy.edit`, and the rule that matched
|
|
230
|
+
* it (APRV-143): `.github/workflows/ci.yml (rule protected-path)`.
|
|
231
|
+
*
|
|
232
|
+
* **Computed.** For a shell payload the classifier is re-run over the bound
|
|
233
|
+
* command; for a file-tool payload `isProtectedPath` is re-run over the bound
|
|
234
|
+
* target. Either way the answer is recomputed from the bytes the approval
|
|
235
|
+
* binds to rather than read off a claim, which is what puts it on this side
|
|
236
|
+
* of the boundary. Absent when no protected path selected the class.
|
|
237
|
+
*/
|
|
238
|
+
protected_path?: TaggedField<string>;
|
|
239
|
+
/**
|
|
240
|
+
* A one-sentence description of the action, written by a language model
|
|
241
|
+
* (APRV-144). **Claimed, and unverified twice over**: nothing checks it, and
|
|
242
|
+
* its author is not even a party the log knows about.
|
|
243
|
+
*
|
|
244
|
+
* Attached at RENDER time by a channel listener and by nothing else. The gate
|
|
245
|
+
* never sees it, the payload hash does not cover it, the log does not record
|
|
246
|
+
* it, and no code path anywhere branches on its content — the only thing that
|
|
247
|
+
* turns on it is whether the line appears. It is a reading aid whose absence
|
|
248
|
+
* costs nothing but the seconds a human spends parsing the command
|
|
249
|
+
* themselves, which is why every failure mode of producing one resolves to
|
|
250
|
+
* absence.
|
|
251
|
+
*
|
|
252
|
+
* A tagger never sets this. `channels/tagging.ts` derives fields from the
|
|
253
|
+
* log, the policy and the bound bytes; a model's sentence is none of those,
|
|
254
|
+
* and putting it on the runtime side of the boundary would be the exact
|
|
255
|
+
* defect SPEC.md §9 exists to prevent.
|
|
256
|
+
*/
|
|
257
|
+
gloss?: TaggedField<string>;
|
|
258
|
+
/**
|
|
259
|
+
* What a proposed policy amendment changes about class resolution, in
|
|
260
|
+
* before -> after form (APRV-109).
|
|
261
|
+
*
|
|
262
|
+
* **Computed**, by `core/policy-diff.ts` over two policy documents whose
|
|
263
|
+
* bytes the runtime hashed itself: the live file, and a baseline accepted
|
|
264
|
+
* only when its own SHA-256 equals the latest attestation. Present on an
|
|
265
|
+
* attestation prompt and absent everywhere else.
|
|
266
|
+
*
|
|
267
|
+
* A prompt that carried only a hash would ask a human to sign for sixty-four
|
|
268
|
+
* characters. The rule this field exists to enforce is in
|
|
269
|
+
* `core/policy-proposal.ts`: a diff too large for the channel REFUSES the
|
|
270
|
+
* ceremony to the terminal path rather than arriving here truncated.
|
|
271
|
+
*/
|
|
272
|
+
policy_diff?: TaggedField<string>;
|
|
273
|
+
/**
|
|
274
|
+
* Whether a proposed policy loads, and what breaks when it does not
|
|
275
|
+
* (APRV-109).
|
|
276
|
+
*
|
|
277
|
+
* **Computed**, by `core/policy-load.ts` over the same bytes. It is on the
|
|
278
|
+
* prompt because a policy that does not load fails closed to all-manual for
|
|
279
|
+
* every class, and the incident that produced `policy amend` was an operator
|
|
280
|
+
* attesting bytes whose consequences nobody had shown them.
|
|
281
|
+
*/
|
|
282
|
+
policy_load?: TaggedField<string>;
|
|
283
|
+
/** The content binding recorded on `approval.requested` (SPEC.md §6.2). */
|
|
284
|
+
payload_hash: TaggedField<string>;
|
|
285
|
+
/**
|
|
286
|
+
* The delivery address for the token this grant would mint (APRV-105):
|
|
287
|
+
* `sealed to x25519:<first 16 hex of the key's digest>`.
|
|
288
|
+
*
|
|
289
|
+
* **Computed**, and present only when the request published a
|
|
290
|
+
* `token_recipient_key` — that is, only under `token_delivery: sealed`. It is
|
|
291
|
+
* on this side of the boundary because the runtime reads the key off the
|
|
292
|
+
* verified log and digests it here; the requester supplies a key, not this
|
|
293
|
+
* line, and cannot make the line say anything else.
|
|
294
|
+
*
|
|
295
|
+
* It is shown because an approver is entitled to know that granting will put a
|
|
296
|
+
* readable token in the requesting process's hands rather than only on this
|
|
297
|
+
* screen. It changes nothing about the decision's authority: the key ADDRESSES
|
|
298
|
+
* and does not AUTHORIZE, so a grant still mints, still binds to the payload,
|
|
299
|
+
* and is still single-use. Absent for every manual-delivery request, which is
|
|
300
|
+
* every request until an operator amends the policy.
|
|
301
|
+
*/
|
|
302
|
+
token_delivery?: TaggedField<string>;
|
|
303
|
+
/**
|
|
304
|
+
* The bytes the approval binds to (SPEC.md §10.4). Non-null is enforced at
|
|
305
|
+
* construction for `manual` autonomy: see {@link createChannelRequest}.
|
|
306
|
+
*/
|
|
307
|
+
fullPayload: TaggedField<PayloadRendering | null>;
|
|
308
|
+
/** Budget verdicts as of the display instant (`core/budgets.ts`). */
|
|
309
|
+
budgets: TaggedField<BudgetVerdict[]>;
|
|
310
|
+
/** Whether the live policy still matches the attestation (`core/attest.ts`). */
|
|
311
|
+
attestation: TaggedField<AttestationStatus>;
|
|
312
|
+
/** The `approval.requested` timestamp, assigned by the runtime (SPEC.md §8). */
|
|
313
|
+
requested_ts: TaggedField<string>;
|
|
314
|
+
/** Milliseconds of TTL left, or `null` when the policy declares no TTL. */
|
|
315
|
+
ttl_remaining_ms: TaggedField<number | null>;
|
|
316
|
+
/**
|
|
317
|
+
* One line telling the approver how old this question is and how long an
|
|
318
|
+
* answer will still reach anyone (APRV-106):
|
|
319
|
+
*
|
|
320
|
+
* ```
|
|
321
|
+
* requested 32 min ago · requester waits until 09:23 UTC
|
|
322
|
+
* requested 32 min ago · expires 09:23 UTC
|
|
323
|
+
* ```
|
|
324
|
+
*
|
|
325
|
+
* **Computed.** Both halves are arithmetic on instants read from the
|
|
326
|
+
* verified log against the display instant: the age from the
|
|
327
|
+
* `approval.requested` record's runtime-assigned `ts`, and the deadline from
|
|
328
|
+
* either the policy's TTL or, for a request that declared one, the
|
|
329
|
+
* requester's own `wait_until`.
|
|
330
|
+
*
|
|
331
|
+
* That second source is the only place a requester-authored value reaches
|
|
332
|
+
* this line, and it is safe in the direction that matters. `wait_until` is
|
|
333
|
+
* always EARLIER than the TTL (a process that waits longer than the TTL is
|
|
334
|
+
* waiting for something that has already lapsed), so it can only make the
|
|
335
|
+
* question look more urgent, never less. It bounds nothing, charges nothing
|
|
336
|
+
* and gates nothing — SPEC.md §11.1's ratchet holds, because the only
|
|
337
|
+
* scrutiny it can move is upward.
|
|
338
|
+
*
|
|
339
|
+
* The line exists because the incident behind APRV-106 was a human answering
|
|
340
|
+
* a question thirty minutes after its asker had stopped listening. The
|
|
341
|
+
* withdrawal removes that question from the queue; this tells an approver who
|
|
342
|
+
* is looking at the message right now how much time is actually left.
|
|
343
|
+
*/
|
|
344
|
+
waiting: TaggedField<string>;
|
|
345
|
+
/** Position in the hash chain. */
|
|
346
|
+
chain: TaggedField<ChainPosition>;
|
|
347
|
+
/** The derived approval state; always `requested` for a live pending item. */
|
|
348
|
+
state: TaggedField<RequestState>;
|
|
349
|
+
}
|
|
350
|
+
/** Refusals {@link createChannelRequest} can return. Frozen, per §11.1(6). */
|
|
351
|
+
export declare const CHANNEL_REQUEST_REFUSAL_CODES: readonly [
|
|
352
|
+
/** A `manual` request was built with no full payload to present (§10.4). */
|
|
353
|
+
"manual-payload-required",
|
|
354
|
+
/** A member arrived that is not a {@link TaggedField}. */
|
|
355
|
+
"untagged-field"];
|
|
356
|
+
export type ChannelRequestRefusalCode = (typeof CHANNEL_REQUEST_REFUSAL_CODES)[number];
|
|
357
|
+
export interface ChannelRequestRefusal {
|
|
358
|
+
ok: false;
|
|
359
|
+
code: ChannelRequestRefusalCode;
|
|
360
|
+
message: string;
|
|
361
|
+
}
|
|
362
|
+
export type CreateChannelRequestResult = {
|
|
363
|
+
ok: true;
|
|
364
|
+
request: ChannelRequest;
|
|
365
|
+
} | ChannelRequestRefusal;
|
|
366
|
+
/**
|
|
367
|
+
* Build a {@link ChannelRequest}, enforcing the two invariants a type alone
|
|
368
|
+
* cannot.
|
|
369
|
+
*
|
|
370
|
+
* 1. **§10.4**: a `manual` request MUST carry a full payload. A manual request
|
|
371
|
+
* with `fullPayload.value === null` is refused `manual-payload-required` and
|
|
372
|
+
* no object is produced, so no channel can be handed one.
|
|
373
|
+
* 2. **§9**: every member is tagged. The interface says so, but a JavaScript
|
|
374
|
+
* caller (a satellite runtime, a test, an adapter compiled from looser
|
|
375
|
+
* sources) can still hand over a bare value; that is refused
|
|
376
|
+
* `untagged-field` rather than passed through to a renderer that would
|
|
377
|
+
* display it with unearned authority.
|
|
378
|
+
*/
|
|
379
|
+
export declare function createChannelRequest(fields: ChannelRequest): CreateChannelRequestResult;
|
|
380
|
+
/**
|
|
381
|
+
* Every displayable member of `request` is a {@link TaggedField}.
|
|
382
|
+
*
|
|
383
|
+
* The runtime half of "untagged fields are unrepresentable": the type says it
|
|
384
|
+
* at compile time, this says it at run time, and `channels/conformance.ts`
|
|
385
|
+
* calls it on both the request handed to a channel and the split the channel
|
|
386
|
+
* reports having rendered. Throws (rather than returning) because it is an
|
|
387
|
+
* assertion helper used inside test suites.
|
|
388
|
+
*/
|
|
389
|
+
export declare function assertTagged(request: ChannelRequest): void;
|
|
390
|
+
/**
|
|
391
|
+
* A set of pending requests presented under one human gesture (SPEC.md §10.3).
|
|
392
|
+
*
|
|
393
|
+
* The log never batches: each member gets its own `approval.granted` /
|
|
394
|
+
* `approval.rejected`. `deliveryId` is assigned by the channel at `notify` and
|
|
395
|
+
* is what ties those separate events back to the one gesture.
|
|
396
|
+
*/
|
|
397
|
+
export interface ChannelBatch {
|
|
398
|
+
requests: ChannelRequest[];
|
|
399
|
+
/** Assigned by the channel at `notify`; absent until then. */
|
|
400
|
+
deliveryId?: DeliveryId;
|
|
401
|
+
}
|
|
402
|
+
/** A channel's identifier for one delivery (message id, prompt id, …). */
|
|
403
|
+
export type DeliveryId = string;
|
|
404
|
+
/**
|
|
405
|
+
* The event payload field carrying a batch's channel delivery id (SPEC.md
|
|
406
|
+
* §10.3: each event carries "the batch's channel delivery id in its payload").
|
|
407
|
+
*
|
|
408
|
+
* First-class since APRV-38: `decide()` takes `batchDeliveryId` and writes this
|
|
409
|
+
* field on `approval.granted` / `approval.rejected`, and the event schema
|
|
410
|
+
* constrains it. What it replaced is described at
|
|
411
|
+
* {@link BATCH_DELIVERY_NOTE_PREFIX}.
|
|
412
|
+
*/
|
|
413
|
+
export declare const BATCH_DELIVERY_ID_FIELD = "batch_delivery_id";
|
|
414
|
+
/**
|
|
415
|
+
* The legacy `note` prefix that carried a batch delivery id before APRV-38.
|
|
416
|
+
*
|
|
417
|
+
* ## The dual-read window (amended SPEC.md §10.3)
|
|
418
|
+
*
|
|
419
|
+
* APRV-22 had no gate parameter and no schema entry to work with, so the id
|
|
420
|
+
* rode inside the one caller-controlled payload field there was: `note`, whose
|
|
421
|
+
* first line read `batch_delivery_id=<id>`, optionally followed by a newline
|
|
422
|
+
* and the human's own words. Logs written by those builds exist and are
|
|
423
|
+
* append-only, so the encoding cannot be migrated away: it can only stop being
|
|
424
|
+
* written. That is exactly what happens now. {@link recordChannelDecision}
|
|
425
|
+
* writes the first-class field and leaves `note` to the human, while
|
|
426
|
+
* {@link batchDeliveryIdOf} reads both and prefers the field. Readers MUST
|
|
427
|
+
* accept both encodings for the life of v0.1.
|
|
428
|
+
*
|
|
429
|
+
* {@link batchNote} is retained so a caller with a v0.1-era log to reproduce
|
|
430
|
+
* can still produce the old shape. Nothing in this repository calls it on the
|
|
431
|
+
* write path.
|
|
432
|
+
*/
|
|
433
|
+
export declare const BATCH_DELIVERY_NOTE_PREFIX = "batch_delivery_id=";
|
|
434
|
+
/**
|
|
435
|
+
* Encode `batchDeliveryId` (and an optional human note) into a `note` string.
|
|
436
|
+
*
|
|
437
|
+
* The pre-APRV-38 encoding, kept for round-trip fidelity with logs that carry
|
|
438
|
+
* it. New decisions use the first-class payload field instead.
|
|
439
|
+
*/
|
|
440
|
+
export declare function batchNote(batchDeliveryId: DeliveryId, note?: string): string;
|
|
441
|
+
/**
|
|
442
|
+
* The batch delivery id recorded on `record`, or `null` for a unit decision.
|
|
443
|
+
*
|
|
444
|
+
* Reads both encodings (see {@link BATCH_DELIVERY_NOTE_PREFIX}), preferring the
|
|
445
|
+
* first-class `batch_delivery_id` field. The fallback is what keeps audit
|
|
446
|
+
* granularity intact across a log that spans the change: a batch grant written
|
|
447
|
+
* last month and one written today resolve to the same id here.
|
|
448
|
+
*/
|
|
449
|
+
export declare function batchDeliveryIdOf(record: EventRecord): DeliveryId | null;
|
|
450
|
+
/** A human gesture, as reported by a channel. Never an authorization. */
|
|
451
|
+
export interface ChannelDecision {
|
|
452
|
+
action_key: string;
|
|
453
|
+
/**
|
|
454
|
+
* What the human did. `revoke` is deliberately absent: withdrawing a standing
|
|
455
|
+
* authorization is a considered act performed against the log through the
|
|
456
|
+
* CLI, not something to collect from an inline button next to "Approve".
|
|
457
|
+
*/
|
|
458
|
+
decision: "grant" | "reject";
|
|
459
|
+
/** The human's free-text note, if the channel collected one. */
|
|
460
|
+
note?: string;
|
|
461
|
+
/** The delivery this gesture answered. */
|
|
462
|
+
deliveryId: DeliveryId;
|
|
463
|
+
/** Set when the delivery was a batch (SPEC.md §10.3). */
|
|
464
|
+
batchDeliveryId?: DeliveryId;
|
|
465
|
+
}
|
|
466
|
+
/**
|
|
467
|
+
* What the runtime tells the channel became of a reported decision.
|
|
468
|
+
*
|
|
469
|
+
* Note what is *not* here: the raw execution token. A grant mints one
|
|
470
|
+
* (`core/token.ts`), and it is returned to the runtime by
|
|
471
|
+
* {@link recordChannelDecision} in {@link ChannelDecisionResult.token}, never in
|
|
472
|
+
* this value. A channel learns that a grant landed, not how to spend it.
|
|
473
|
+
*/
|
|
474
|
+
export type DecisionOutcome = {
|
|
475
|
+
ok: true;
|
|
476
|
+
action_key: string;
|
|
477
|
+
decision: "grant" | "reject";
|
|
478
|
+
state: RequestState;
|
|
479
|
+
/** The appended `approval.granted` / `approval.rejected` record. */
|
|
480
|
+
record: EventRecord;
|
|
481
|
+
/**
|
|
482
|
+
* A single-use execution token reached THIS surface (grant only). Never
|
|
483
|
+
* its value.
|
|
484
|
+
*
|
|
485
|
+
* False on a grant that minted one and withheld it: a harness-executed
|
|
486
|
+
* request mints none at all (APRV-106), and a self-delivered one seals it
|
|
487
|
+
* to the requester's own address and hands this caller no copy
|
|
488
|
+
* (APRV-211). Neither is a grant without authorization; both are grants
|
|
489
|
+
* whose token was never this surface's to hold.
|
|
490
|
+
*/
|
|
491
|
+
tokenIssued: boolean;
|
|
492
|
+
} | GateRefusal;
|
|
493
|
+
/** A channel's self-report. `detail` explains a `false`; SPEC.md §10.2 polls it. */
|
|
494
|
+
export interface ChannelHealth {
|
|
495
|
+
ok: boolean;
|
|
496
|
+
detail?: string;
|
|
497
|
+
}
|
|
498
|
+
/**
|
|
499
|
+
* The plugin interface every channel implements (SPEC.md §10.3's
|
|
500
|
+
* `notify(request) -> delivery_id`, `poll()/webhook() -> decision`).
|
|
501
|
+
*
|
|
502
|
+
* `onDecision` is the poll/webhook half inverted: the channel discovers the
|
|
503
|
+
* gesture however it likes (a prompt returning, an HTTP callback, a Telegram
|
|
504
|
+
* button) and calls the registered handler. The handler is the runtime's, and
|
|
505
|
+
* what it does is call {@link recordChannelDecision}. A channel that wanted to
|
|
506
|
+
* write the log itself would have to import the gate, and the conformance suite
|
|
507
|
+
* would not care — but the review would.
|
|
508
|
+
*/
|
|
509
|
+
export interface Channel {
|
|
510
|
+
/** Stable identifier: `cli`, `web`, `telegram`. Recorded for audit. */
|
|
511
|
+
name: string;
|
|
512
|
+
/** Present a request (or a batch) to a human. Returns the delivery id. */
|
|
513
|
+
notify(request: ChannelRequest | ChannelBatch): Promise<DeliveryId> | DeliveryId;
|
|
514
|
+
/** Register the runtime's decision handler. Called once, before `notify`. */
|
|
515
|
+
onDecision(handler: (decision: ChannelDecision) => DecisionOutcome): void;
|
|
516
|
+
/** Liveness/config self-report. */
|
|
517
|
+
health(): ChannelHealth;
|
|
518
|
+
/**
|
|
519
|
+
* Annotate a delivery whose request is no longer answerable, and take away
|
|
520
|
+
* whatever gesture it offered (APRV-106). Optional.
|
|
521
|
+
*
|
|
522
|
+
* A withdrawn request leaves every queue by derivation — it is no longer
|
|
523
|
+
* `requested`, and that one predicate is what every channel builds its queue
|
|
524
|
+
* from — so a channel that implements nothing here is still correct: it will
|
|
525
|
+
* never present the request again. What it will not do is fix the message
|
|
526
|
+
* ALREADY on the approver's phone, which still shows two buttons for a
|
|
527
|
+
* question nobody is waiting on. Push channels should implement this;
|
|
528
|
+
* pull channels (`cli`, `web`) re-render from the queue every time and have
|
|
529
|
+
* nothing to retract.
|
|
530
|
+
*
|
|
531
|
+
* Best effort by contract: the runtime calls it and carries on. It collects
|
|
532
|
+
* no gesture, returns no decision, and touches no log.
|
|
533
|
+
*/
|
|
534
|
+
retract?(deliveryId: DeliveryId, reason: string): Promise<void> | void;
|
|
535
|
+
}
|
|
536
|
+
/** One field as a channel actually rendered it. */
|
|
537
|
+
export interface RenderedField {
|
|
538
|
+
/** The {@link ChannelRequest} member this came from. */
|
|
539
|
+
field: string;
|
|
540
|
+
/** How the channel presented it. Must equal the field's own `kind`. */
|
|
541
|
+
kind: "computed" | "claimed";
|
|
542
|
+
/** What the human saw. */
|
|
543
|
+
text: string;
|
|
544
|
+
}
|
|
545
|
+
/** What a channel says it put in front of a human, for one request. */
|
|
546
|
+
export interface RenderedRequest {
|
|
547
|
+
action_key: string;
|
|
548
|
+
fields: RenderedField[];
|
|
549
|
+
/**
|
|
550
|
+
* The full-payload region, verbatim and delineated from the summary
|
|
551
|
+
* (SPEC.md §10.4). `null` means the channel rendered no payload — legal only
|
|
552
|
+
* off the manual path.
|
|
553
|
+
*/
|
|
554
|
+
fullPayloadText: string | null;
|
|
555
|
+
/** Set when this request was rendered as part of a batch. */
|
|
556
|
+
batchDeliveryId?: DeliveryId;
|
|
557
|
+
}
|
|
558
|
+
/**
|
|
559
|
+
* A channel that can be asked what it rendered, for tests only.
|
|
560
|
+
*
|
|
561
|
+
* Conformance cannot read a Telegram message or a terminal's scrollback, so a
|
|
562
|
+
* channel under test reports its own rendering split and the suite checks it
|
|
563
|
+
* against the tagged request. That is an honesty-assuming check — a channel
|
|
564
|
+
* could lie about what it rendered — and it is worth having anyway: the failure
|
|
565
|
+
* mode it catches is a channel that *believes* a claimed field is computed,
|
|
566
|
+
* which is a code path, not a lie. Implementations should build
|
|
567
|
+
* {@link lastRendered} from the same function that builds the real output, not
|
|
568
|
+
* from a parallel description of it.
|
|
569
|
+
*/
|
|
570
|
+
export interface TestableChannel extends Channel {
|
|
571
|
+
/** The most recent rendering: one entry per request, batch members included. */
|
|
572
|
+
lastRendered(): RenderedRequest[];
|
|
573
|
+
}
|
|
574
|
+
/** Is this channel introspectable by the conformance suite? */
|
|
575
|
+
export declare function isTestableChannel(channel: Channel): channel is TestableChannel;
|
|
576
|
+
/** Who is recording. A `human:` id; the gate refuses anything else. */
|
|
577
|
+
export interface ChannelActorOptions {
|
|
578
|
+
/** The approver's identity (`human:<id>`; SPEC.md §11 — config-declared). */
|
|
579
|
+
actor: string;
|
|
580
|
+
/** The channel that collected the gesture, for audit context in the note. */
|
|
581
|
+
channel?: string;
|
|
582
|
+
}
|
|
583
|
+
/**
|
|
584
|
+
* {@link recordChannelDecision}'s result: the channel-safe outcome, plus the
|
|
585
|
+
* secret the channel must never see.
|
|
586
|
+
*/
|
|
587
|
+
export interface ChannelDecisionResult {
|
|
588
|
+
/** Hand this back to the channel. Carries no token. */
|
|
589
|
+
outcome: DecisionOutcome;
|
|
590
|
+
/**
|
|
591
|
+
* The raw single-use execution token, on a successful grant only (APRV-17).
|
|
592
|
+
* The runtime keeps it; the log holds only its SHA-256. Splitting it out of
|
|
593
|
+
* `outcome` is what lets a channel's `onDecision` handler return the outcome
|
|
594
|
+
* without ever holding the token.
|
|
595
|
+
*/
|
|
596
|
+
token?: string;
|
|
597
|
+
}
|
|
598
|
+
/**
|
|
599
|
+
* Turn a reported gesture into a log event — by calling the gate, and by doing
|
|
600
|
+
* nothing else.
|
|
601
|
+
*
|
|
602
|
+
* There is no second decision path in this codebase and this function is not
|
|
603
|
+
* one: it is a translation from {@link ChannelDecision} to `decide()`'s
|
|
604
|
+
* arguments. Every gate rule therefore still holds, and every gate refusal code
|
|
605
|
+
* still surfaces verbatim in {@link DecisionOutcome}:
|
|
606
|
+
*
|
|
607
|
+
* - `actor-not-human` — the configured actor is not `human:…`. A channel cannot
|
|
608
|
+
* escalate itself by claiming to be one, because the actor comes from the
|
|
609
|
+
* runtime's configuration, not from the {@link ChannelDecision}.
|
|
610
|
+
* - `already-decided` — the duplicate-callback case every push channel has
|
|
611
|
+
* (a Telegram button pressed twice, a webhook redelivered). It refuses, and
|
|
612
|
+
* the log keeps the first human answer.
|
|
613
|
+
* - `expired`, `budget-exceeded`, `policy-not-attested`, `append-failed`
|
|
614
|
+
* (`head-moved`) — unchanged, all of them.
|
|
615
|
+
*
|
|
616
|
+
* `batchDeliveryId`, when present, is passed to the gate as such and lands in
|
|
617
|
+
* the event payload as `batch_delivery_id` (amended SPEC.md §10.3). The human's
|
|
618
|
+
* `note` is left carrying the human's words alone. See
|
|
619
|
+
* {@link BATCH_DELIVERY_NOTE_PREFIX} for the encoding this replaced and for the
|
|
620
|
+
* dual-read window readers stay inside for the life of v0.1.
|
|
621
|
+
*/
|
|
622
|
+
export declare function recordChannelDecision(logPath: string, decision: ChannelDecision, actorOptions: ChannelActorOptions, gateOptions?: DecideOptions): ChannelDecisionResult;
|
|
623
|
+
export declare function recordAttestationDecision(logPath: string, decision: ChannelDecision, actorOptions: ChannelActorOptions, gateOptions?: DecideOptions): ChannelDecisionResult;
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The channel-side facade over the canonical renderer (APRV-119).
|
|
3
|
+
*
|
|
4
|
+
* Everything this module used to hold — the email view (APRV-100), the diff view
|
|
5
|
+
* (APRV-124), the command view and its injective escape marking (APRV-126), the
|
|
6
|
+
* classifier-derived breakdown and protected-path lines (APRV-143, APRV-144) —
|
|
7
|
+
* now lives in `core/wysiwys.ts`, absorbed into {@link canonicalRender}. It
|
|
8
|
+
* moved because the gate consults it: `core/gate.ts` computes the
|
|
9
|
+
* `display_hash` a request records at the write boundary from the same function
|
|
10
|
+
* the channels render with, and core cannot import channels.
|
|
11
|
+
*
|
|
12
|
+
* What is left here is the one function that needs a channel type, plus the
|
|
13
|
+
* re-exports that keep every existing import path working. A channel imports
|
|
14
|
+
* this; nothing else does.
|
|
15
|
+
*/
|
|
16
|
+
import type { PayloadRendering } from "./contract.js";
|
|
17
|
+
export * from "../core/wysiwys.js";
|
|
18
|
+
/**
|
|
19
|
+
* The text a channel puts inside its payload region (SPEC.md §10.4, §9).
|
|
20
|
+
*
|
|
21
|
+
* For a whole payload this is {@link canonicalRender}'s text verbatim, so the
|
|
22
|
+
* three channels present the same reading of the same bytes and the log's
|
|
23
|
+
* `display_hash` names it. Claimed material — the summary, the estimate, the
|
|
24
|
+
* rationale, a model's gloss — is never inside this region; each channel renders
|
|
25
|
+
* it above, under its own claimed heading.
|
|
26
|
+
*
|
|
27
|
+
* A **truncated** rendering has no canonical form and does not get one. The
|
|
28
|
+
* tagging layer cut `text` short at the caller's `maxPayloadChars`, so the bytes
|
|
29
|
+
* on screen are not the bytes the token will execute, and giving a partial
|
|
30
|
+
* payload the canonical block's authority is exactly the substitution WYSIWYS
|
|
31
|
+
* forbids. The pre-existing truncated text is returned unchanged, the channel's
|
|
32
|
+
* own "TRUNCATED — do not grant on it" marker still surrounds it, and
|
|
33
|
+
* `channels/batch.ts` still refuses to fold such a member into a batch.
|
|
34
|
+
*/
|
|
35
|
+
export declare function payloadRegionText(rendering: PayloadRendering, actionClass: string): string;
|