Repository source: docs/plans/qdb_quote_numbering/qdb_quote_numbering_step3_development_plan.md
QDB Quote Numbering — Step 3: Development Plan
Completion Contract
Normal entry point: the existing "Submit a quote..." compose form on the qdb site — both its plain HTML submit (
createThread()) and its default JS client-keypair-signed submit (prepareThread()+createPreparedPost()) go through the same form.Observable end-to-end outcome: after submitting, the visitor lands back on the board and sees their new quote's card showing
#<N>, exactly one past the previous highest number.Failure/recovery: if existing input validation rejects the submission, no number is consumed — the number lookup is a pure read that happens before anything commits, so a corrected resubmission still gets the correct next number.
Deployment/external-system boundary: none — uses the per-vhost read-model SQLite database already live for the qdb instance; no new env var, migration, or external service.
Release condition: complete after Stage 3; no follow-up cycle is needed for the feature to be usable.
Key Risks
The write-time suffix and
quote_card.php's display regex drift out of sync, silently reverting new quotes to raw-ID display.Impact: numbering appears broken for all new submissions.
Early warning / validation: Stage 1's manual mint check.
Mitigation: reuse the exact existing
-qdb-(\d+)$suffix convention, don't invent a new format.
There are two real submission entry points (
createThread()for the plain form,prepareThread()for the JS client-signed flow) that independently mint a thread ID today; patching only one would leave the other entry point unnumbered.Impact: numbering silently fails for whichever path isn't patched — likely the common case, since client-keypair signing is the default.
Early warning / validation: Stage 1 verifies both paths explicitly.
Mitigation: factor numbering into one shared private helper called from both methods, so they cannot drift apart.
The site-profile check is scoped incorrectly, numbering zenmemes/chouse threads too, or failing to number real qdb submissions.
Impact: a shared-code regression reaching the other two live instances.
Early warning / validation: Stage 2's non-qdb regression test.
Mitigation: gate strictly on
SiteProfileRegistry::active()['name'] === 'qdb', the same check already used for theme selection.
Stage 1
Goal: assign a sequential
-qdb-<N>suffixed thread ID at submission time on the qdb site, from both real submission entry points.Dependencies: none.
Expected changes:
New private helper on
LocalWriteService, e.g.nextQdbQuoteNumber(): int— runs the verified MAX-extraction query (MAX(CAST(substr(root_post_id, instr(root_post_id,'-qdb-')+5) AS INTEGER))) against the read-modelthreadstable, returnsCOALESCE(result, 0) + 1.New private helper, e.g.
mintThreadPostId(): string— returnssprintf('thread-%s-qdb-%d', gmdate('YmdHis'), $this ->nextQdbQuoteNumber())whenSiteProfileRegistry::active()['name'] === 'qdb', otherwise falls back to the existinggenerateRecordId('thread').createThread()andprepareThread()both callmintThreadPostId()in place of their current directgenerateRecordId('thread')call.
Verification approach:
With
FORUM_SITE_ID=qdb: submit via the plain compose form (createThreadpath) and confirm the minted ID isthread-<timestamp>-qdb-<N>withN= previous max + 1.Exercise
prepareThread(directly or via/api/prepare_thread) and confirm the same numbering behavior.With a non-qdb
FORUM_SITE_ID: confirm the minted ID is unchanged (original random-suffix form, no-qdb-number).
Risks or open questions:
Impact: see Key Risks (suffix/regex drift; two entry points; site-scope mistake).
Early warning / validation: the three checks above, before Stage 2.
Mitigation: single shared helper; reuse of the already-verified query and site check.
Canonical components/API contracts touched:
LocalWriteService ::createThread(),LocalWriteService::prepareThread()(internal behavior only — no public method signature changes).
Stage 2
Goal: lock in Stage 1's behavior with automated tests.
Dependencies: Stage 1.
Expected changes: new test cases in the existing thread-creation test coverage for
LocalWriteService:First qdb submission after existing imported quotes receives max + 1.
Two sequential qdb submissions receive two distinct, increasing numbers.
A qdb instance with zero
-qdb-rows yet mints number1(confirms theCOALESCEfallback).A non-qdb profile submission's ID format is unchanged.
Verification approach: run the targeted test class, then the full suite (
./v3 test); confirm the new cases pass and no new failures appear beyond the existing tracked pre-existing/order-dependent ones.Risks or open questions:
Impact: an untested edge case (e.g. empty table) could crash a fresh qdb instance's first submission.
Early warning / validation: the zero-rows test case above.
Mitigation: covered directly in this stage before it's considered done.
Canonical components/API contracts touched: test suite only.
Stage 3
Goal: confirm the full Completion Contract end-to-end on a real local qdb instance, and close the long-deferred
qdb_todo.txtitem.Dependencies: Stage 1, Stage 2.
Expected changes: no further production code changes expected; update
qdb_todo.txtitem 2 to note this is resolved (it had explicitly deferred the work until after the archive importer, which has since landed).Verification approach: with
FORUM_SITE_ID=qdblocally, submit one quote through the live compose form with a saved browser keypair (prepareThreadpath) and one without (createThreadpath); confirm each new quote's board card shows#<N>immediately after redirect, with the second submission showingN+1.Risks or open questions:
Impact: leaving
qdb_todo.txtstale misleads future work on the same file.Early warning / validation: part of this stage's own checklist.
Mitigation: update it in this stage's commit.
Canonical components/API contracts touched: none (verification and a doc-note update only).