approval-md 0.4.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/SPEC.md +3 -0
- package/cli.js +72 -4
- package/dist/src/channels/contract.d.ts +53 -0
- package/dist/src/channels/contract.js +56 -0
- package/dist/src/channels/contract.js.map +1 -1
- package/dist/src/channels/tagging.d.ts +25 -0
- package/dist/src/channels/tagging.js +57 -5
- 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.d.ts +454 -19
- package/dist/src/channels/telegram.js +931 -80
- package/dist/src/channels/telegram.js.map +1 -1
- package/dist/src/cli/amend.js +8 -1
- package/dist/src/cli/amend.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-telegram-webhook.d.ts +2 -0
- package/dist/src/cli/channel-telegram-webhook.js +7 -0
- package/dist/src/cli/channel-telegram-webhook.js.map +1 -1
- package/dist/src/cli/channel-telegram.d.ts +75 -2
- package/dist/src/cli/channel-telegram.js +170 -15
- package/dist/src/cli/channel-telegram.js.map +1 -1
- package/dist/src/cli/execute.d.ts +12 -0
- package/dist/src/cli/execute.js +46 -1
- package/dist/src/cli/execute.js.map +1 -1
- package/dist/src/cli/help.d.ts +1 -0
- package/dist/src/cli/help.js +39 -18
- package/dist/src/cli/help.js.map +1 -1
- package/dist/src/cli/hook.d.ts +114 -3
- package/dist/src/cli/hook.js +468 -50
- package/dist/src/cli/hook.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.js +23 -6
- package/dist/src/cli/main.js.map +1 -1
- package/dist/src/cli/up.js +5 -0
- package/dist/src/cli/up.js.map +1 -1
- package/dist/src/cli/verb-registry.js +1 -0
- 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/audit.d.ts +98 -9
- package/dist/src/core/audit.js +363 -15
- package/dist/src/core/audit.js.map +1 -1
- package/dist/src/core/command-class.js +6 -0
- package/dist/src/core/command-class.js.map +1 -1
- package/dist/src/core/gate.d.ts +58 -1
- package/dist/src/core/gate.js +30 -3
- package/dist/src/core/gate.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.d.ts +82 -3
- package/dist/src/core/log.js +285 -40
- package/dist/src/core/log.js.map +1 -1
- package/dist/src/core/policy-proposal.d.ts +17 -0
- package/dist/src/core/policy-proposal.js +29 -14
- 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/sender-identity.d.ts +24 -1
- package/dist/src/core/sender-identity.js +31 -0
- package/dist/src/core/sender-identity.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/daemon/git-evidence.d.ts +2 -2
- package/dist/src/daemon/git-evidence.js +1 -1
- package/dist/src/serve/archive.d.ts +8 -0
- package/dist/src/serve/archive.js +11 -2
- package/dist/src/serve/archive.js.map +1 -1
- package/docs/README-extended.md +2 -0
- package/docs/cli-reference.md +507 -25
- package/package.json +2 -2
- package/schema/event.schema.json +354 -28
- 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/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.json +2 -1
- package/schema/policy.schema.json +52 -0
|
@@ -219,10 +219,10 @@
|
|
|
219
219
|
* process memory and nothing else: no event is appended, no message is edited,
|
|
220
220
|
* and the log is not opened.
|
|
221
221
|
*/
|
|
222
|
-
import type { ChannelBatch, ChannelDecision, ChannelHealth, ChannelRequest, DecisionOutcome, DeliveryId, RenderedRequest, TaggedField, TestableChannel } from "./contract.js";
|
|
222
|
+
import type { ChannelBatch, ChannelDecision, ChannelDecisionRefusal, ChannelHealth, ChannelRequest, DecisionOutcome, DeliveryId, RenderedRequest, TaggedField, TestableChannel } from "./contract.js";
|
|
223
223
|
import type { ChannelSender } from "../core/sender-identity.js";
|
|
224
224
|
import { type Reaction, type ReviewVerdict } from "../core/audit.js";
|
|
225
|
-
import { type PromptLayout } from "../core/prompt-layout.js";
|
|
225
|
+
import { type PromptLayout, type PromptSay, type PromptStyle } from "../core/prompt-layout.js";
|
|
226
226
|
export { TELEGRAM_CHAT_ENV, TELEGRAM_TOKEN_ENV, telegramChatEnvFor, telegramTokenEnvFor, } from "../core/telegram-config.js";
|
|
227
227
|
/** The real Bot API. Overridden only by tests, against a local mock. */
|
|
228
228
|
export declare const TELEGRAM_DEFAULT_API_BASE = "https://api.telegram.org";
|
|
@@ -486,6 +486,66 @@ export interface TelegramConfig {
|
|
|
486
486
|
* what it rendered before the key existed.
|
|
487
487
|
*/
|
|
488
488
|
layout?: PromptLayout;
|
|
489
|
+
/**
|
|
490
|
+
* Whether APRV-196's stale-copy fallback is on (APRV-456). Defaults to `true`,
|
|
491
|
+
* the direct-bot shape.
|
|
492
|
+
*
|
|
493
|
+
* On, a decision tap whose nonce this process is not holding falls back to
|
|
494
|
+
* its action reference, and is carried to the gate when this process holds
|
|
495
|
+
* that action open. The bound that makes that safe is that only the bot token
|
|
496
|
+
* can put a button in front of the approver, so any button carrying a live
|
|
497
|
+
* reference was put there by some process of this gate.
|
|
498
|
+
*
|
|
499
|
+
* Off, that tap is refused `nonce-not-issued` before the gate is called and
|
|
500
|
+
* never reaches the decision handler; at most one `audit.decision_refused` is
|
|
501
|
+
* written for it, through {@link TelegramChannel.onRefusedDecision}. The verb
|
|
502
|
+
* turns it off when the channel runs through a relay (an `--api-base` other
|
|
503
|
+
* than the Bot API), where anyone holding the relay's token can send the
|
|
504
|
+
* approver a card with forged text and a real action's reference, and when
|
|
505
|
+
* the operator passes `--no-stale-copy`. It is launch configuration and
|
|
506
|
+
* nothing else: no policy key and nothing in an update can set it.
|
|
507
|
+
*/
|
|
508
|
+
staleCopy?: boolean;
|
|
509
|
+
/**
|
|
510
|
+
* Which layout a prompt is drawn in (APRV-489), from
|
|
511
|
+
* `channels.telegram.prompt.style`. Passed in by the verb, which has already
|
|
512
|
+
* loaded the policy, for {@link TelegramConfig.layout}'s reason. Defaults to
|
|
513
|
+
* `technical`, so a channel constructed without one sends exactly what it
|
|
514
|
+
* sent before the key existed.
|
|
515
|
+
*/
|
|
516
|
+
promptStyle?: PromptStyle;
|
|
517
|
+
/**
|
|
518
|
+
* The operator's plain-words declarations per class (APRV-489), from
|
|
519
|
+
* `channels.telegram.prompt.say`. Read only under `promptStyle: minimal`.
|
|
520
|
+
*/
|
|
521
|
+
say?: PromptSay;
|
|
522
|
+
/**
|
|
523
|
+
* Where the style and the `say` map come from FOR EACH DELIVERY (fix round
|
|
524
|
+
* 2, S4). The verb passes a function that reads the policy file the tagger
|
|
525
|
+
* reads, so a re-attested style or `say` takes effect on the next card
|
|
526
|
+
* without a restart, exactly as a re-attested class rule does. When absent,
|
|
527
|
+
* {@link TelegramConfig.promptStyle} and {@link TelegramConfig.say} are used.
|
|
528
|
+
* A resolver that throws means `technical`.
|
|
529
|
+
*/
|
|
530
|
+
promptFor?: () => {
|
|
531
|
+
style: PromptStyle;
|
|
532
|
+
say: PromptSay;
|
|
533
|
+
fallback?: string;
|
|
534
|
+
};
|
|
535
|
+
}
|
|
536
|
+
/**
|
|
537
|
+
* A decision tap the channel refused on its own, before any handler ran
|
|
538
|
+
* (APRV-456), handed to {@link TelegramChannel.onRefusedDecision} so the
|
|
539
|
+
* runtime can record that a human's attention was spent.
|
|
540
|
+
*
|
|
541
|
+
* `decision.action_key` and `decision.deliveryId` come from the delivery THIS
|
|
542
|
+
* process holds open for the reference the bytes carried, never from the bytes
|
|
543
|
+
* themselves; a tap whose reference names nothing open here produces no
|
|
544
|
+
* {@link TelegramRefusedDecision} at all.
|
|
545
|
+
*/
|
|
546
|
+
export interface TelegramRefusedDecision {
|
|
547
|
+
decision: ChannelDecision;
|
|
548
|
+
refusal: ChannelDecisionRefusal;
|
|
489
549
|
}
|
|
490
550
|
/**
|
|
491
551
|
* Why a callback was ignored.
|
|
@@ -542,6 +602,14 @@ export interface TelegramStats {
|
|
|
542
602
|
* restart is costing the approver a wrong tap.
|
|
543
603
|
*/
|
|
544
604
|
staleCopyDecisions: number;
|
|
605
|
+
/**
|
|
606
|
+
* Taps refused `nonce-not-issued` because the stale-copy fallback is off
|
|
607
|
+
* (APRV-456): a decision button whose nonce this process is not holding, on
|
|
608
|
+
* a relayed channel or one started with `--no-stale-copy`. Counted whether or
|
|
609
|
+
* not a record was written, because a rising number here on a relayed gate
|
|
610
|
+
* is either restarts or somebody putting cards in front of the approver.
|
|
611
|
+
*/
|
|
612
|
+
staleCopyRefusals: number;
|
|
545
613
|
/**
|
|
546
614
|
* Bot commands handed to the runtime's command handler (APRV-216). Never a
|
|
547
615
|
* decision and never a log event: a command reorders what this process shows
|
|
@@ -694,10 +762,21 @@ export interface TelegramRendering {
|
|
|
694
762
|
* attached one. Naming the subset as a type is what lets {@link reviewRow} and
|
|
695
763
|
* {@link telegramRow} share one implementation of those five without either
|
|
696
764
|
* side casting: a card supplies exactly these fields, and the compiler refuses
|
|
697
|
-
* a card that reaches for `budgets`, `
|
|
698
|
-
* a pending question has.
|
|
765
|
+
* a card that reaches for `budgets`, `ttl_remaining_ms`, or anything else that
|
|
766
|
+
* only a pending question has.
|
|
767
|
+
*
|
|
768
|
+
* Since APRV-480 a card also carries `fullPayload`, the bytes the sampled
|
|
769
|
+
* execution bound to, and `payload_hash`, the binding the log recorded for them.
|
|
770
|
+
* Under supervised-retro a review counts as the individual approval nobody gave
|
|
771
|
+
* before the action ran, so the reviewer has to be able to read what was
|
|
772
|
+
* published; a uuid beside the agent's own summary of what it did is the agent
|
|
773
|
+
* describing its own homework. `fullPayload` is computed the way a prompt's is
|
|
774
|
+
* (hash-checked against the binding before anything renders it) and is `null`
|
|
775
|
+
* when nobody holds the bytes; `payload_hash` is absent only when the execution
|
|
776
|
+
* recorded no binding at all. Which of the two a card SHOWS is
|
|
777
|
+
* {@link reviewPayloadView}'s decision, and the card says which.
|
|
699
778
|
*/
|
|
700
|
-
export type ReviewCardFields = Pick<ChannelRequest, "action_key" | "class" | "task" | "summary"> & Partial<Pick<ChannelRequest, "command_breakdown" | "gloss">>;
|
|
779
|
+
export type ReviewCardFields = Pick<ChannelRequest, "action_key" | "class" | "task" | "summary" | "fullPayload"> & Partial<Pick<ChannelRequest, "command_breakdown" | "gloss" | "payload_hash">>;
|
|
701
780
|
/** The rows a review card renders, in the order it renders them (APRV-299). */
|
|
702
781
|
export declare const REVIEW_CARD_ROWS: readonly ["class", "command_breakdown", "task", "summary", "gloss"];
|
|
703
782
|
export type ReviewCardRow = (typeof REVIEW_CARD_ROWS)[number];
|
|
@@ -726,6 +805,40 @@ export type ReviewCardRow = (typeof REVIEW_CARD_ROWS)[number];
|
|
|
726
805
|
* computed heading.
|
|
727
806
|
*/
|
|
728
807
|
export declare function renderTelegram(request: ChannelRequest, heading?: string, layout?: PromptLayout): TelegramRendering;
|
|
808
|
+
/**
|
|
809
|
+
* The settle edit of a minimal card (APRV-489): the outcome, the card's
|
|
810
|
+
* headline, the detail lines, and the collapsed block WHOLE.
|
|
811
|
+
*
|
|
812
|
+
* The edit replaces the only message that holds the canonical rendering, so
|
|
813
|
+
* the rendering is never what gives way to fit: the runtime's own detail lines
|
|
814
|
+
* are shortened instead, with a marker, and the outcome headline always stays.
|
|
815
|
+
* The card was at most 3800 characters with its quote box and
|
|
816
|
+
* deadline line, which the edit drops, so headline plus collapsed block always
|
|
817
|
+
* fit under {@link TELEGRAM_SETTLE_BUDGET} (the card budget is `MINIMAL_MESSAGE_BUDGET` in `telegram-minimal.ts`).
|
|
818
|
+
*/
|
|
819
|
+
export declare function minimalSettleText(outcome: string, detail: string[], card: {
|
|
820
|
+
headline: string;
|
|
821
|
+
details: string;
|
|
822
|
+
}): string;
|
|
823
|
+
/**
|
|
824
|
+
* The settle edit of last resort (fix round 2, S3): sent when Telegram refused
|
|
825
|
+
* the full settle edit of a minimal card, so a decided card never keeps looking
|
|
826
|
+
* undecided. The outcome, the request's key, the first detail line (who, when,
|
|
827
|
+
* which record) cut to fit, and where the rest is. Always under 1000 units.
|
|
828
|
+
*/
|
|
829
|
+
export declare function shortSettleText(outcome: string, actionKey: string, detail: string[]): string;
|
|
830
|
+
/**
|
|
831
|
+
* Whether a Bot API failure is a refusal of THIS message rather than a passing
|
|
832
|
+
* failure (fix round 2, S2): any 4xx but 429. A 429, a 5xx and a transport
|
|
833
|
+
* failure are retried by the dispatch cycle as before; a refusal never will
|
|
834
|
+
* succeed with the same text, so the minimal card gives way to the technical one.
|
|
835
|
+
*/
|
|
836
|
+
export declare function isMessageRefusal(cause: unknown): boolean;
|
|
837
|
+
/**
|
|
838
|
+
* The longest settle edit of a minimal card, in characters of HTML: under
|
|
839
|
+
* Telegram's 4096 with room for a relay's `Agent: <name>` line.
|
|
840
|
+
*/
|
|
841
|
+
export declare const TELEGRAM_SETTLE_BUDGET = 3990;
|
|
729
842
|
/**
|
|
730
843
|
* Split `text` so every chunk survives HTML escaping inside the message limit.
|
|
731
844
|
*
|
|
@@ -1017,6 +1130,13 @@ export declare const TELEGRAM_REVIEW_DENIED = "\u2717 REVIEWED \u2014 DENIED";
|
|
|
1017
1130
|
* been recorded.
|
|
1018
1131
|
*/
|
|
1019
1132
|
export declare const TELEGRAM_REVIEW_ARMED = "DENY ARMED \u2014 nothing is recorded yet";
|
|
1133
|
+
/**
|
|
1134
|
+
* The headline a card wears while a grade is held and no verdict has been
|
|
1135
|
+
* given (APRV-482). A grade is not a verdict: tapping one with nothing armed
|
|
1136
|
+
* records nothing, and the card keeps the grade so the OK or the Deny that
|
|
1137
|
+
* follows records it.
|
|
1138
|
+
*/
|
|
1139
|
+
export declare function telegramReviewGradeHeld(reaction: Reaction): string;
|
|
1020
1140
|
/**
|
|
1021
1141
|
* What a review tap's single answer says (APRV-302).
|
|
1022
1142
|
*
|
|
@@ -1032,8 +1152,25 @@ export declare const TELEGRAM_REVIEW_ARMED = "DENY ARMED \u2014 nothing is recor
|
|
|
1032
1152
|
export declare const TELEGRAM_REVIEW_ACK = "Heard \u2014 recording your review. The card will say what the log recorded.";
|
|
1033
1153
|
/** The toast a first Deny tap gets: it says plainly that nothing was written. */
|
|
1034
1154
|
export declare const TELEGRAM_REVIEW_ARM_TOAST = "Deny armed \u2014 nothing recorded. Tap Deny again to record it, or a reaction to record it with a grade.";
|
|
1155
|
+
/**
|
|
1156
|
+
* The card line and toast for a tap that would finish another account's
|
|
1157
|
+
* half-finished review (PR #614 refutation F3): an armed Deny or a held grade
|
|
1158
|
+
* belongs to the account that tapped it, and nothing is recorded. A held grade
|
|
1159
|
+
* is replaced by the tapper's own grade (a grade finishes nothing), which the
|
|
1160
|
+
* line says (PR #614 recheck NF-1, minor). Under 200 characters, the Bot API's
|
|
1161
|
+
* limit for a callback answer.
|
|
1162
|
+
*/
|
|
1163
|
+
export declare const TELEGRAM_REVIEW_OTHER_SENDER = "Another account's unfinished review (armed Deny or held grade) is on this card. Nothing was recorded. Tap your own grade to replace a held one; an armed Deny is theirs to finish.";
|
|
1035
1164
|
/** The toast a reaction that needs the human's own words gets. */
|
|
1036
1165
|
export declare const TELEGRAM_REVIEW_NOTE_TOAST = "Heard \u2014 reply to the prompt with why. Nothing is recorded until it arrives.";
|
|
1166
|
+
/**
|
|
1167
|
+
* The card line for a note prompt that could not be sent (APRV-492). The toast
|
|
1168
|
+
* went out before the send and told the human to reply to a prompt, so the
|
|
1169
|
+
* card says the prompt is not coming and how to be asked again: a grade tap is
|
|
1170
|
+
* what the relay licenses a prompt for. With Deny armed the grade alone asks;
|
|
1171
|
+
* otherwise the grade is held and OK asks.
|
|
1172
|
+
*/
|
|
1173
|
+
export declare function telegramReviewNoteUnasked(verdict: ReviewVerdict): string;
|
|
1037
1174
|
/**
|
|
1038
1175
|
* What the ForceReply prompt asks for.
|
|
1039
1176
|
*
|
|
@@ -1057,7 +1194,14 @@ export declare function reviewNotePromptLines(reaction: Reaction, verdict: Revie
|
|
|
1057
1194
|
export interface ReviewTap {
|
|
1058
1195
|
/** `seq` of the `audit.sampled` record this card was drawn for. */
|
|
1059
1196
|
sampleSeq: number;
|
|
1060
|
-
|
|
1197
|
+
/**
|
|
1198
|
+
* The verdict the taps added up to, or absent when the human gave none
|
|
1199
|
+
* (APRV-482): a grade tapped with nothing armed. The channel still hands that
|
|
1200
|
+
* tap to the runtime, which refuses it `verdict-required`, so the refusal a
|
|
1201
|
+
* reviewer reads is the core's own and no channel decides which gestures are
|
|
1202
|
+
* approvals.
|
|
1203
|
+
*/
|
|
1204
|
+
verdict?: ReviewVerdict;
|
|
1061
1205
|
/** The grade, when the human gave one. Absent means absent. */
|
|
1062
1206
|
reaction?: Reaction;
|
|
1063
1207
|
/** The human's words, when a note prompt collected any. */
|
|
@@ -1074,6 +1218,14 @@ export interface ReviewTap {
|
|
|
1074
1218
|
* policy, refuses an account it does not name, and records the one it does.
|
|
1075
1219
|
*/
|
|
1076
1220
|
sender?: ChannelSender;
|
|
1221
|
+
/**
|
|
1222
|
+
* The payload binding this card showed WHOLE (APRV-481), present only when
|
|
1223
|
+
* {@link reviewPayloadView} put the bytes on the screen. Set by the channel
|
|
1224
|
+
* from the card it is holding, never from anything the network sent; the
|
|
1225
|
+
* runtime checks it against the log before recording it as the review's
|
|
1226
|
+
* `payload_hash`.
|
|
1227
|
+
*/
|
|
1228
|
+
payloadHash?: string;
|
|
1077
1229
|
}
|
|
1078
1230
|
/** What the runtime did with a review tap, as it reports it back. */
|
|
1079
1231
|
export interface ReviewTapResponse {
|
|
@@ -1085,6 +1237,15 @@ export interface ReviewTapResponse {
|
|
|
1085
1237
|
detail: string[];
|
|
1086
1238
|
/** The toast, which Telegram caps at a short sentence. */
|
|
1087
1239
|
toast: string;
|
|
1240
|
+
/**
|
|
1241
|
+
* The refusal code, when the runtime refused (PR #614 refutation N6/F3).
|
|
1242
|
+
* The card holds a lone grade only when this is `verdict-required`: the
|
|
1243
|
+
* runtime judges the verdict after the sender and the roster, so that code
|
|
1244
|
+
* says the tapper may review and lacks only the verdict. A grade refused for
|
|
1245
|
+
* anything else (an unmapped sender, a reviewer off the roster) is not held,
|
|
1246
|
+
* so it cannot block the next account's tap.
|
|
1247
|
+
*/
|
|
1248
|
+
code?: string;
|
|
1088
1249
|
}
|
|
1089
1250
|
export type ReviewTapHandler = (tap: ReviewTap) => ReviewTapResponse | Promise<ReviewTapResponse>;
|
|
1090
1251
|
/**
|
|
@@ -1130,6 +1291,22 @@ export interface ReviewCardState {
|
|
|
1130
1291
|
* whose next Deny tap arms again.
|
|
1131
1292
|
*/
|
|
1132
1293
|
denyArmed: boolean;
|
|
1294
|
+
/**
|
|
1295
|
+
* A grade tapped before any verdict (APRV-482), held so the OK or the second
|
|
1296
|
+
* Deny that follows records it. **Process memory**, like the arming: it
|
|
1297
|
+
* appends nothing, the card states it, and losing it to a restart costs a tap.
|
|
1298
|
+
*/
|
|
1299
|
+
heldReaction: Reaction | null;
|
|
1300
|
+
/**
|
|
1301
|
+
* Who armed Deny and who holds the grade, as the transport attributed the tap
|
|
1302
|
+
* (PR #614 refutation F3): the sender's id, or `null` for a tap that carried
|
|
1303
|
+
* none. A held grade or an armed Deny is one person's half-finished review,
|
|
1304
|
+
* so it rides only on that person's next verdict tap; another account's tap
|
|
1305
|
+
* is refused on the card rather than recording someone else's grade or
|
|
1306
|
+
* denial under its own name. Process memory, cleared with the state it owns.
|
|
1307
|
+
*/
|
|
1308
|
+
armedBy: string | null;
|
|
1309
|
+
heldBy: string | null;
|
|
1133
1310
|
/**
|
|
1134
1311
|
* The outcome, once the runtime has recorded one. Written only from the
|
|
1135
1312
|
* handler's answer, never inferred here.
|
|
@@ -1198,6 +1375,105 @@ export declare function parseReviewCallback(data: unknown): {
|
|
|
1198
1375
|
nonce: string;
|
|
1199
1376
|
choice: ReviewChoice;
|
|
1200
1377
|
} | null;
|
|
1378
|
+
/**
|
|
1379
|
+
* How much payload, in ESCAPED characters, a review card carries whole
|
|
1380
|
+
* (APRV-480).
|
|
1381
|
+
*
|
|
1382
|
+
* A card is one message edited in place, so it cannot spill the payload over
|
|
1383
|
+
* several messages the way a prompt does: the edit that settles it replaces one
|
|
1384
|
+
* message, and a second message holding the rest of the bytes would outlive the
|
|
1385
|
+
* card it belonged to. So the bytes are shown whole or not at all. Never cut:
|
|
1386
|
+
* a review that recorded a payload hash over bytes the reviewer saw half of
|
|
1387
|
+
* would claim more than they read. This is the CEILING; the budget a given
|
|
1388
|
+
* card gets is {@link reviewPayloadBudget}, which is this or the headroom the
|
|
1389
|
+
* card's own rows leave under {@link TELEGRAM_MAX_MESSAGE_CHARS}, whichever is
|
|
1390
|
+
* smaller (PR #614 refutation F4).
|
|
1391
|
+
*/
|
|
1392
|
+
export declare const REVIEW_PAYLOAD_BUDGET = 2000;
|
|
1393
|
+
/**
|
|
1394
|
+
* The longest agent-written summary a review card shows, in characters (PR
|
|
1395
|
+
* #614 refutation F4). The summary is the requesting party's own claim and its
|
|
1396
|
+
* schema sets no length, so before this cap an agent could write a summary
|
|
1397
|
+
* that pushed the card past Telegram's limit, which failed the send and held
|
|
1398
|
+
* the review queue. Longer text is cut with a marker saying how much is not
|
|
1399
|
+
* shown; the registration record keeps it whole.
|
|
1400
|
+
*/
|
|
1401
|
+
export declare const REVIEW_SUMMARY_MAX = 400;
|
|
1402
|
+
/**
|
|
1403
|
+
* The most characters a review card's notice region (a refusal, or the arming
|
|
1404
|
+
* line) shows, all its lines together (PR #614 refutation F4). Each line was
|
|
1405
|
+
* already cut at {@link REVIEW_NOTICE_MAX}; the region had no total, so a
|
|
1406
|
+
* refusal with several long lines could take the card over the limit and make
|
|
1407
|
+
* the redraw that carries it fail. The whole refusal is on the listener's
|
|
1408
|
+
* stderr.
|
|
1409
|
+
*/
|
|
1410
|
+
export declare const REVIEW_NOTICE_REGION_MAX = 600;
|
|
1411
|
+
/**
|
|
1412
|
+
* The visible length of HTML this channel wrote: tags removed and each entity
|
|
1413
|
+
* {@link escapeHtml} produces counted as the one character it stands for.
|
|
1414
|
+
* Telegram measures its limit after entity parsing, so this is the number the
|
|
1415
|
+
* limit applies to.
|
|
1416
|
+
*/
|
|
1417
|
+
export declare function telegramVisibleLength(html: string): number;
|
|
1418
|
+
/**
|
|
1419
|
+
* The longest payload text, in characters, a review card will even try to
|
|
1420
|
+
* render (APRV-480 refutation: review-delivery resource cap).
|
|
1421
|
+
*
|
|
1422
|
+
* Supervised payloads carry no size cap of their own (`PROPOSE_PAYLOAD_MAX_BYTES`
|
|
1423
|
+
* binds proposals only), and the party under review writes them. Without this
|
|
1424
|
+
* bound every render of a card, every redraw after a tap, and every tap's
|
|
1425
|
+
* {@link reviewPayloadView} would run the canonical renderer and HTML escaping
|
|
1426
|
+
* over the whole payload only to conclude "too long", so an agent could make
|
|
1427
|
+
* each review gesture cost work proportional to bytes it chose. Text past this
|
|
1428
|
+
* length is `too-long` without being rendered at all; the card builder
|
|
1429
|
+
* (`cli/audit-card.ts`) keeps no more than this much text in the card it holds.
|
|
1430
|
+
* Eight times the card budget, because a canonical rendering is never that much
|
|
1431
|
+
* shorter than the JSON it renders, and erring here only ever shows the hash.
|
|
1432
|
+
*/
|
|
1433
|
+
export declare const REVIEW_RENDER_INPUT_MAX: number;
|
|
1434
|
+
/** The heading over a payload the card shows whole (APRV-480). */
|
|
1435
|
+
export declare const TELEGRAM_REVIEW_PAYLOAD_BYTES = "PAYLOAD \u2014 the bytes that ran, shown whole; this review covers them";
|
|
1436
|
+
/** The heading over a payload the card names by hash only (APRV-480). */
|
|
1437
|
+
export declare const TELEGRAM_REVIEW_PAYLOAD_HASH_ONLY = "PAYLOAD \u2014 NOT SHOWN, hash only; this review does not cover the bytes";
|
|
1438
|
+
/** The heading on a card whose execution recorded no payload binding (APRV-480). */
|
|
1439
|
+
export declare const TELEGRAM_REVIEW_PAYLOAD_NONE = "PAYLOAD \u2014 none recorded; the execution bound to no payload hash";
|
|
1440
|
+
/**
|
|
1441
|
+
* What a review card shows of the payload, decided once from the card alone
|
|
1442
|
+
* (APRV-480).
|
|
1443
|
+
*
|
|
1444
|
+
* - `bytes`: the canonical rendering, whole. Only this case lets a review
|
|
1445
|
+
* record a payload hash (APRV-481), because only here did the reviewer read
|
|
1446
|
+
* the bytes the hash names.
|
|
1447
|
+
* - `hash`: the binding, and the reason the bytes are not on the card: nobody
|
|
1448
|
+
* holds them (`unavailable`), or they are longer than one card carries
|
|
1449
|
+
* (`too-long`). The card says so in its heading.
|
|
1450
|
+
* - `none`: the execution recorded no binding, so there is nothing to show and
|
|
1451
|
+
* nothing to name.
|
|
1452
|
+
*
|
|
1453
|
+
* Pure and deterministic over the card, so the rendering and the tap that
|
|
1454
|
+
* follows it can never disagree about which case was on the screen.
|
|
1455
|
+
*/
|
|
1456
|
+
export type ReviewPayloadView = {
|
|
1457
|
+
kind: "bytes";
|
|
1458
|
+
hash: string;
|
|
1459
|
+
text: string;
|
|
1460
|
+
} | {
|
|
1461
|
+
kind: "hash";
|
|
1462
|
+
hash: string;
|
|
1463
|
+
reason: "unavailable" | "too-long";
|
|
1464
|
+
} | {
|
|
1465
|
+
kind: "none";
|
|
1466
|
+
};
|
|
1467
|
+
export declare function reviewPayloadView(card: ReviewCard): ReviewPayloadView;
|
|
1468
|
+
/**
|
|
1469
|
+
* How many escaped characters of payload this card can show whole (PR #614
|
|
1470
|
+
* refutation F4): {@link REVIEW_PAYLOAD_BUDGET}, or the room the card's own
|
|
1471
|
+
* rows leave under {@link TELEGRAM_MAX_MESSAGE_CHARS} after the longest heading,
|
|
1472
|
+
* the payload heading and the largest notice a tap can add, whichever is
|
|
1473
|
+
* smaller. Pure over the card, so the view a card is drawn with and the view a
|
|
1474
|
+
* tap records are the same view.
|
|
1475
|
+
*/
|
|
1476
|
+
export declare function reviewPayloadBudget(card: ReviewCard): number;
|
|
1201
1477
|
/**
|
|
1202
1478
|
* The card's message: the rows, whatever notice the last tap produced, and the
|
|
1203
1479
|
* keyboard.
|
|
@@ -1210,13 +1486,15 @@ export declare function parseReviewCallback(data: unknown): {
|
|
|
1210
1486
|
* every card said the same thing to a reader who had already read them once, and
|
|
1211
1487
|
* pushed the rows a review is actually about off the first screen.
|
|
1212
1488
|
*
|
|
1213
|
-
* Pure.
|
|
1214
|
-
*
|
|
1215
|
-
*
|
|
1216
|
-
*
|
|
1217
|
-
*
|
|
1218
|
-
*
|
|
1219
|
-
*
|
|
1489
|
+
* Pure. It carries no approve button: the action ran, and a card that offered
|
|
1490
|
+
* an approve would be presenting a settled fact as a live authorization. A
|
|
1491
|
+
* sample is never delivered as an approval request and never accepts a token.
|
|
1492
|
+
*
|
|
1493
|
+
* It DOES carry a payload region since APRV-480, between the computed rows and
|
|
1494
|
+
* the claimed ones, exactly where a prompt puts its own. Under supervised-retro
|
|
1495
|
+
* the review is the individual approval, so the reviewer reads the bytes that
|
|
1496
|
+
* ran, or is told plainly that the card shows only their hash and why
|
|
1497
|
+
* ({@link reviewPayloadView}). The heading says which, every time.
|
|
1220
1498
|
*/
|
|
1221
1499
|
export declare function renderReviewCard(state: ReviewCardState): {
|
|
1222
1500
|
text: string;
|
|
@@ -1239,6 +1517,13 @@ export declare class TelegramApiError extends Error {
|
|
|
1239
1517
|
* JSON, or carried no description.
|
|
1240
1518
|
*/
|
|
1241
1519
|
readonly description: string | null;
|
|
1520
|
+
/**
|
|
1521
|
+
* The Bot API's `parameters.retry_after`, in seconds, when the failure
|
|
1522
|
+
* carried one (PR #614 recheck NF-2). Telegram sends it with 429 "Too Many
|
|
1523
|
+
* Requests" and means it: a send before it elapses is refused again.
|
|
1524
|
+
* `null` when the body named none.
|
|
1525
|
+
*/
|
|
1526
|
+
readonly retryAfterSeconds: number | null;
|
|
1242
1527
|
constructor(message: string, method: string,
|
|
1243
1528
|
/**
|
|
1244
1529
|
* The HTTP status, when the failure was an HTTP one. `null` for a transport
|
|
@@ -1251,8 +1536,32 @@ export declare class TelegramApiError extends Error {
|
|
|
1251
1536
|
* error body carried one. `null` when the body was absent, unreadable, not
|
|
1252
1537
|
* JSON, or carried no description.
|
|
1253
1538
|
*/
|
|
1254
|
-
description?: string | null
|
|
1539
|
+
description?: string | null,
|
|
1540
|
+
/**
|
|
1541
|
+
* The Bot API's `parameters.retry_after`, in seconds, when the failure
|
|
1542
|
+
* carried one (PR #614 recheck NF-2). Telegram sends it with 429 "Too Many
|
|
1543
|
+
* Requests" and means it: a send before it elapses is refused again.
|
|
1544
|
+
* `null` when the body named none.
|
|
1545
|
+
*/
|
|
1546
|
+
retryAfterSeconds?: number | null);
|
|
1255
1547
|
}
|
|
1548
|
+
/**
|
|
1549
|
+
* Whether a failed send is the Bot API refusing the message itself, so that
|
|
1550
|
+
* sending the same message again can only fail again (PR #614 recheck NF-2).
|
|
1551
|
+
*
|
|
1552
|
+
* Only an HTTP 400 whose description matches
|
|
1553
|
+
* {@link TELEGRAM_DETERMINISTIC_SEND_REFUSALS} qualifies. Anything else, an
|
|
1554
|
+
* error that is not a {@link TelegramApiError} included, is treated as
|
|
1555
|
+
* transient, which is the direction that keeps a card in front of the
|
|
1556
|
+
* approver: a transient failure misread as deterministic hides a sample from
|
|
1557
|
+
* the phone, and the converse costs a bounded number of retries.
|
|
1558
|
+
*/
|
|
1559
|
+
export declare function isDeterministicSendRefusal(cause: unknown): boolean;
|
|
1560
|
+
/**
|
|
1561
|
+
* The wait, in milliseconds, a failed call's `retry_after` asks for, or `null`
|
|
1562
|
+
* when it named none (PR #614 recheck NF-2).
|
|
1563
|
+
*/
|
|
1564
|
+
export declare function retryAfterMsOf(cause: unknown): number | null;
|
|
1256
1565
|
/**
|
|
1257
1566
|
* Whether a failed call is the Bot API saying an edit changed nothing
|
|
1258
1567
|
* (APRV-277).
|
|
@@ -1302,6 +1611,17 @@ export interface TelegramPollResult {
|
|
|
1302
1611
|
tap: ReviewTap;
|
|
1303
1612
|
ok: boolean;
|
|
1304
1613
|
}[];
|
|
1614
|
+
/**
|
|
1615
|
+
* Decision taps the channel itself refused before any handler ran (APRV-456),
|
|
1616
|
+
* in order. `action_key` is the action this process holds open for the
|
|
1617
|
+
* reference the tap carried, or `null` when it holds nothing for it; only a
|
|
1618
|
+
* non-null one can have produced an `audit.decision_refused`.
|
|
1619
|
+
*/
|
|
1620
|
+
refused: {
|
|
1621
|
+
code: ChannelDecisionRefusal["code"];
|
|
1622
|
+
action_key: string | null;
|
|
1623
|
+
detail: string;
|
|
1624
|
+
}[];
|
|
1305
1625
|
}
|
|
1306
1626
|
/**
|
|
1307
1627
|
* How updates reach this channel (APRV-424).
|
|
@@ -1414,6 +1734,12 @@ export declare class TelegramChannel implements TestableChannel {
|
|
|
1414
1734
|
private readonly describeAction;
|
|
1415
1735
|
/** The policy's row layout for this channel (APRV-218). Read-only, and pure input to the renderer. */
|
|
1416
1736
|
private readonly layout;
|
|
1737
|
+
/** The policy's prompt style for this channel (APRV-489). */
|
|
1738
|
+
private readonly promptStyle;
|
|
1739
|
+
/** The policy's `say` declarations (APRV-489). Pure input to the minimal renderer. */
|
|
1740
|
+
private readonly say;
|
|
1741
|
+
/** The per-delivery resolver (fix round 2, S4), or null for the fixed values above. */
|
|
1742
|
+
private readonly promptFor;
|
|
1417
1743
|
/** When {@link sweep} last ran, so the poll loop can call it every cycle. */
|
|
1418
1744
|
private lastSweepMs;
|
|
1419
1745
|
/**
|
|
@@ -1423,6 +1749,25 @@ export declare class TelegramChannel implements TestableChannel {
|
|
|
1423
1749
|
*/
|
|
1424
1750
|
private ack;
|
|
1425
1751
|
private handler;
|
|
1752
|
+
/**
|
|
1753
|
+
* APRV-196's stale-copy fallback, on or off (APRV-456). Fixed at
|
|
1754
|
+
* construction from {@link TelegramConfig.staleCopy}; nothing an update
|
|
1755
|
+
* carries can change it.
|
|
1756
|
+
*/
|
|
1757
|
+
private readonly staleCopy;
|
|
1758
|
+
/**
|
|
1759
|
+
* Where a `nonce-not-issued` refusal is recorded (APRV-456). The runtime's,
|
|
1760
|
+
* like every other handler here; absent, the refusal still happens and only
|
|
1761
|
+
* the record is missing.
|
|
1762
|
+
*/
|
|
1763
|
+
private refusedDecisionHandler;
|
|
1764
|
+
/**
|
|
1765
|
+
* Nonces already refused `nonce-not-issued` (APRV-456), oldest first, capped
|
|
1766
|
+
* at {@link TELEGRAM_REFUSED_NONCE_CAP}. A redelivered or replayed tap on the
|
|
1767
|
+
* same button is refused again with the same toast and records nothing, so
|
|
1768
|
+
* one button costs the log at most one record.
|
|
1769
|
+
*/
|
|
1770
|
+
private readonly unissuedRefused;
|
|
1426
1771
|
/**
|
|
1427
1772
|
* What to do with a bot command (APRV-216). Absent unless the runtime asked
|
|
1428
1773
|
* for commands, and its absence is what keeps `message` out of
|
|
@@ -1527,6 +1872,21 @@ export declare class TelegramChannel implements TestableChannel {
|
|
|
1527
1872
|
private readonly counters;
|
|
1528
1873
|
constructor(config: TelegramConfig);
|
|
1529
1874
|
onDecision(handler: (decision: ChannelDecision) => DecisionOutcome): void;
|
|
1875
|
+
/**
|
|
1876
|
+
* Register where a tap this channel refused on its own is recorded
|
|
1877
|
+
* (APRV-456).
|
|
1878
|
+
*
|
|
1879
|
+
* Separate from {@link onDecision} on purpose: that handler's job is to call
|
|
1880
|
+
* the gate, and a `nonce-not-issued` tap must never reach the gate. The
|
|
1881
|
+
* runtime wires this to an audit-tier append that grants nothing.
|
|
1882
|
+
*/
|
|
1883
|
+
onRefusedDecision(handler: (refused: TelegramRefusedDecision) => void): void;
|
|
1884
|
+
/**
|
|
1885
|
+
* Whether APRV-196's stale-copy fallback is on (APRV-456). Read by the
|
|
1886
|
+
* listener for the restart banner, which must not promise that earlier
|
|
1887
|
+
* copies still decide when they do not.
|
|
1888
|
+
*/
|
|
1889
|
+
staleCopyEnabled(): boolean;
|
|
1530
1890
|
/**
|
|
1531
1891
|
* Register what to do with a checkpoint tap (APRV-257).
|
|
1532
1892
|
*
|
|
@@ -1569,8 +1929,9 @@ export declare class TelegramChannel implements TestableChannel {
|
|
|
1569
1929
|
* A unit like a checkpoint prompt: one thing to read, never grouped into a
|
|
1570
1930
|
* digest, and never delivered through {@link notify} — a digest is a set of
|
|
1571
1931
|
* similar pending REQUESTS decided together, and a sample is neither pending
|
|
1572
|
-
* nor a request. It sends ONE message:
|
|
1573
|
-
*
|
|
1932
|
+
* nor a request. It sends ONE message: the rows, the payload whole or by hash
|
|
1933
|
+
* (APRV-480, never split across messages), and a keyboard whose six buttons
|
|
1934
|
+
* collect a verdict and a grade and mint nothing.
|
|
1574
1935
|
*
|
|
1575
1936
|
* Refuses when no handler is registered, rather than sending a dead button.
|
|
1576
1937
|
*/
|
|
@@ -1666,6 +2027,28 @@ export declare class TelegramChannel implements TestableChannel {
|
|
|
1666
2027
|
*/
|
|
1667
2028
|
private sendPrompt;
|
|
1668
2029
|
private deliverOne;
|
|
2030
|
+
/**
|
|
2031
|
+
* Send `request` as ONE minimal card (APRV-489), or report why it was not.
|
|
2032
|
+
*
|
|
2033
|
+
* The card is drawn from the same `renderTelegram` output the technical card
|
|
2034
|
+
* would send, under the same layout, so the collapsed block IS the technical
|
|
2035
|
+
* card. The buttons carry the same callback data as the technical card's; only
|
|
2036
|
+
* the reject label reads "Deny" (R7). Nothing is sent when the card is refused,
|
|
2037
|
+
* so the caller's technical send is the only send.
|
|
2038
|
+
*/
|
|
2039
|
+
private sendMinimal;
|
|
2040
|
+
/**
|
|
2041
|
+
* The style and `say` map in force for the delivery about to be drawn (fix
|
|
2042
|
+
* round 2, S4): the resolver's answer when the verb gave one, else the fixed
|
|
2043
|
+
* configuration. A resolver that throws is `technical`, the side that shows more.
|
|
2044
|
+
*/
|
|
2045
|
+
private currentPrompt;
|
|
2046
|
+
/**
|
|
2047
|
+
* What a decision on a digest records about its layout (APRV-489): nothing
|
|
2048
|
+
* under a technical policy, and the technical card with its reason under a
|
|
2049
|
+
* minimal one, since a digest is always drawn technical.
|
|
2050
|
+
*/
|
|
2051
|
+
private digestRendering;
|
|
1669
2052
|
/**
|
|
1670
2053
|
* Forget every nonce issued for `deliveryId`, and report the action key it
|
|
1671
2054
|
* was issued for.
|
|
@@ -2113,9 +2496,32 @@ export declare class TelegramChannel implements TestableChannel {
|
|
|
2113
2496
|
* stays open, and a fresh card is offered — and can never cost a record
|
|
2114
2497
|
* nobody asked for.
|
|
2115
2498
|
*
|
|
2116
|
-
*
|
|
2117
|
-
*
|
|
2118
|
-
* nowhere rather than recording a grade the human moved on from.
|
|
2499
|
+
* Only one grade can be outstanding on one card, so a second prompt that is
|
|
2500
|
+
* SENT replaces the first, and the older prompt stops resolving: a late reply
|
|
2501
|
+
* to it lands nowhere rather than recording a grade the human moved on from.
|
|
2502
|
+
*
|
|
2503
|
+
* APRV-492, and the relay licenses one prompt per GRADE tap, so which taps
|
|
2504
|
+
* get a prompt sent is not this method's choice alone. Three rules:
|
|
2505
|
+
*
|
|
2506
|
+
* - A tap that would ask the SAME question again (same verdict, same grade,
|
|
2507
|
+
* same account: a doubled OK or a doubled second Deny) sends nothing. The
|
|
2508
|
+
* prompt on screen already collects those words, and a second send would
|
|
2509
|
+
* be refused.
|
|
2510
|
+
* - A prompt for a DIFFERENT VERDICT retires the old one BEFORE the send. A
|
|
2511
|
+
* verdict change is not a grade tap, so the relay refuses that send, and an
|
|
2512
|
+
* old prompt left live would record the verdict the human just changed. A
|
|
2513
|
+
* note may be lost this way; a wrong verdict is never recorded.
|
|
2514
|
+
* - A prompt for the same verdict and a different grade follows a grade tap,
|
|
2515
|
+
* which the relay licenses, and retires the old one only once the new send
|
|
2516
|
+
* has succeeded. A send that fails anyway (the network, not the relay)
|
|
2517
|
+
* leaves the old prompt live, and a reply to it records the grade the old
|
|
2518
|
+
* prompt names in its own text, not the one the card now holds: an
|
|
2519
|
+
* accepted residual (PR #619 refutation S2).
|
|
2520
|
+
*
|
|
2521
|
+
* A send that fails says so on the card, because the toast already told the
|
|
2522
|
+
* human to reply to a prompt: the note was not asked for, nothing was
|
|
2523
|
+
* recorded, and a grade tap asks again. Then it throws, so the operator hears
|
|
2524
|
+
* of it. A later prompt that is sent clears that line.
|
|
2119
2525
|
*/
|
|
2120
2526
|
private askForNote;
|
|
2121
2527
|
/**
|
|
@@ -2145,6 +2551,35 @@ export declare class TelegramChannel implements TestableChannel {
|
|
|
2145
2551
|
private recordReview;
|
|
2146
2552
|
/** One `editMessageText` that replaces a review card's text and its keyboard. */
|
|
2147
2553
|
private redrawReview;
|
|
2554
|
+
/**
|
|
2555
|
+
* Refuse a decision tap whose nonce this process is not holding, with the
|
|
2556
|
+
* stale-copy fallback off (APRV-456).
|
|
2557
|
+
*
|
|
2558
|
+
* What it does, in order, and what it never does:
|
|
2559
|
+
*
|
|
2560
|
+
* - It never calls the decision handler, so the gate is never asked. That is
|
|
2561
|
+
* the property: a button this process did not put on the screen cannot
|
|
2562
|
+
* carry a human's tap to `decide()`, whatever reference it carries.
|
|
2563
|
+
* - It looks the reference up among the deliveries THIS process holds open,
|
|
2564
|
+
* and only to name the action on the audit record. A reference that names
|
|
2565
|
+
* nothing open here produces no record: there is no action key this
|
|
2566
|
+
* process could truthfully write, and inventing one from bytes that came
|
|
2567
|
+
* off the network is the self-report §11.1 invariant 4 forbids.
|
|
2568
|
+
* - It hands at most one record per nonce to {@link onRefusedDecision}: a
|
|
2569
|
+
* redelivered or replayed tap on the same button is refused with the same
|
|
2570
|
+
* toast and records nothing.
|
|
2571
|
+
* - It leaves the live card alone. The request stays pending, and the card
|
|
2572
|
+
* this process did send still decides it.
|
|
2573
|
+
*
|
|
2574
|
+
* "Not holding" covers a nonce this process never issued and one it issued
|
|
2575
|
+
* and has since forgotten (an annotated card, a swept delivery, a restart).
|
|
2576
|
+
* It does not try to tell them apart: a set of every nonce ever issued would
|
|
2577
|
+
* reopen the hole for anyone who has seen one genuine button's bytes, because
|
|
2578
|
+
* an old nonce with a new reference would pass it. The cost is that a tap on
|
|
2579
|
+
* an earlier copy after a restart is refused on this shape and the approver
|
|
2580
|
+
* taps the newest card, which the restart banner says.
|
|
2581
|
+
*/
|
|
2582
|
+
private refuseUnissued;
|
|
2148
2583
|
private ignore;
|
|
2149
2584
|
private answer;
|
|
2150
2585
|
/**
|