feat(cli): S4 follow-up, refusal backoff and tracker boot (row 45, #1527)
Rocko's round 2 candidate, packet agents/rocko/work/s4-follow-up/
(build.patch c8cec070, candidate manifest 5b067a9d, 8/8 OK).
- Definite DM refusals wait the full 30-minute cap, counted from the
journal's last refusal, so five refusals span about two hours before
gave-up (lead decision 73). Unknown outcomes keep doubling.
- Broker close sends at host.mjs:144/150/184/188 pass a callback, which
closes the EPIPE window both reviewers found in round 1.
- README documents manual recovery for an open decision.
- trackers-boot test, X9, X14, and Darkwing's round 1 notes 1-4.
- The append type check stays out; Rocko's reason holds (both reviewers
agree).
Reviews: Darkwing approve (comment 26884), Filbert approve (26886).
Landing gate on b13fef4c plus the patch: every node suite and every
scripts/test-*.sh green, test-task 98/0.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
+30
-9
@@ -145,9 +145,10 @@ inbox through the reader capability and does two things:
|
||||
- **Digest.** It sends one digest a day at 08:00 America/Chicago. The hour
|
||||
comes from the IANA zone, so daylight saving time is handled. If the host
|
||||
starts after 08:00 and the day has no digest yet, the digest goes at once.
|
||||
The digest lists the inbox and marks each blocking decision as DM sent or
|
||||
DM pending. An empty inbox gets one line. Each message stays within
|
||||
Discord's 2000 characters.
|
||||
The digest lists the inbox. It marks each blocking decision with one of
|
||||
three states: "DM sent", "DM pending" or "DM refused, not retried". An
|
||||
empty inbox gets one line. Each message stays within Discord's 2000
|
||||
characters.
|
||||
|
||||
The notifier only reads the bus. Its memory is the journal
|
||||
`<dataRoot>/notify/<business>/sent.jsonl` (directory 0700, file 0600), with
|
||||
@@ -160,21 +161,41 @@ one line per send attempt:
|
||||
|
||||
- A decision, or a day's digest, counts as sent once it has a `confirmed`
|
||||
line.
|
||||
- A `refused` or `unknown` send is retried. The wait starts at 30 s and
|
||||
doubles up to 30 min. A duplicate costs less than a miss. Every retry
|
||||
- A `refused` or `unknown` send is retried. After an `unknown` outcome the
|
||||
wait starts at 30 s and doubles up to 30 min. A duplicate costs less than
|
||||
a miss. Every retry
|
||||
carries the same Discord nonce, so a retry inside Discord's dedupe window
|
||||
returns the first message. A DM's nonce comes from the decision id. A
|
||||
digest's comes from the business and the day.
|
||||
- A definite refusal is a DM refused with an HTTP 4xx other than 429. The
|
||||
next attempt waits the full 30 min, counted from the refusal's `at` in
|
||||
the journal, so a restart does not shorten it. After 5 of them for one
|
||||
decision, at least 2 hours apart end to end, the notifier appends one
|
||||
`gave-up` line, logs it once and never sends that DM again. The digest
|
||||
marks the decision "DM refused, not retried". The count comes from the
|
||||
journal, so a restart keeps it. An `unknown` outcome (network, 5xx or
|
||||
429) retries without a limit (lead decisions 72 and 73).
|
||||
- Recovery after a give-up is manual. Fixing the binding does not resend
|
||||
the DM, which stays given up. The decision stays open in `mosaic inbox`,
|
||||
the digest lists it, and the operator decides it with `mosaic decide`.
|
||||
- On open, a final line without its newline is a torn write. The notifier
|
||||
copies those bytes to `torn-<UTC stamp>.bin` in the same directory (0600,
|
||||
a new file, synced), then truncates `sent.jsonl` to its last newline and
|
||||
syncs it. It logs both steps. A crash between the two leaves the torn
|
||||
tail in place, and the next open repairs it with a second copy. The next
|
||||
append therefore starts on a line of its own.
|
||||
- A malformed complete line refuses with exit 3 and changes nothing.
|
||||
- The journal refuses with exit 3 when its directory is looser than 0700 or
|
||||
not yours, when `sent.jsonl` is a symlink, or when the file is not a
|
||||
regular 0600 file you own.
|
||||
- A malformed complete line refuses with exit 3 and changes nothing. Each
|
||||
line is type-checked: `at` an ISO timestamp, `kind` `dm` or `digest`,
|
||||
`decision` a string (required for `dm`, null for `digest`), `day` a real
|
||||
`YYYY-MM-DD` date (required for `digest`), `outcome` one of `confirmed`,
|
||||
`refused`, `unknown` or `gave-up` (`gave-up` only for `dm`), `messageId`
|
||||
a string (required when confirmed) or null, `status` an integer when
|
||||
present.
|
||||
- The journal refuses with exit 3 when its directory is looser than 0700,
|
||||
not yours, a symlink (dangling or not) or not writable, or cannot be
|
||||
created (for example, a parent is a file). It also refuses when
|
||||
`sent.jsonl` is a symlink, a directory or not writable, or is not a
|
||||
regular 0600 file you own. The message names the path.
|
||||
- No Discord channel or user id goes in the journal, a log line or an
|
||||
error.
|
||||
|
||||
|
||||
Reference in New Issue
Block a user