approval-md 0.3.0 → 0.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +83 -914
- package/SPEC.md +101 -13
- package/cli.js +134 -7
- package/dist/src/adapters/smtp.d.ts +18 -0
- package/dist/src/adapters/smtp.js +24 -3
- package/dist/src/adapters/smtp.js.map +1 -1
- package/dist/src/channels/contract.d.ts +72 -0
- package/dist/src/channels/contract.js +161 -1
- package/dist/src/channels/contract.js.map +1 -1
- package/dist/src/channels/relay-server.d.ts +90 -0
- package/dist/src/channels/relay-server.js +295 -0
- package/dist/src/channels/relay-server.js.map +1 -0
- package/dist/src/channels/relay.d.ts +273 -0
- package/dist/src/channels/relay.js +564 -0
- package/dist/src/channels/relay.js.map +1 -0
- package/dist/src/channels/tagging.d.ts +25 -0
- package/dist/src/channels/tagging.js +63 -6
- package/dist/src/channels/tagging.js.map +1 -1
- package/dist/src/channels/telegram-minimal.d.ts +251 -0
- package/dist/src/channels/telegram-minimal.js +618 -0
- package/dist/src/channels/telegram-minimal.js.map +1 -0
- package/dist/src/channels/telegram-webhook.d.ts +273 -0
- package/dist/src/channels/telegram-webhook.js +536 -0
- package/dist/src/channels/telegram-webhook.js.map +1 -0
- package/dist/src/channels/telegram.d.ts +725 -20
- package/dist/src/channels/telegram.js +1332 -123
- package/dist/src/channels/telegram.js.map +1 -1
- package/dist/src/cli/amend.js +358 -5
- package/dist/src/cli/amend.js.map +1 -1
- package/dist/src/cli/attest.js +43 -6
- package/dist/src/cli/attest.js.map +1 -1
- package/dist/src/cli/audit-card.d.ts +1 -1
- package/dist/src/cli/audit-card.js +40 -1
- package/dist/src/cli/audit-card.js.map +1 -1
- package/dist/src/cli/audit.d.ts +7 -5
- package/dist/src/cli/audit.js +22 -10
- package/dist/src/cli/audit.js.map +1 -1
- package/dist/src/cli/channel-relay.d.ts +108 -0
- package/dist/src/cli/channel-relay.js +334 -0
- package/dist/src/cli/channel-relay.js.map +1 -0
- package/dist/src/cli/channel-telegram-webhook.d.ts +275 -0
- package/dist/src/cli/channel-telegram-webhook.js +716 -0
- package/dist/src/cli/channel-telegram-webhook.js.map +1 -0
- package/dist/src/cli/channel-telegram.d.ts +347 -28
- package/dist/src/cli/channel-telegram.js +648 -56
- package/dist/src/cli/channel-telegram.js.map +1 -1
- package/dist/src/cli/channel.d.ts +1 -1
- package/dist/src/cli/channel.js +5 -1
- package/dist/src/cli/channel.js.map +1 -1
- package/dist/src/cli/daemon.js +37 -5
- package/dist/src/cli/daemon.js.map +1 -1
- package/dist/src/cli/doctor.d.ts +63 -0
- package/dist/src/cli/doctor.js +856 -111
- package/dist/src/cli/doctor.js.map +1 -1
- package/dist/src/cli/execute.d.ts +29 -1
- package/dist/src/cli/execute.js +387 -21
- package/dist/src/cli/execute.js.map +1 -1
- package/dist/src/cli/exit-codes.d.ts +12 -0
- package/dist/src/cli/exit-codes.js +13 -0
- package/dist/src/cli/exit-codes.js.map +1 -1
- package/dist/src/cli/gate.d.ts +2 -0
- package/dist/src/cli/gate.js +233 -2
- package/dist/src/cli/gate.js.map +1 -1
- package/dist/src/cli/help.d.ts +10 -1
- package/dist/src/cli/help.js +283 -54
- package/dist/src/cli/help.js.map +1 -1
- package/dist/src/cli/hook.d.ts +485 -6
- package/dist/src/cli/hook.js +2370 -70
- package/dist/src/cli/hook.js.map +1 -1
- package/dist/src/cli/init.js +16 -7
- package/dist/src/cli/init.js.map +1 -1
- package/dist/src/cli/instructions.js +24 -3
- package/dist/src/cli/instructions.js.map +1 -1
- package/dist/src/cli/log-anchor.d.ts +55 -0
- package/dist/src/cli/log-anchor.js +199 -60
- package/dist/src/cli/log-anchor.js.map +1 -1
- package/dist/src/cli/log-unlock.d.ts +23 -0
- package/dist/src/cli/log-unlock.js +109 -0
- package/dist/src/cli/log-unlock.js.map +1 -0
- package/dist/src/cli/main.d.ts +0 -12
- package/dist/src/cli/main.js +72 -4
- package/dist/src/cli/main.js.map +1 -1
- package/dist/src/cli/muse.d.ts +2 -0
- package/dist/src/cli/muse.js +62 -0
- package/dist/src/cli/muse.js.map +1 -0
- package/dist/src/cli/payload.js +79 -1
- package/dist/src/cli/payload.js.map +1 -1
- package/dist/src/cli/scaffold.js +6 -0
- package/dist/src/cli/scaffold.js.map +1 -1
- package/dist/src/cli/serve.d.ts +53 -0
- package/dist/src/cli/serve.js +270 -0
- package/dist/src/cli/serve.js.map +1 -0
- package/dist/src/cli/up.js +26 -2
- package/dist/src/cli/up.js.map +1 -1
- package/dist/src/cli/verb-registry.d.ts +2 -0
- package/dist/src/cli/verb-registry.js +338 -39
- package/dist/src/cli/verb-registry.js.map +1 -1
- package/dist/src/cli/wordmark.d.ts +2 -2
- package/dist/src/cli/wordmark.js +2 -2
- package/dist/src/core/attest-bootstrap.d.ts +95 -0
- package/dist/src/core/attest-bootstrap.js +135 -0
- package/dist/src/core/attest-bootstrap.js.map +1 -0
- package/dist/src/core/attest.d.ts +20 -3
- package/dist/src/core/attest.js +25 -0
- package/dist/src/core/attest.js.map +1 -1
- package/dist/src/core/audit.d.ts +153 -12
- package/dist/src/core/audit.js +415 -20
- package/dist/src/core/audit.js.map +1 -1
- package/dist/src/core/channel-lease.d.ts +322 -0
- package/dist/src/core/channel-lease.js +663 -0
- package/dist/src/core/channel-lease.js.map +1 -0
- package/dist/src/core/command-class.d.ts +51 -0
- package/dist/src/core/command-class.js +766 -8
- package/dist/src/core/command-class.js.map +1 -1
- package/dist/src/core/daemon-host.d.ts +178 -0
- package/dist/src/core/daemon-host.js +228 -0
- package/dist/src/core/daemon-host.js.map +1 -0
- package/dist/src/core/daemon-identity.d.ts +238 -0
- package/dist/src/core/daemon-identity.js +252 -0
- package/dist/src/core/daemon-identity.js.map +1 -0
- package/dist/src/core/gate.d.ts +364 -6
- package/dist/src/core/gate.js +802 -27
- package/dist/src/core/gate.js.map +1 -1
- package/dist/src/core/harness-version.d.ts +128 -4
- package/dist/src/core/harness-version.js +179 -12
- package/dist/src/core/harness-version.js.map +1 -1
- package/dist/src/core/harness-wait.d.ts +82 -3
- package/dist/src/core/harness-wait.js +89 -3
- package/dist/src/core/harness-wait.js.map +1 -1
- package/dist/src/core/log-lock.d.ts +435 -0
- package/dist/src/core/log-lock.js +1093 -0
- package/dist/src/core/log-lock.js.map +1 -0
- package/dist/src/core/log-subscribe.d.ts +21 -0
- package/dist/src/core/log-subscribe.js +16 -6
- package/dist/src/core/log-subscribe.js.map +1 -1
- package/dist/src/core/log-write-layer.d.ts +55 -0
- package/dist/src/core/log-write-layer.js +91 -0
- package/dist/src/core/log-write-layer.js.map +1 -0
- package/dist/src/core/log.d.ts +130 -22
- package/dist/src/core/log.js +473 -50
- package/dist/src/core/log.js.map +1 -1
- package/dist/src/core/payload-census.d.ts +51 -0
- package/dist/src/core/payload-census.js +125 -2
- package/dist/src/core/payload-census.js.map +1 -1
- package/dist/src/core/payload-store.d.ts +31 -0
- package/dist/src/core/payload-store.js +113 -8
- package/dist/src/core/payload-store.js.map +1 -1
- package/dist/src/core/payload.d.ts +18 -3
- package/dist/src/core/payload.js +20 -4
- package/dist/src/core/payload.js.map +1 -1
- package/dist/src/core/policy-diff.d.ts +10 -0
- package/dist/src/core/policy-diff.js +10 -2
- package/dist/src/core/policy-diff.js.map +1 -1
- package/dist/src/core/policy-explain.d.ts +7 -1
- package/dist/src/core/policy-explain.js +16 -1
- package/dist/src/core/policy-explain.js.map +1 -1
- package/dist/src/core/policy-load.d.ts +24 -0
- package/dist/src/core/policy-load.js.map +1 -1
- package/dist/src/core/policy-match.d.ts +38 -0
- package/dist/src/core/policy-match.js +35 -0
- package/dist/src/core/policy-match.js.map +1 -1
- package/dist/src/core/policy-proposal.d.ts +40 -0
- package/dist/src/core/policy-proposal.js +37 -16
- package/dist/src/core/policy-proposal.js.map +1 -1
- package/dist/src/core/prompt-layout.d.ts +186 -1
- package/dist/src/core/prompt-layout.js +345 -3
- package/dist/src/core/prompt-layout.js.map +1 -1
- package/dist/src/core/read-scope.d.ts +10 -0
- package/dist/src/core/read-scope.js +4 -0
- package/dist/src/core/read-scope.js.map +1 -1
- package/dist/src/core/run-payload.d.ts +160 -0
- package/dist/src/core/run-payload.js +304 -0
- package/dist/src/core/run-payload.js.map +1 -0
- package/dist/src/core/sender-identity.d.ts +59 -11
- package/dist/src/core/sender-identity.js +79 -11
- package/dist/src/core/sender-identity.js.map +1 -1
- package/dist/src/core/state.d.ts +52 -1
- package/dist/src/core/state.js +52 -6
- package/dist/src/core/state.js.map +1 -1
- package/dist/src/core/telegram-config.d.ts +79 -0
- package/dist/src/core/telegram-config.js +108 -0
- package/dist/src/core/telegram-config.js.map +1 -1
- package/dist/src/core/validate.d.ts +20 -6
- package/dist/src/core/validate.js +0 -0
- package/dist/src/core/validate.js.map +1 -1
- package/dist/src/core/verify.d.ts +23 -1
- package/dist/src/core/verify.js +20 -2
- package/dist/src/core/verify.js.map +1 -1
- package/dist/src/daemon/audit.d.ts +80 -5
- package/dist/src/daemon/audit.js +126 -6
- package/dist/src/daemon/audit.js.map +1 -1
- package/dist/src/daemon/daemon.d.ts +174 -2
- package/dist/src/daemon/daemon.js +298 -7
- package/dist/src/daemon/daemon.js.map +1 -1
- package/dist/src/daemon/git-evidence.d.ts +2 -2
- package/dist/src/daemon/git-evidence.js +1 -1
- package/dist/src/daemon/projection.d.ts +13 -2
- package/dist/src/daemon/projection.js +39 -6
- package/dist/src/daemon/projection.js.map +1 -1
- package/dist/src/mcp/server.d.ts +39 -3
- package/dist/src/mcp/server.js +22 -3
- package/dist/src/mcp/server.js.map +1 -1
- package/dist/src/muse/server.d.ts +16 -0
- package/dist/src/muse/server.js +238 -0
- package/dist/src/muse/server.js.map +1 -0
- package/dist/src/serve/archive.d.ts +166 -0
- package/dist/src/serve/archive.js +363 -0
- package/dist/src/serve/archive.js.map +1 -0
- package/dist/src/serve/arguments.d.ts +93 -0
- package/dist/src/serve/arguments.js +228 -0
- package/dist/src/serve/arguments.js.map +1 -0
- package/dist/src/serve/credentials.d.ts +91 -0
- package/dist/src/serve/credentials.js +143 -0
- package/dist/src/serve/credentials.js.map +1 -0
- package/dist/src/serve/follow.d.ts +86 -0
- package/dist/src/serve/follow.js +101 -0
- package/dist/src/serve/follow.js.map +1 -0
- package/dist/src/serve/hook-thread.d.ts +134 -0
- package/dist/src/serve/hook-thread.js +333 -0
- package/dist/src/serve/hook-thread.js.map +1 -0
- package/dist/src/serve/hook-worker.d.ts +134 -0
- package/dist/src/serve/hook-worker.js +173 -0
- package/dist/src/serve/hook-worker.js.map +1 -0
- package/dist/src/serve/server.d.ts +412 -0
- package/dist/src/serve/server.js +1251 -0
- package/dist/src/serve/server.js.map +1 -0
- package/docs/README-extended.md +768 -0
- package/docs/cli-reference.md +2139 -67
- package/package.json +4 -2
- package/schema/event.schema.json +413 -30
- package/schema/fixtures/event/invalid/audit-lock-reclaimed-age-without-created.json +19 -0
- package/schema/fixtures/event/invalid/audit-lock-reclaimed-agent-actor.json +14 -0
- package/schema/fixtures/event/invalid/audit-lock-reclaimed-lockfile-path.json +14 -0
- package/schema/fixtures/event/invalid/audit-lock-reclaimed-operator-cleared-system-actor.json +14 -0
- package/schema/fixtures/event/invalid/audit-lock-reclaimed-unknown-reason.json +14 -0
- package/schema/fixtures/event/invalid/audit-lock-reclaimed-without-age.json +13 -0
- package/schema/fixtures/event/invalid/audit-reviewed-agent-actor.json +6 -1
- package/schema/fixtures/event/invalid/audit-reviewed-bad-payload-hash.json +21 -0
- package/schema/fixtures/event/invalid/audit-reviewed-loved-no-note.json +5 -0
- package/schema/fixtures/event/invalid/audit-reviewed-no-subject-hash.json +19 -0
- package/schema/fixtures/event/invalid/audit-reviewed-no-subject-seq.json +19 -0
- package/schema/fixtures/event/invalid/audit-reviewed-no-verdict-source.json +19 -0
- package/schema/fixtures/event/invalid/audit-reviewed-no-verdict.json +19 -0
- package/schema/fixtures/event/invalid/audit-reviewed-system-actor.json +6 -1
- package/schema/fixtures/event/invalid/audit-sampled-no-policy-hash.json +14 -0
- package/schema/fixtures/event/invalid/daemon-id-malformed.json +15 -0
- package/schema/fixtures/event/invalid/execution-started-truncated-policy-sha256.json +18 -0
- package/schema/fixtures/event/invalid/harness-cap-at-margin.json +16 -0
- package/schema/fixtures/event/invalid/harness-cap-fractional.json +16 -0
- package/schema/fixtures/event/invalid/harness-cap-negative.json +16 -0
- package/schema/fixtures/event/invalid/harness-cap-string.json +16 -0
- package/schema/fixtures/event/invalid/harness-cap-without-harness-execution.json +16 -0
- package/schema/fixtures/event/invalid/harness-cap-zero.json +16 -0
- package/schema/fixtures/event/valid/approval-requested-harness-cap.json +18 -0
- package/schema/fixtures/event/valid/audit-lock-reclaimed-implausible-created.json +18 -0
- package/schema/fixtures/event/valid/audit-lock-reclaimed-legacy.json +14 -0
- package/schema/fixtures/event/valid/audit-lock-reclaimed-unlock.json +19 -0
- package/schema/fixtures/event/valid/audit-lock-reclaimed.json +19 -0
- package/schema/fixtures/event/valid/audit-reviewed-reaction.json +5 -0
- package/schema/fixtures/event/valid/audit-reviewed.json +6 -1
- package/schema/fixtures/event/valid/audit-sampled.json +2 -1
- package/schema/fixtures/event/valid/daemon-identity-declared.json +17 -0
- package/schema/fixtures/event/valid/daemon-identity.json +16 -0
- package/schema/fixtures/event/valid/execution-started-policy-sha256.json +18 -0
- package/schema/fixtures/event/valid/harness-kind-hermes.json +23 -0
- package/schema/fixtures/policy/invalid/daemons-malformed-id.json +10 -0
- package/schema/fixtures/policy/valid/daemons-allowlist.json +19 -0
- package/schema/fixtures/policy-md/invalid/schema-invalid-agent-may-request.md +21 -0
- package/schema/fixtures/policy-md/valid/agent-may-request.md +24 -0
- package/schema/policy.schema.json +102 -6
|
@@ -0,0 +1,273 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The authenticated relay channel: its protocol (SPEC.md §10.3, APRV-455).
|
|
3
|
+
*
|
|
4
|
+
* Agent Village residents review their approval.md settings in the Edge City
|
|
5
|
+
* onboarding app, and that review is meant to be the attestation of the policy
|
|
6
|
+
* in force. The app signs the resident in through EdgeOS, and the operator's
|
|
7
|
+
* control plane is what holds that session. This module is the daemon's side
|
|
8
|
+
* of the hop from there: a gesture the control plane carries, attributed to the
|
|
9
|
+
* EdgeOS `/humans/me` id of the session that made it, arriving over an
|
|
10
|
+
* authenticated HTTP post (`channels/relay-server.ts`) and turned into a log
|
|
11
|
+
* event by the SAME function a Telegram tap goes through.
|
|
12
|
+
*
|
|
13
|
+
* ## What the relay is trusted for, and what it is not
|
|
14
|
+
*
|
|
15
|
+
* The daemon trusts the relay's report of WHO acted, exactly as it trusts the
|
|
16
|
+
* Telegram Bot API's report of `callback_query.from.id`. That is operator
|
|
17
|
+
* trust (APRV-422: no hosted service has authority over decisions), and it is
|
|
18
|
+
* the whole of what the shared secret buys: a post that carries it is a report
|
|
19
|
+
* from the operator's relay, and a post that does not is nothing. Every
|
|
20
|
+
* question of AUTHORITY is answered here, by the runtime, from the verified log
|
|
21
|
+
* and the attested policy:
|
|
22
|
+
*
|
|
23
|
+
* - which approver the account is, by `core/sender-identity.ts` against the
|
|
24
|
+
* attested mapping, exactly as a tap is resolved;
|
|
25
|
+
* - whether that approver may decide this request, attest this policy, or
|
|
26
|
+
* neither, by `core/gate.ts` and `core/policy-proposal.ts`, unchanged;
|
|
27
|
+
* - whether the request is still answerable (expiry, policy drift, an earlier
|
|
28
|
+
* answer), by the same gate, with the same codes.
|
|
29
|
+
*
|
|
30
|
+
* The relay holds NO human identity of its own. A Telegram listener launched
|
|
31
|
+
* `--as human:carter` falls back to that identity for a channel the policy maps
|
|
32
|
+
* no sender for; this surface does not, ever (`requireSenderMapping`). A post
|
|
33
|
+
* naming an account the attested policy does not map is refused
|
|
34
|
+
* `sender-unmapped` with one `audit.decision_refused`, and a policy that maps
|
|
35
|
+
* nobody on `edgeos` refuses every gesture the same way.
|
|
36
|
+
*
|
|
37
|
+
* ## The body, closed
|
|
38
|
+
*
|
|
39
|
+
* ```json
|
|
40
|
+
* { "gesture": "attest" | "decline", "policy_sha256": "<64 hex>",
|
|
41
|
+
* "sender": { "channel": "edgeos", "id": "<EdgeOS /humans/me id>" },
|
|
42
|
+
* "nonce": "<16..128 of A-Z a-z 0-9 _ ->", "issued_at": "<RFC 3339 UTC>" }
|
|
43
|
+
* { "gesture": "grant" | "reject", "action_key": "<the request's key>",
|
|
44
|
+
* "sender": { ... }, "nonce": "...", "issued_at": "..." }
|
|
45
|
+
* { "gesture": "propose", "policy_sha256": "<64 hex>",
|
|
46
|
+
* "nonce": "...", "issued_at": "..." }
|
|
47
|
+
* ```
|
|
48
|
+
*
|
|
49
|
+
* Unknown keys are refused rather than ignored, so a field that looks like it
|
|
50
|
+
* carries authority (an `actor`, a second sender, a `ts`) cannot ride along on
|
|
51
|
+
* a valid post and be read by something later. `issued_at` is used for one
|
|
52
|
+
* thing only, the freshness window that bounds how long a nonce must be
|
|
53
|
+
* remembered; it is never recorded, and every record's `ts` is still the
|
|
54
|
+
* runtime's own (SPEC.md §11.1 invariant 2).
|
|
55
|
+
*
|
|
56
|
+
* ## Why `propose` exists here
|
|
57
|
+
*
|
|
58
|
+
* The control plane renders the resident's policy and writes it into the
|
|
59
|
+
* store; somebody then has to append the `policy.proposed` an acceptance
|
|
60
|
+
* answers. The existing route for that is `approval policy amend`, which is a
|
|
61
|
+
* git-aware terminal ceremony that blocks for its own `--wait`. `propose` is the
|
|
62
|
+
* same `core/policy-proposal.ts` call, under the same secret, recorded under the
|
|
63
|
+
* actor the operator started the relay with (`--proposer`), and it returns at
|
|
64
|
+
* once. It binds the hash the control plane rendered: bytes on disk that are not
|
|
65
|
+
* the bytes it names are refused, so the prompt the resident answers is the file
|
|
66
|
+
* the control plane showed them.
|
|
67
|
+
*
|
|
68
|
+
* It also admits the one proposal `policy amend` refuses: bytes that are
|
|
69
|
+
* ALREADY the policy in force (`ProposeInput.reaffirm`). The onboarding review
|
|
70
|
+
* most residents make is "accept, unchanged", and the operator attested those
|
|
71
|
+
* bytes at provisioning (APRV-449). The reaffirmation changes the attester of
|
|
72
|
+
* record and nothing else: same hash, a diff that reads "no semantic change",
|
|
73
|
+
* and an acceptance resolved against the policy in force like any other.
|
|
74
|
+
*
|
|
75
|
+
* ## No token leaves this surface
|
|
76
|
+
*
|
|
77
|
+
* A grant may mint a single-use execution token for the deciding surface
|
|
78
|
+
* (manual delivery). A relay is not a place a token can go: the control plane
|
|
79
|
+
* is not the requester and the HTTP response is not a secrets channel. The
|
|
80
|
+
* token `recordChannelDecision` returns is dropped here, unread, and the
|
|
81
|
+
* response says only whether one was minted. The deployments this exists for
|
|
82
|
+
* mint none (a harness-executed request) or seal it to the requester's own
|
|
83
|
+
* key, so nothing is lost in practice, and the residual is stated rather than
|
|
84
|
+
* hidden: a manual-delivery request granted through the relay has a token
|
|
85
|
+
* nobody holds, and its requester asks again.
|
|
86
|
+
*/
|
|
87
|
+
import type { DecideOptions } from "../core/gate.js";
|
|
88
|
+
import { type ChannelSender } from "../core/sender-identity.js";
|
|
89
|
+
/** The channel name every relay gesture is recorded under, and its senders' namespace. */
|
|
90
|
+
export declare const RELAY_CHANNEL = "edgeos";
|
|
91
|
+
/** The header the shared secret travels in, lowercased as Node presents it. */
|
|
92
|
+
export declare const RELAY_SECRET_HEADER = "x-approval-relay-secret";
|
|
93
|
+
/** The one path this transport answers. */
|
|
94
|
+
export declare const RELAY_PATH = "/relay/gesture";
|
|
95
|
+
/** The interface it binds when the operator names none. Loopback, always. */
|
|
96
|
+
export declare const RELAY_DEFAULT_HOST = "127.0.0.1";
|
|
97
|
+
/** The port it binds when the operator names none: `serve` is 4682, the webhook 4683. */
|
|
98
|
+
export declare const RELAY_DEFAULT_PORT = 4684;
|
|
99
|
+
/** Largest body accepted. A gesture is a handful of short strings. */
|
|
100
|
+
export declare const RELAY_MAX_BODY_BYTES: number;
|
|
101
|
+
/** How much of an oversized body is drained before the socket is dropped. */
|
|
102
|
+
export declare const RELAY_DRAIN_LIMIT_BYTES: number;
|
|
103
|
+
/**
|
|
104
|
+
* How far `issued_at` may sit from this process's clock, either side.
|
|
105
|
+
*
|
|
106
|
+
* Five minutes: a control plane and a sandbox on the same platform agree to
|
|
107
|
+
* well under a second, and a gesture older than this was not made by a person
|
|
108
|
+
* who is still looking at the screen that made it.
|
|
109
|
+
*/
|
|
110
|
+
export declare const RELAY_GESTURE_WINDOW_MS: number;
|
|
111
|
+
/**
|
|
112
|
+
* How long a claimed nonce is remembered.
|
|
113
|
+
*
|
|
114
|
+
* Longer than the whole window a fresh gesture can occupy (issued up to one
|
|
115
|
+
* window in the future, accepted up to one window late), with a minute of
|
|
116
|
+
* margin, so a replay is refused by the nonce while it is still fresh and by
|
|
117
|
+
* the window once the nonce is forgotten. There is no instant at which neither
|
|
118
|
+
* holds.
|
|
119
|
+
*/
|
|
120
|
+
export declare const RELAY_NONCE_RETENTION_MS: number;
|
|
121
|
+
/**
|
|
122
|
+
* How long a relay proposal stays open (APRV-455 refuter L2).
|
|
123
|
+
*
|
|
124
|
+
* The onboarding review is answered in the screen that rendered it, so an hour
|
|
125
|
+
* is generous; past it the prompt retires from every channel queue by
|
|
126
|
+
* derivation, and a control plane whose resident came back later proposes
|
|
127
|
+
* again. Until a proposal is attested the bytes on disk are not the policy in
|
|
128
|
+
* force, so the control plane restores the in-force bytes when a proposal is
|
|
129
|
+
* declined or lapses.
|
|
130
|
+
*/
|
|
131
|
+
export declare const RELAY_PROPOSAL_TTL_MS: number;
|
|
132
|
+
/** The actor a proposal is recorded under when the operator names none. */
|
|
133
|
+
export declare const RELAY_DEFAULT_PROPOSER = "agent:edgeos-relay";
|
|
134
|
+
/** The gestures, closed. */
|
|
135
|
+
export declare const RELAY_GESTURES: readonly ["propose", "attest", "decline", "grant", "reject"];
|
|
136
|
+
export type RelayGestureName = (typeof RELAY_GESTURES)[number];
|
|
137
|
+
/**
|
|
138
|
+
* Every refusal THIS transport makes, and nothing else. Frozen, per SPEC.md
|
|
139
|
+
* §11.1 invariant 6, and pinned by `conformance/vectors/refusal-unions.v1.json`
|
|
140
|
+
* because the control plane on the other side of the hop is a second
|
|
141
|
+
* implementation that branches on these strings.
|
|
142
|
+
*
|
|
143
|
+
* Refusals the GATE or the decision surface makes (`sender-unmapped`,
|
|
144
|
+
* `expired`, `policy-drift`, `already-decided`, `proposal-stale`,
|
|
145
|
+
* `proposal-not-found`, ...) are not here: they arrive verbatim under
|
|
146
|
+
* `refusal` in the response body, from their own unions, exactly as a Telegram
|
|
147
|
+
* tap would surface them.
|
|
148
|
+
*
|
|
149
|
+
* None of these appends to the log. A post the relay refuses is a fact about
|
|
150
|
+
* the transport and is counted and reported on stderr, because an endpoint
|
|
151
|
+
* that wrote its refusals into the approval log would let anyone who can reach
|
|
152
|
+
* it grow the record a human is asked to trust.
|
|
153
|
+
*/
|
|
154
|
+
export declare const RELAY_REFUSAL_CODES: readonly [
|
|
155
|
+
/** No secret header, or one that is not the launch environment's secret. */
|
|
156
|
+
"relay-secret-mismatch",
|
|
157
|
+
/** More than one secret header. Malformed, and not resolvable by choosing. */
|
|
158
|
+
"relay-duplicate-secret-header",
|
|
159
|
+
/** The request line and the Host header do not form a URL. */
|
|
160
|
+
"relay-malformed-request",
|
|
161
|
+
/** A path this transport does not serve. */
|
|
162
|
+
"relay-unknown-path",
|
|
163
|
+
/** The right path, a method other than POST. */
|
|
164
|
+
"relay-method-not-allowed",
|
|
165
|
+
/** Larger than {@link RELAY_MAX_BODY_BYTES}. */
|
|
166
|
+
"relay-body-too-large",
|
|
167
|
+
/** The body could not be read off the socket, or is not JSON. */
|
|
168
|
+
"relay-body-unreadable",
|
|
169
|
+
/** JSON that is not a gesture of the closed shape. */
|
|
170
|
+
"relay-body-invalid",
|
|
171
|
+
/**
|
|
172
|
+
* The sender is not an EdgeOS account this transport can report: a channel
|
|
173
|
+
* other than `edgeos`, or an id outside the grammar `senders.edgeos` pins.
|
|
174
|
+
*/
|
|
175
|
+
"relay-sender-invalid",
|
|
176
|
+
/** `issued_at` is further than {@link RELAY_GESTURE_WINDOW_MS} from now. */
|
|
177
|
+
"relay-gesture-stale",
|
|
178
|
+
/** This nonce was already used within {@link RELAY_NONCE_RETENTION_MS}. */
|
|
179
|
+
"relay-nonce-replayed",
|
|
180
|
+
/**
|
|
181
|
+
* The nonce ledger could not be written, so a replay could not be refused.
|
|
182
|
+
* Fails closed: nothing is decided without it.
|
|
183
|
+
*/
|
|
184
|
+
"relay-nonce-unavailable",
|
|
185
|
+
/**
|
|
186
|
+
* `propose` named a hash that is not the hash of the policy file on disk.
|
|
187
|
+
* Distinct from the gate's `proposal-stale`, which is the same fact found at
|
|
188
|
+
* ACCEPTANCE: this one is found before anything is proposed.
|
|
189
|
+
*/
|
|
190
|
+
"relay-policy-mismatch",
|
|
191
|
+
/** The gesture threw while being applied. Nothing is claimed about the log. */
|
|
192
|
+
"relay-handler-failed"];
|
|
193
|
+
export type RelayRefusalCode = (typeof RELAY_REFUSAL_CODES)[number];
|
|
194
|
+
/** A parsed, shape-checked gesture. */
|
|
195
|
+
export type RelayGesture = {
|
|
196
|
+
gesture: "attest" | "decline";
|
|
197
|
+
policySha256: string;
|
|
198
|
+
sender: ChannelSender;
|
|
199
|
+
nonce: string;
|
|
200
|
+
issuedAtMs: number;
|
|
201
|
+
} | {
|
|
202
|
+
gesture: "grant" | "reject";
|
|
203
|
+
actionKey: string;
|
|
204
|
+
sender: ChannelSender;
|
|
205
|
+
nonce: string;
|
|
206
|
+
issuedAtMs: number;
|
|
207
|
+
} | {
|
|
208
|
+
gesture: "propose";
|
|
209
|
+
policySha256: string;
|
|
210
|
+
nonce: string;
|
|
211
|
+
issuedAtMs: number;
|
|
212
|
+
};
|
|
213
|
+
export type RelayParse = {
|
|
214
|
+
ok: true;
|
|
215
|
+
gesture: RelayGesture;
|
|
216
|
+
} | {
|
|
217
|
+
ok: false;
|
|
218
|
+
code: RelayRefusalCode;
|
|
219
|
+
message: string;
|
|
220
|
+
};
|
|
221
|
+
/**
|
|
222
|
+
* Check one decoded body against the closed shape.
|
|
223
|
+
*
|
|
224
|
+
* Pure: no clock, no file, no log. Freshness and the nonce are the server's,
|
|
225
|
+
* because both need state this function deliberately does not hold.
|
|
226
|
+
*/
|
|
227
|
+
export declare function parseRelayGesture(value: unknown): RelayParse;
|
|
228
|
+
/** Is `issuedAtMs` within the window of `nowMs`? */
|
|
229
|
+
export declare function relayGestureFresh(issuedAtMs: number, nowMs: number): boolean;
|
|
230
|
+
/** What claiming one nonce came to. */
|
|
231
|
+
export type NonceClaim = "claimed" | "replayed" | {
|
|
232
|
+
error: string;
|
|
233
|
+
};
|
|
234
|
+
/**
|
|
235
|
+
* Where used nonces are remembered.
|
|
236
|
+
*
|
|
237
|
+
* One file per nonce, named by its SHA-256, written whole to a private temp and
|
|
238
|
+
* then hard-linked into place, under the gate's own `daemon/` directory. The
|
|
239
|
+
* file system settles the race: `link` fails with EEXIST exactly as `O_EXCL`
|
|
240
|
+
* does, so of any number of processes claiming one nonce at once exactly one
|
|
241
|
+
* wins, and a claim file never exists without the time it was claimed.
|
|
242
|
+
* That is why two relays on one gate need no lease between them for replay
|
|
243
|
+
* protection, and why a restart forgets nothing it was still obliged to
|
|
244
|
+
* remember. Files older than {@link RELAY_NONCE_RETENTION_MS} are pruned.
|
|
245
|
+
*/
|
|
246
|
+
export interface NonceLedger {
|
|
247
|
+
claim(nonce: string, nowMs: number): NonceClaim;
|
|
248
|
+
}
|
|
249
|
+
/** The file ledger described at {@link NonceLedger}. */
|
|
250
|
+
export declare function fileNonceLedger(dir: string): NonceLedger;
|
|
251
|
+
/** What applying a gesture produced: an HTTP status and the JSON body. */
|
|
252
|
+
export interface RelayResponse {
|
|
253
|
+
status: number;
|
|
254
|
+
body: Record<string, unknown>;
|
|
255
|
+
}
|
|
256
|
+
export interface ApplyRelayOptions {
|
|
257
|
+
/** The gate options a decision runs under: the policy location, the clock. */
|
|
258
|
+
gateOptions: DecideOptions;
|
|
259
|
+
/** The actor a `propose` is recorded under. */
|
|
260
|
+
proposer: string;
|
|
261
|
+
}
|
|
262
|
+
/** A transport refusal as a response. Never appended. */
|
|
263
|
+
export declare function relayRefusal(status: number, code: RelayRefusalCode, message: string): RelayResponse;
|
|
264
|
+
/**
|
|
265
|
+
* Turn one checked gesture into a log event, or into the refusal that replaces
|
|
266
|
+
* one.
|
|
267
|
+
*
|
|
268
|
+
* Decisions and attestations go through {@link recordChannelDecision}, the one
|
|
269
|
+
* place a reported gesture becomes a log event, with `requireSenderMapping` set
|
|
270
|
+
* and the channel named `edgeos`. A proposal goes through `proposeAttestation`.
|
|
271
|
+
* Nothing here decides anything those two do not already decide.
|
|
272
|
+
*/
|
|
273
|
+
export declare function applyRelayGesture(logPath: string, gesture: RelayGesture, options: ApplyRelayOptions): RelayResponse;
|