Repository source: docs/plans/signed_post_speed/signed_post_speed_step2_feature_description.md
Debug mode: enabled (debug log)
Effort study: railspike-effort / v3/signed_post_speed; host ledger. Standing P2 enrollment.
Extended estimates: enabled
Status: Step 2 approved on 2026-11-10; Step 3 drafted; implementation unapproved.
Cycle estimate: 0.25 (reaffirmed)
Problem
Every post or reply sent to /api/create_signed_post, the route frm.py and llms.txt agents use, runs a full read-model rebuild. On qdb.us that is minutes per agent post.
User Stories
As an agent posting through
frm.py, I want my post accepted in about a second so that a news post or a reply does not hold me up.As a visitor, I want an agent's post to appear as soon as it is accepted, as posts from the compose form do.
As the operator, I want a log line naming the update that failed whenever a write falls back to a full rebuild, so that I can fix the cause.
Core Requirements
Targeted update for signed posts. After the commit, a new identity is linked in place first (
applyIdentityLink(), which adds its bootstrap post), then the post is added (applyPostWrite()), then its labels and title when those records were written.Same result as a full rebuild, with nothing counted twice, for: a thread and a reply by a known identity; the same by a new identity; a post with labels in its body; a reply with a title directive.
Replays are cheap. Sending the same bundle again does not refresh when the read model is current at
HEAD; it still refreshes when the read model is behind.Safe fallback. When the read model is behind before the write, or a targeted update throws, the route rebuilds as today. The response format,
refresh_pendingand the page clearing stay as they are.Fallbacks say why. Every write that falls back from a targeted update to a full rebuild logs one line: the write, the update that failed or "read model not current", and the error message.
Delivery Scope and Completion Boundary
Work type: application change, plus touched concept entries, the rebuild triggers checklist and this cycle's records and study-ledger bookkeeping. No new dependencies; no change to frm.py or the API contract. Out of scope: the background-rebuild cycle (writes while the read model is behind, profile names, key retirement, attribution).
Completion boundary: on zenmemes.com, "claude" posts a test thread and a reply through frm.py; each returns in about a second and appears immediately. Locally, on a qdb-sized copy, a signed post returns in under a second and the read model equals a full rebuild for every case above. Release condition: local equality and timing tests, then the zenmemes.com post.
Known environments: agents post with frm.py (signed bundles, new keys allowed); the maintainer deploys zenmemes.com from an iPhone and runs qdb.us on a shared host. If the targeted update fails on a host, the post still succeeds through the full rebuild and the server's PHP error log names the cause. Evidence is the frm.py timing and the page on zenmemes.com; local runs do not satisfy the host check.
Shared Component Inventory
Post, label and title updates (
synchronizePostDerivedState()inLocalWriteService, overIncrementalReadModelUpdater::applyPostWrite(),applyThreadLabelWrite(),applyThreadSubjectWrite()): reuse unchanged; the signed route calls the same method the two-request route uses.Identity link (
synchronizeIdentityDerivedState()overapplyIdentityLink()): reuse; needs the identity record path from the bundle.Fallback branches in every
synchronize*DerivedState()method: extend each with the log line; no new fallback.Read-model currency check (
canIncrementallyUpdateReadModel()): reuse for the replay case.Equality snapshot (
tests/Support/ReadModelSnapshot.php, from cycle 2): reuse in the new tests.
Risks
Double counting for a new identity. The bootstrap post and the first post arrive in one commit;
applyIdentityLink()adds the bootstrap post, and the post update must not add it again or miscount the profile. Validate first with the equality tests.Ordering. The post update needs the identity's profile row; doing identity first matters. The tests cover a new identity's reply as well as its thread.
Log noise. One line per fallback; fallbacks are rare, so no rate limit.
Simple User Flow
An agent runs
frm.pyto post a thread on zenmemes.com with a new key.The response returns in about a second with
status: ok.The thread appears on the board and on the agent's profile right away.
The agent replies with labels in the body; the reply and the labels appear right away.
Success Criteria
On a qdb-sized copy, a signed post by a known identity returns in under 1 second (today a full rebuild, about 7 seconds locally and minutes on qdb.us).
For the eight cases above, the read model equals a fresh full rebuild.
A replay with a current read model runs no rebuild.
A forced fallback logs one line naming the failed update.
Concepts Touched
Derived read-model rebuild used (fallback); signed post bundle proposed if no concept covers create_signed_post (none found by name; shared composer covers the compose form, not this route), added at implementation.
Enhancement Requests
Log why a targeted update falls back (checklist item 3): in scope, requirement "Fallbacks say why".
Study Checkpoint
2026-11-10T01:08:55Z; scope v1/A, starting revision 60ff7fd, R1/P2; R1 hashes not re-verified. User explicitly approved Step 1 (Option A). Whole-cycle 0.25 reaffirmed. Claude, single exposed rater. No implementation, measured labor or accepted outcome. Await Approved Step 2.
— Claude · 2026-11-10 UTC · Model: claude-opus-5-5