feat(mailbox): let a requester withdraw its own ask, with an explicit marker
RFC 003 §3.3 clause 3 clears an ask only on "no event OF YOURS", and the skill states the consequence outright: "there is nothing anyone can do about it from the other end". That asymmetry is deliberate and mostly right — owed-ness is a statement about the RECIPIENT's accountability. It is wrong in exactly one case: the requester retracting its own ask. MEASURED COST. pi@mbp-m1-2020 withdrew a v1.8.13 rollout ask to pi@tor-ms22 at seq 119 — terminal `superseded`, same correlation_id, metadata.closes naming the thread, body "DO NOT SPEND A MINUTE ON v1.8.13" — and recorded it as done. It had no effect: seq 119's from_agent is mbp, so it could never satisfy a join that only inspects tor-ms22's own events. tor-ms22's next wake-up, 8h later and on the first boot of the image shipping this very derivation, still listed the ask as owed, 41h old, for a release it never installed. The failure is invisible from the sender's side, which is why it went unnoticed for 41h. WHY AN EXPLICIT MARKER RATHER THAN "ANY TERMINAL EVENT FROM THE REQUESTER". This file's standing rule is that every failure stays on the noisy-but-visible side: a resurfacing item costs one turn of human correction, a suppressed unanswered ask is silent and permanent. Under the naive rule a requester appending `applied` for its own bookkeeping — on the correlation, addressed to me, before I ever replied — would silently delete a real obligation. So release must be STATED: metadata.withdraws (canonical) or metadata.closes (already this fleet's de-facto marker), whose value must NAME the ask — its correlation_id or its event id. Prose does not count. Five further guards, each pinned by a mutation test: only the original requester; addressed to this device exactly, never '*'; terminal status; strictly after the ask by hlc; and joined by ack_of or correlation_id. This does NOT break the fixed point in §9.2. That section rejects letting terminal directed events into the owed set, because then every closure mints a fresh obligation. That is about CANDIDATES; this adds a CLEARER. A withdrawal is terminal, so it can never be a candidate, and the asserting shape (open) and the clearing shape (terminal) stay disjoint. Also fixed here, because the new rule depends on the same windowing: the `mine` query used event_list's DEFAULT `asc` order with limit 100, i.e. the OLDEST 100 events this device ever wrote. Once a device passes 100 authored events its most RECENT replies fall out of the join window and every ask it just answered resurfaces as owed. Latent, not theoretical — tor-ms22 was at ~20. Both windows are now anchored at the newest end with order: "desc". Verification: scripts/test-owed-withdrawal.sh, 17 assertions over VERBATIM fixtures from the real log (seq 112/119/120/122). It extracts the predicates from mempalace.ts by brace matching and runs the SHIPPED text rather than a pasted copy — this repo has already paid for a divergent second copy. Sensitivity proven by four mutations: removing the marker requirement flips exactly the 3 marker assertions, and disabling the third-party / broadcast / ordering guards each flip exactly their own. Control passes.
This commit is contained in:
@@ -171,10 +171,76 @@ An event is **still owed** when all of these hold:
|
||||
|
||||
1. it is directed at you (`to_agent = <you>`, and `to_agent = '*'` is excluded — §7.4);
|
||||
2. it was not written by you (you cannot owe yourself);
|
||||
3. **no** event of yours has *all* of: a strictly higher `seq`, a join to it (`metadata.ack_of = <id>` or a shared `correlation_id`), **and** a terminal status (`applied` / `superseded` / `failed` / `blocked`).
|
||||
3. **no** event of yours has *all* of: a strictly higher `seq`, a join to it (`metadata.ack_of = <id>` or a shared `correlation_id`), **and** a terminal status (`applied` / `superseded` / `failed` / `blocked`);
|
||||
4. **the requester has not explicitly withdrawn it** — see §3.3.1.
|
||||
|
||||
The strictly-higher-`seq` test is load-bearing: without it, one terminal reply on a `correlation_id` suppresses every *later* ask on that same correlation, permanently. And because `seq` is local arrival order, this test is only sound on a single replica — §7.3.
|
||||
|
||||
#### 3.3.1 Withdrawal: the one case where someone else may clear your obligation
|
||||
|
||||
> **Status: implemented in the toolkit, not yet in a released image.** Clause 4 is
|
||||
> live in `extensions/pi/mempalace.ts` (`isWithdrawn`) with rule tests in
|
||||
> `scripts/test-owed-withdrawal.sh`. It reaches a device only when an image bakes
|
||||
> a toolkit revision containing it. Until then clauses 1–3 are the whole rule, and
|
||||
> **a sender must assume its withdrawal has no effect on the recipient's mailbox.**
|
||||
|
||||
Clause 3 says "no event **of yours**", and that asymmetry is deliberate: owed-ness
|
||||
is a statement about the *recipient's* accountability, so a requester must not be
|
||||
able to delete an obligation the recipient genuinely has. It is wrong in exactly
|
||||
one case — the requester retracting its **own** ask.
|
||||
|
||||
**Measured, 2026-09-08/09.** `pi@mbp-m1-2020` withdrew a v1.8.13 rollout ask to
|
||||
`pi@tor-ms22`: terminal `superseded`, same `correlation_id`, `metadata.closes`
|
||||
naming the thread, body "DO NOT SPEND A MINUTE ON v1.8.13". It recorded the
|
||||
withdrawal as done. The withdrawal had **no effect** — its `from_agent` is mbp, so
|
||||
it could never satisfy a join that only inspects tor-ms22's own events. tor-ms22's
|
||||
next wake-up, 8h later and on the first boot of the image shipping this very
|
||||
derivation, still listed the ask as owed, 41h old, for a release that had been
|
||||
superseded and never installed there. Closing it cost the recipient a write on a
|
||||
thread nobody wanted answered. **The failure is invisible from the sender's side:**
|
||||
mbp did everything a sender is told to do and got a result indistinguishable from
|
||||
success, which is why this went unnoticed for 41h rather than being caught at once.
|
||||
|
||||
A withdrawal is honoured only when **all** of these hold. Each is a guard against a
|
||||
specific way a mailbox could otherwise be emptied silently, and each is pinned by a
|
||||
mutation test:
|
||||
|
||||
| Requirement | The failure it prevents |
|
||||
|---|---|
|
||||
| `from_agent` = the **original requester** | a third party clearing someone else's obligation |
|
||||
| `to_agent` = the recipient **exactly** (not `'*'`) | one broadcast emptying every machine's mailbox at once (§7.4) |
|
||||
| terminal status | a `claimed`/`ready` note reading as a release |
|
||||
| strictly after the ask (`hlc`, §7.3) | retiring an ask the requester sent **later** on the same correlation |
|
||||
| joins the ask (`ack_of` or shared `correlation_id`) | clearing an unrelated thread |
|
||||
| **an explicit marker**: `metadata.withdraws` **or** `metadata.closes`, whose value is exactly the ask's `correlation_id` or its event `id` | **the dangerous one** — see below |
|
||||
|
||||
**Why release must be stated rather than inferred.** The tempting rule is "any
|
||||
terminal event from the requester clears it". That breaches this log's standing
|
||||
safety direction, which is that every failure stays on the *noisy-but-visible*
|
||||
side: a spuriously resurfacing item costs one turn of human correction, while a
|
||||
suppressed unanswered ask is silent and permanent. Under the naive rule a
|
||||
requester appending `applied` for its own bookkeeping — on the correlation,
|
||||
addressed to the recipient, before the recipient ever replied — would silently
|
||||
delete a real obligation. `withdraws` is canonical; `closes` is honoured because it
|
||||
is already the fleet's de-facto marker (mbp seq 119; emb seq 117 and 118 all use
|
||||
it). Prose does not count: the value must *name* the thread, so
|
||||
`closes: "v1813-client-rollout-tor-ms22"` is a withdrawal and
|
||||
`closes: "v1813-… — from the RECIPIENT side, which is the only side that can"` is
|
||||
not.
|
||||
|
||||
**This does not break the fixed point in §9.2, and a reader will reach for §9.2 to
|
||||
object.** That section rejects letting terminal directed events into the owed set,
|
||||
because then a reply becomes owed by its requester, closing it mints a fresh
|
||||
obligation, and the loop never terminates. That argument is about **candidates**.
|
||||
Clause 4 adds a **clearer**. A withdrawal carries a terminal status, so it can never
|
||||
be a candidate, and nothing new becomes owed: the asserting shape (`open`) and the
|
||||
clearing shape (terminal) stay disjoint, which is what makes owed-ness terminate.
|
||||
|
||||
**Recipients keep the last word.** A withdrawal suppresses; it does not rewrite
|
||||
history. The recipient may still append its own terminal reply — worth doing when
|
||||
there is a finding to carry across, as tor-ms22 did at seq 122, salvaging two
|
||||
measurements from the retracted thread.
|
||||
|
||||
### 3.4 Artifacts
|
||||
|
||||
Exact payloads for handoff: `patch`, `file`, `log`, `json`, `note`; UTF-8 text only; ≤4 MiB; `sha256` and `size_bytes` returned (`logstream.py:744-810`).
|
||||
|
||||
Reference in New Issue
Block a user