The estate, by state — and where it produces a confident zero
Every surface, feed, store and job in the Mallory estate, colour-coded by whether it is carrying traffic, dark, paused or retiring. Then the Demand Board arc, and a ranked gap list split by what the 17 August measurement gate depends on.
This page's diagnosis is wrong in several places and should not be relied on. It concluded the Tower had died and the vault had gone silent. Neither was true: the Tower ran throughout, and the vault took 670 commits in the period this page calls silent. The error was a stale local clone read as if it were live.
The estate map, the state colour-coding and the gap list below still hold. The outage narrative and the gate arithmetic do not.
The vault's last commit is 2026-08-08 07:30. There are no daily notes for 8–12 August. The nightly reflection, the miss log and the weekly review have all been silent for five days, and nothing reported it — it was found by the operator trying to use the estate.
The first diagnosis was wrong, and the correction is more interesting. This
page originally concluded the Tower had died, on the reasoning that
cron-watchdog.sh and status-digest.sh both run on it and would have
died with it. A live read of GET /status on 13 Aug disproved that:
hub.lastContact was eleven minutes old and fresh: true.
The Tower is up. context-push has been running the whole time.
The failure is partial — the job plane that writes the vault stopped while the VM-side
jobs carried on — and the reason nobody saw it is a design fault in the health check
itself. signalsHealth() reads four signals from different producers on different
hosts and collapses them:
lastContact = max(ts across all four)
fresh = ANY one within its own TTL
So one healthy producer reports the whole estate healthy. That is the same
error as view recording / for every surface, and the same error as
a push_sent row written before the fan-out: an aggregate that conceals the member
you actually need to see. It is this briefing's thesis holding in a form sharper than the
original draft — not a monitor that died, but a monitor that was built to average away
the failure it was watching for.
The gate arithmetic below is corrected in place and is materially worse than first reported.
Before anything else
What was read, and what could not be
The working assumption going in was that only mallory could be read, and that
mallory-runtime, mallory-pebble and the vault would have to be
taken on trust. All four were in fact readable. Everything here about the Tower jobs, the
Goose recipes and the vault is therefore Confirmed from
source. Two of the largest findings below could only be reached that way.
What is still out of reach
Live production state. No wrangler --remote was attempted. So: which Tower host
actually runs each cron, current pause flags, prod KV contents
(config:response-slas, board:editorial), row counts in
calendar_events, and whether any push subscriptions exist. Anything resting on
those is marked Unverifiable here.
How claims are marked
- Confirmed — the file was opened; a
path:lineis given. - Inferred — reasoned from code that was read, but not observed running.
- Given — operator-supplied figures and settled decisions, taken as read.
- Unverifiable here — needs live prod or Tower state.
Prior art
What the existing decks already say
A large part of this ground is already covered by the three decks in the archive. Their framing is reused rather than redrawn, and none of it is restated here as discovery.
| Question | Already answered by |
|---|---|
| The 142 / 130 / 12 / 0 founding audit | 2026-07-18 deck situation[0]; spec §1. |
| The reframe to "a demand on my time" | 2026-08-01 deck decisions[0]; spec §1. |
triage defaults to 'off', never enabled | 2026-08-01 spec §1.3, citing responseSla.ts:204. |
The board has never read loop_items | 2026-08-01 spec §1.3; v1 §9 fenced them out. |
| "No usage instrumentation of any kind" | 2026-08-01 spec §1.4 — the reason the measurement layer exists. |
The responseSlaPoll transport gap | 2026-07-27 addendum §4, in full. |
| Retiring the ~$70/mo triage as the wrong feed | 2026-08-01 spec §3 and "On cost". |
| The gate, and that it may retire the board | 2026-08-01 spec §2, §6 step 5. |
| "A clean zero indistinguishable from a broken job" | 2026-08-01 deck risks[3], naming three instances. |
The 2026-08-01 deck's delivery[2] reads: "stampNudgeUrl behind tests, then
notify mirrors the send and ships the stamped link." The mirror shipped. The stamped
link did not — no caller anywhere sets nudgeId. The deck
records an intention as a completed state, which is exactly the failure its own
risks[3] warns about.
New in this pass
nudgeIdhas zero callers, and the coalesced-digest shape means it structurally cannot have one on the main path.push_sentis logged before fan-out — it counts intentions, not deliveries.- Nothing anywhere records that the board was looked at.
- Three of the seven event verbs have no writer.
- The miss log silently lost the night of 4 August.
- The miss log is not coming back empty — two real misses in four answered nights.
- The weekly rollup, where the numbers are meant to land, has not run since W29.
- The calendar ingest caller now exists (3 Aug) — but only in the Goose twin.
reminderPushfans out one push per reminder, uncoalesced.- The one-day skew in the live figures is evidence of the two-pipeline deploy split.
Part one
The ecosystem map
Read top to bottom: producers push into ingest ports, ports write stores, jobs judge stores,
surfaces read the judgement, delivery goes back out. Each node names what it feeds.
→ is a live edge; ↛ is
an edge that exists in design but carries nothing.
Colour follows the guide's functional-colour rule — health reads as the absence of colour, and oxblood is spent only where something genuinely demands attention. The red nodes are the ones to look at.
- LiveCarrying traffic now
- DarkBuilt and tested, no caller, never enabled
- Confident zeroRuns, reports success, emits a zero that cannot be told from a break
- PausedOff by flag, still present
- RetiringDecision taken, not yet removed
- GatedDeliberately not built until ~17 Aug
Producers
Tower / Goose container / VM —mallory-runtime:277.→ POST /ingest/calendarpost_calendar. If this is the live host, "Coming up" is still empty.↛ /ingest/calendarmisses_drafted — a run that drafts nothing is downgraded to fail.→ vault · Daily notes · ## Missesis_paused gate at :31; live flag Unverifiable here. Missed roughly half of real asks Given.→ POST /ingest/pendingIngest ports
Workermallory-core · Access-gatedroutes/nudge.ts:103.→ nudges → notify()Stores
D1 · KV · R2usage_events instead. Better answer, never written down.→ GET /updatesboard.ts, routes/board.ts, boardCompose.ts. Confirmed.↛ boardtriage resolves to 'off' by default (responseSla.ts:204) and was never switched on.↛ summary · wants_actionJudgement & delivery jobs
Worker cron — dispatched inindex.ts:280demands (spec §6 step 9).→ channel_pending.summaryedge-poll; every live connection is ingest. Iterates to zero and logs nothing.↛ anythingnudgeFlush.ts:43) — which is why attribution cannot work. See gap A6.→ notify() × 1notify() once per reminder — no coalescing, and it bypasses shouldSendNow entirely (reminderPush.ts:25–33). Against 283 reminders, the likeliest source of ~16 pushes/day.→ notify() × NSurfaces
Pagesmallory-web · Android shell · watch · vault/ opened by ?view=updates (+page.svelte:614). GET /board records nothing. No instrument knows it was opened.↛ usage_eventsdata.pebbleKind. Token bug fixed 2 Aug.→ Timeline APISKILL.md:65). No review written since W29.↛ W30, W31, W32Part two
The Demand Board arc
Three cycles in six weeks, each re-diagnosing the last. The through-line is that the question kept getting better while the instrument kept getting worse.
| Cycle | Diagnosis | What it changed |
|---|---|---|
| 18 Jul Situation Board | The SLA engine works but is invisible; pushes are a slower echo of native ones; three surfaces fragment the view. | One merged self-draining board. Ambient by default, breach escalates. |
| 27 Jul Availability addendum | Post-ship: the board renders and judges nothing. 0 of 219 triaged, 0 deadlines, 0 breach pushes, empty board:editorial. | Enrichment moved off the dead poll onto a transport-agnostic sweep. Deferral verbs surfaced. |
| 1 Aug The reframe | Not under-commissioned — aimed at the wrong question. Invested entirely in the one axis Slack wins, with no evidence anywhere. | Unit becomes "a demand on my time". Measurement ships first and gates everything. |
| Now The gate | Measurement shipped 2 Aug. Miss log started 3 Aug. Window closes ~16–17 Aug. | Nothing downstream is built. The gate may conclude "retire the board and its feed". |
Where it sits in the map
The Demand Board is not a node — it is a planned replacement for the court zone. Today the board reads one source. It was designed to read five.
| Zone | Designed to read | Actually reads |
|---|---|---|
| On your court | demands, projected from four sources plus capture | channel_pending only — untriaged, so rules-order |
| Coming up | calendar_events | Caller exists since 3 Aug in the Goose twin only Unverifiable |
| Remember | reminders | Working |
| Handled / FYI | Resolved and dismissed, newest first | Working since PR #172 |
| Editor's note | KV board:editorial, availability-aware | Composer runs 3×/day; availability-awareness not built |
| Alex's own to-dos | loop_items | Never — fenced out by v1 §9, still fenced |
| Delegated work | q_tasks where status='needs_you' | Never wired |
What the gate is actually collecting
Read directly from the vault's daily notes. The window opened 3 Aug and runs ~14 nights:
| Night | ## Misses | |
|---|---|---|
| 3 Aug | CE Plus cert expired Saturday; Bruno's remediation email ~5d old, no reply sent | Real miss |
| 4 Aug | Section absent. Reflection empty too. | Night lost |
| 5 Aug | None. | Answered |
| 6 Aug | None. | Answered |
| 7 Aug | Schema migration due 10 Aug arrived urgent and unassigned while on leave; no owner going into the weekend | Real miss |
| 8–12 Aug | No daily note at all — Tower outage, five consecutive nights. | Lost |
Corrected 12 Aug: four usable nights out of ten elapsed. The window was to close ~16–17 Aug on fourteen nights. Even if the Tower returned immediately it would reach eight, half of them produced by a system that was intermittently dead. The 17 August gate cannot be met on its original basis and needs an explicit decision — extend it, or change what it rests on.
The five lost nights must be marked lost, not backfilled as
None. The spec is explicit that inventing a miss is worse than reporting none;
the inverse holds harder, because a fabricated None. would report that curation
was working on nights when nothing was watching.
Two real misses in four answered nights, and both are the kind the board was reframed to catch — a demand on time, not a message awaiting a reply. On this evidence the gate is trending toward "build it", not "retire it". Confirmed.
But 4 Aug is gone, silently, from a 14-night sample — a 7% hole in the only evidence the decision rests on, and nothing surfaced it.
The push ratio
| Event | Rows | What it actually counts | Trustworthy |
|---|---|---|---|
push_sent | 82 | Calls to notify(), logged before fan-out and outside every send's error handling — so it counts even with no subscriptions and every send throwing. | Upper bound only |
view | 12 | Route navigations recorded as pathname. Board, chat, brief, Updates and R&D are all panels on /. | Cannot separate surfaces |
open | 6 | PWA session starts, one per mount. | Yes — the only clean number |
push_tapped | 0 | Nothing. The branch is unreachable. | Not a behaviour |
So the headline stands, but as a floor rather than a measurement: up to ~16 push attempts a day against 2 app opens a day. Against a design intent of "few, calendar-aware pushes rather than going ambient", the estate is going ambient by roughly eight to one.
The windows aren't the same. push_sent spans 3–7 Aug
(5 days, 16.4/day); open spans 4–6 Aug (3 days, 2.0/day). They overlap only
on 4–6 Aug. A like-for-like ratio needs the per-day split.
That one-day skew is itself a finding. The measurement layer merged on 2 Aug in a single PR. The Worker half started logging on 3 Aug; the browser half on 4 Aug. Two independent build pipelines from one merge — the deploy-split question, answered by the estate's own data.
The two chains that matter
How the zeros are manufactured
stampNudgeUrl fails, but that no caller ever
supplies a nudgeId for it to stamp. All 82 push_sent rows
carry a NULL subject because the field is optional and nothing sets it.
Part three
The gap list
Group A is only things where the gate's own evidence is at risk — a number that will be read on 17 August and is currently wrong, missing or unreadable. Every one is additive instrumentation or a diagnostic read. None builds anything downstream, none touches the demand model, none changes what Mallory sends.
Four candidates that felt urgent were pushed to Group B, because fixing them changes behaviour the gate is currently measuring. Changing the thing you are measuring mid-window is worse than a small gap in the data.
Group A — measurement integrity · safe now
Nothing records that the board was looked at
- Broken
- The gate's central question is whether the board earns its keep. There is no instrument that can answer it.
- Evidence
GET /board(src/routes/board.ts:21) writes no usage event and no last-read timestamp. The board is a panel on/opened by?view=updates(+page.svelte:614). The beacon logspathname(+layout.svelte:36), so every surface records/. Confirmed- Radius
- One
recordUsageEventinhandleBoard, plus an explicitsubjectIdfromopenUpdates(). Additive; nothing readsusage_eventsat runtime. - Verdict
- Do now. Correction 12 Aug: the window has since lost five more nights to the Tower outage, so this instrument matters more, not less — whatever basis the gate ends up using.
4 August is missing from the miss log, and nothing said so
- Broken
- One night of a fourteen-night sample vanished. The gate's only judgement signal has a hole in it, invisible from inside the vault.
- Evidence
Daily notes/2026-08-04.mdhas no## Missesheading and an empty## End-of-Day Reflection. Every other night 3–7 Aug has both. Confirmed Themisses_draftedguard should have downgraded that run tofailand alerted, so either the job never ran or the alert never landed — Unverifiable here.- Radius
- None — a read. Check
eod-reflect.jsonfor 4 Aug. - Verdict
- Do now, and check daily until 17 Aug. A second lost night puts the sample below what the decision can carry.
The rollup the numbers land in has not run for three weeks
- Broken
- Spec §4.6 chose the weekly review over a dashboard so the numbers would be read. The review is not being produced, so nothing is being read.
- Evidence
- The rollup exists —
.claude/skills/review/SKILL.md:65queriesusage_eventsgrouped by action. The vault's most recent weekly review is2026-W29; W30, W31 and W32 were never written. Confirmed - Radius
- Run
/review. Separately, find out whyjobs/goose/weekly-review.shstopped. - Verdict
- Do now. Otherwise the gate arrives with raw rows and no read-out.
push_sent counts intentions, not deliveries
- Broken
- The 82 is the number the whole "going ambient" argument rests on, and it is an upper bound of unknown tightness.
- Evidence
recordUsageEventruns atsrc/webpush.ts:187–198, before the web-push fan-out (:201) and the FCM fan-out (:218). Each send is separately try/caught and swallowed. Confirmed- Radius
- Add a delivered/failed count to the event's
detailafter fan-out rather than moving the write — moving it would lose the record of attempted-and-failed pushes, which is itself worth knowing. - Verdict
- Do now, but note the discontinuity — rows before and after are not comparable. Stamp the changeover date in the weekly review.
Three of the seven event verbs have no writer
- Broken
surfaced,actedandexpiredare defined and validated but never emitted. "Cost per acted demand" has a structurally zero denominator.- Evidence
- Declared at
src/db.usage.ts:10, accepted atsrc/routes/events.ts:9. A global grep forrecordUsageEventreturns exactly two call sites:webpush.tsandevents.ts.boardActionposts to/response-slas/action, which records nothing. Confirmed - Radius
- One insert in the
/response-slas/actionhandler with the verb indetail. - Verdict
- Do
actednow — it pairs with A1. Leavesurfacedandexpired; they belong to the demand model and are properly gated.
push_tapped cannot fire — and the fix is not a one-liner
- Broken
- The cut is one link earlier than it appears, and the obvious fix does not work on the main path.
- Evidence
nudgeIdis optional onPushMessage(webpush.ts:22) and no production caller sets it — a grep across all 13notify()call sites returns nothing. Confirmed- Deeper
nudgeFlushdeliberately coalesces N nudge rows into one digest push (nudgeFlush.ts:43) — a headline decision of the 18 Jul spec. So there is no singlenudgeIdto stamp, andsubject_id TEXTcannot hold a set. A schema gap, not a missing argument. Inferred- Verdict
- Wire the single-nudge paths now; hold the digest decision — the gate may retire the feed that generates digests anyway.
Group B — everything else · waits for the gate
"Coming up" depends on which host runs calendar-sync
- Status
- The caller was built on 3 Aug — but only into the Goose twin.
mallory-runtimePR #118 addspost_calendartolib/common.sh:148, called fromjobs/goose/calendar-sync.sh:277. The VM original has zero hits. Confirmed - Verdict
- Check now, change nothing. One query against
calendar_eventsmay retire a known gap and date two specs.
reminderPush fans out one push per reminder, uncapped
- Status
- Likeliest source of ~16 pushes/day, and a direct contradiction of "few, calendar-aware pushes".
reminderPush.ts:25–33, 8×/day, bypassingshouldSendNow. Confirmed - Verdict
- Wait. Coalescing changes the exact volume the gate is measuring. Confirm it is the source first, from the per-day split.
The board still cannot see Alex's own work
- Status
loop_itemsandq_tasks · needs_youare both structured, both tracked, both invisible. Confirmed- Verdict
- Wait. This is the projection layer — explicitly gated as step 7.
The two-pipeline deploy is real, but not the trap it looks like
- Status
- D17 (
docs/estate/05-decisions.md:124–132) connected bothmallory-webandmallory-coretomain, verified 2026-07-27 with the proving commands recorded. Merge is the deploy for both. Confirmed - Cost
- Two independent builds from one merge, which can succeed and fail separately — visible as the 3 Aug / 4 Aug skew. And no pause between merge and production.
- Verdict
- No action. Documented, verified, trade-off taken deliberately.
Retiring feeds still running
- Status
triageoff atresponseSla.ts:204;slaEnrichretires only after the board readsdemands;slack-mentionscarries its pause gate. Correctly parked.- Verdict
- Leave everything off. The 27 Jul addendum's warning stands: enabling active posture on stale items emits high-priority pushes that override quiet hours before any revert can land.
Documented state has drifted from actual state again
- Status
- Two fresh instances, both inside the measurement cycle. (i) Spec §4.2 called the
nudgesprune change "one line"; the code kept the 3-day prune and mirrored intousage_eventsat 365 days instead — a better answer, never written down. (ii) The deck saysnotify"ships the stamped link"; it does not. Confirmed - Verdict
- Amend the decks when the gate closes, alongside whatever it decides.
The one thing that, if wrong, makes the rest wrong
usage_events measures the Worker's intentions, not Alex's behaviour
push_sent counts calls to notify(), logged before anything is
delivered. view collapses the board, chat, the brief and R&D into a single
/. acted, surfaced and expired have no
writer at all. push_tapped is unreachable. Only open — six
rows — is a clean record of something Alex actually did.
Everything in Group A follows from that reading, and so does the shape of the 17 August decision. If it is right, the gate is on course to be settled by the miss log alone — the vault half, not the D1 half — and the instrumentation shipped on 2 August will have contributed almost nothing to a decision it was built to inform.
If it is wrong — if the beacon was mis-traced, or there is a writer that was not found — then Group A dissolves, the 82:6 ratio is a real measurement, and 17 August can proceed on the data as it stands.
Checkable in one query, and worth running before anything here is acted on:
SELECT action, subject_type, subject_id, COUNT(*) FROM usage_events GROUP BY 1,2,3.
If every view row carries subject_id = '/' and no row carries
action = 'acted', this briefing is right.
design.mallory.chat/reviews/briefings · estate briefing, 2026-08-08, updated 2026-08-12 · diagnosis only.