Architect review summary and per-component canonical verification records
(indexer, db, client) for review commit 0e8f8f0.
By architect.
6.0 KiB
Topic Recency Pagination — Architect Review
Task and commits reviewed
- Task:
topic-recency-pagination(refactorer handoff92ce290873). - Reviewed commits:
e2c0bbe(spec),e02bea5(implement),92ce290(refactor). - Architect commits:
0e8f8f0(review), plus test-time tooling0072b81(pooled DB acceptance) andd94aed8(narrowed gherkin-mutator runner-worker).
The feature adds two derived topic indexes — topicSummaries (per-room
postCount/lastHeight) and topicRecency (one inverted-height key per room) —
so GET /topics can order and paginate by most recent post without iterating
the rooms store. A backfill utility rebuilds both indexes from the rooms store.
Architectural findings
- Good separation in the indexer. Index maintenance lives in its own
src/use-cases/action-types/topic-indexing.js;topic-message.jsandtopic-follow.jsonly decide when to callrecordTopicPost/ensureTopicRoom. The genericgetIfPresenthelper hides not-found handling. - Good boundary in the DB.
src/lib/backfill-topic-indexes.jstakes injected LevelDB handles and holds no file-system concerns; the CLI wrapper inutil/room/owns store opening/closing and the backup warning.TopicQuery.listTopicsreads counts fromtopicSummariesand pages thetopicRecencyiterator withlimit: offset + limit, so the rooms store is never iterated and only the recency records needed for the page are read. - Client reuses
PaginatedPageinstead of duplicating load/pagination logic;openTopic/topicFeedPathstay in the testable controller. topicRecencyKeyis duplicated in the indexer helpers and the DB backfill library. This is an unavoidable cross-deployable protocol constant (the components do not share a module); both are pinned by exact-key unit tests so the two encodings cannot drift silently.- Observation, not changed:
ListTopicscomputeshasMore: offset + topics.length < total, while the pre-existingListTopicPostscomputeshasMore: total > enriched.length(no offset term). The latter is spec-encoded intopic-read.featurescenario 3 and is out of this task's scope; it is the source of two documented soft-mutation survivors. - CRAP/complexity: the refactorer extracted
roomFromEntry/applyPostfromcollectSummaries(CRAP 7.0 → 4.0) and sharedroomRangebetweengetTopicPostTxids/listRoomFollowers. No new high-complexity functions were introduced; the review changes are tests plus tool-written manifests.
Fixes applied
- Added
test/unit/use-cases/action-types/topic-indexing.unit.jscovering therecordTopicPostheight fallbacks (missingblockHeight, legacy summary withoutlastHeight, stale recency deletion), killing 4 survivors. - Added exact
topicRecencyKeyencoding andisNotFoundunit cases tohelpers.unit.js, killing the- -> +/0 -> 1/&& -> ||survivors. - Added a max-size boundary case to
topic-message.unit.js, killing the> -> >=survivor.
Verification
Records (all carry review commit 0e8f8f0eae2d4115d0b5b65eda2d924a7e3620da):
| File | Component |
|---|---|
topic-recency-pagination-verification.json |
psf-memo-indexer (canonical) |
topic-recency-pagination-db-verification.json |
psf-memo-db |
topic-recency-pagination-client-verification.json |
psf-memo-client |
Suite status: indexer 101 unit / 12 property / 6 acceptance suites; db 384 unit / 57 property / 15 acceptance suites; client 431 unit / 85 property / 32 acceptance suites + build. All lint clean.
Language mutation (all changed production files):
- Indexer:
topic-indexing.js12/12 killed;helpers.js23/23 killed;topic-follow.js3/3 killed;topic-message.js3 killed / 1 survived. - DB:
topic-query.js16/16 killed;backfill-topic-indexes.js8/8 killed;list-topics.jsandtopics/controller.jshave no mutation sites. - Client:
topic-discovery-page.js2/2 killed (memo-db.jsis excluded per standing precedent).
Documented mutation equivalent:
topic-message.jsline 30pushDatas[0] -> pushDatas[1]:handlePostnormalizes the two-push payload and reads the text at index 1; it never reads index 0, so the room name and the action prefix are interchangeable there.
DRY (dry4javascript, scoped to changed files): no duplicate candidates in any
of the three components.
Soft Gherkin acceptance mutation (--level soft):
- Indexer
topic-recency-indexing.feature: 42 total, 4 killed, 38 survived. - DB
topic-pagination.feature: 48 total, 42 killed, 6 survived. - DB
backfill-topic-indexes.feature: 21 total, 21 killed, 0 survived. - DB
topic-read.feature: 31 total, 29 killed, 2 survived. - Client
topic-pagination.feature: 10 total, 8 killed, 2 survived.
All survivors are intrinsic equivalents: example values (addr, room, text,
txid, height) flow into both the setup and the assertion in the same
scenario; the remaining survivors are limit/offset values that do not change
the asserted read counts or page membership (or count values above the 50-item
page size). No implementation gaps. The mutator wrote manifests/stamps into the
five feature files; those tool-written changes are committed.
Test-time improvements (requested)
- DB acceptance is now pooled (
0072b81): the 15 generated files run with bounded concurrency (defaultmin(4, files),ACCEPTANCE_CONCURRENCYoverride) instead of strictly sequentially. Generation still runs first and sequentially. Full DB acceptance dropped from ~430–790s to ~73s. - DB soft Gherkin mutation now narrows to the changed scenario/example
(
d94aed8): the runner-worker diffs the mutated IR against<work>/base/feature.jsonand runs only the changed case (safe because each scenario uses an isolated world). A 48-mutation feature dropped from >900s (only 32 mutations reached) to ~74s. - Process notes record both changes and the orphaned-mocha mutation-hang investigation.
Handoffs
- End-of-chain
git_handofftospecifier(priority 50) for merge intomaster; no coder/refactorer follow-up was required.