Skip to content

docs(design): scheduled-publish redesign — sortAt + a pending bitmap (PROPOSAL) - #324

Closed
JustMaier wants to merge 1 commit into
mainfrom
docs/scheduled-publish-redesign
Closed

docs(design): scheduled-publish redesign — sortAt + a pending bitmap (PROPOSAL)#324
JustMaier wants to merge 1 commit into
mainfrom
docs/scheduled-publish-redesign

Conversation

@JustMaier

Copy link
Copy Markdown
Contributor

Docs-only. A PROPOSAL, not a decision — Justin's, 2026-07-15. Nothing benchmarked, nothing scheduled.

The design

Write Image."sortAt" at schedule time (future for scheduled posts). BitDex holds a pending bitmap it clears locally on a sweep, and the engine — not the caller — excludes it from every query.

The consequence that matters: publish sends BitDex nothing at all. No op crosses the wire at Tf. There is no batch to apply, lose, or verify.

What it answers

"It seems like it's all tied to the queryOpSet, right? Or have we been running into issues unrelated to that as well?"

It is not all queryOpSet. Two distinct data families, plus a third that has cost as much engineering time:

  • A — fan-out. One PG row must become N BitDex writes and BitDex works out which N. queryOpSet ships a query, resolves it against its own index at apply time, and never checks what it matched. Confirmed live: 13 of 20 images stuck at publishedAt = 0, invisible, none self-healed at 5.8h — FD #69397's report verbatim. Same family: DocCache evicted by Post id rather than image slots; fan-out dedup collapse; Post fan-out firing pre-insert.
  • B — deferred/activation. A special state that isn't a bitmap, isn't durable like the rest, and needs an event replayed at an exact instant. The ~49.7k orphan loss, fix(ops): publish fan-out reaches deferred slots (reschedule-drop) #313 reschedule-drop, the ~1/320 activation-miss, and the entire verifier apparatus built to detect its failures.
  • C — instrument failures. The verifier raised false orphans for months; no data was ever lost in that class. And three adversarial passes over PR fix(activation): gate verification on an observed publish (v1.1.49, step 1 of 3) #323 found zero defects in the design and ~10 in the prose around it.

The shared root — and why the design works

Both A and B are a write resolved or replayed LATER, against state that has MOVED, with NO RECORD of what it was supposed to affect. Neither has an error path, because neither knows what correct looks like.

The cure, general: make it a durable VALUE that can be compared, not an EVENT that must fire correctly.

A value can be diffed against the source and repaired. "Did an op fire correctly three hours ago?" has no answer — only consequences. sortAt is a value. publishedAt-via-queryOpSet is an event.

What it deletes

Family B entirely — no batch ⇒ no orphan class. The deferred map (durability domain #4) becomes a bitmap. isPublished. And the whole verifier: #320, #321, PR #323, the barrier, publish_seq, VerifyGate, the ring. All of it verifies a batch that would not exist.

What it does NOT fix — stated so it isn't forgotten

  • queryOpSet survives for availability, postedToId, and every other post-level field. One caller removed, not the pattern.
  • The fan-out moves rather than disappears — but stops being time-critical, and a stale field is detectable where a missed event isn't.
  • A missed reschedule still publishes early — same class, now auditable.

Caveats that travel

n = 3 events. An anecdote, not a rate. The denominator (15,942 images / 3,593 posts, no LIMIT) is the only solid number. Four hypotheses were refuted by one measurement each, and the one true partial (13/7) is n=1 and unexplained — no rule named off it. The hot-path ANDNOT cost is unmeasured, and unmeasured premises died four times in one evening on exactly this subject.

🤖 Generated with Claude Code

…a pending bitmap

PROPOSAL, not a decision. Justin's, 2026-07-15. Records the design, the failure
classes it targets, what it explicitly does NOT fix, and what must be measured
before committing. Nothing benchmarked.

The design: write Image."sortAt" at SCHEDULE time (future for scheduled posts);
BitDex holds a `pending` bitmap it clears locally on a sweep and the ENGINE
excludes on every query. Consequence: publish sends BitDex nothing at all — no
batch to apply, lose, or verify.

Answers the question "is it all queryOpSet?" — it is not. Two distinct data
families plus a third that has cost as much time:

  A) fan-out: one PG row must become N BitDex writes and BitDex works out which
     N. queryOpSet ships a QUERY and resolves it against its own index at apply
     time, never checking what it matched. Confirmed live: 13 of 20 images stuck
     at publishedAt=0, invisible, none self-healed at 5.8h. Also: DocCache
     evicted by Post id not image slots; fan-out dedup collapse; Post fan-out
     firing pre-insert.
  B) deferred/activation: a special state that isn't a bitmap, isn't durable
     like the rest, needing an event replayed at an exact instant. The ~49.7k
     orphan loss, #313 reschedule-drop, the ~1/320 activation-miss, and the
     ENTIRE verifier apparatus built to detect its failures.
  C) instrument failures: the verifier's false orphans (no data was ever lost),
     and ~10 prose defects found in three review passes that found ZERO defects
     in the design.

The shared root, and why the design works: both A and B are a write resolved or
replayed LATER, against state that has MOVED, with NO RECORD of what it was
supposed to affect. Neither has an error path because neither knows what correct
looks like. The cure is general: make it a durable VALUE that can be compared,
not an EVENT that must fire correctly. A value can be diffed and repaired; "did
an op fire correctly three hours ago" has no answer, only consequences.

Explicitly NOT fixed, recorded so it isn't forgotten: queryOpSet survives for
availability, postedToId, and every other post-level field — the redesign
removes one caller, not the pattern. The fan-out moves rather than disappears. A
missed reschedule still publishes early (but becomes auditable).

Stronger variant argued in-doc: DERIVE pending (sortAt > now) from the sort
layers rather than store it — a derived bitmap cannot drift.

Evidence log carries its own caveats: n=3 events, an anecdote not a rate; the
denominator is the only solid number; four hypotheses refuted by one measurement
each; one partial at n=1, unexplained, no rule named off it.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@JustMaier

Copy link
Copy Markdown
Contributor Author

Closing — not wanted as a PR. Design doc kept locally instead, per Justin.

@JustMaier JustMaier closed this Jul 15, 2026
@JustMaier
JustMaier deleted the docs/scheduled-publish-redesign branch July 15, 2026 22:20
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant