Repository navigation
feat(seeder): expand db-seed with demo files + multi-type comments - #1484
Merged
Merged
Conversation
`./sbpp.sh db-seed` previously stopped at empty `:prefix_demos` rows and
ban-only comments. Two visible-but-empty surfaces on a freshly-seeded
dev DB:
1. **"Review Demo" link on the banlist** rendered as a dead anchor —
`:prefix_demos` was untouched by the seeder, so the page handler
never emitted the affordance and the surface was unreachable for
manual exploration / acceptance audits / screenshot work without
hand-uploading a demo first.
2. **Moderation queues** (admin submissions + protests) and the
public banlist's per-row comment disclosure rendered with empty
comment threads — `:prefix_comments` only ever got type `'B'`
(ban-attached) rows, so the C / S / P columns of the table were
structurally untestable.
This PR closes both gaps with one expansion of `Sbpp\Tests\Synthesizer`:
**Demo seeding.** `insertDemos()` writes ~80 rows at medium scale
(64 ban-attached, 16 submission-attached — 80/20 split that mirrors
the panel's own bias toward bans-with-evidence) and lands a paired
~1 KiB opaque text payload on disk under `SB_DEMOS` (`web/demos/` on
the dev bind-mount). Filenames are `md5(seed|demtype|demid)` so
re-seeds round-trip identically and the truncate-time wipe can find
them by re-reading the table. Picks use `array_rand` for exact
unique counts — rejection-sampling-with-collisions undershoots the
budget by ~6% which made the screenshot-flow rendering jittery
across runs. The synth payload is text (not a Source-engine binary),
so the "Review Demo" link round-trips end-to-end through
`getdemo.php` with `Content-Type: application/octet-stream` and a
download lands in the browser, but the SDK demoviewer won't replay
them — that would need a real demo binary and is out of scope for a
panel-chrome seeder.
**Multi-type comments.** `insertComments()` now fans the comment
budget across all four `:prefix_comments.type` values:
- `B` (ban) — 60% — `comments.bid` → `bans.bid`
- `C` (comm block) — 20% — `comments.bid` → `comms.bid`
- `S` (submission) — 10% — `comments.bid` → `submissions.subid`
- `P` (protest) — 10% — `comments.bid` → `protests.pid`
Medium scale → 200 comments → 120/40/20/20. The split matches the
overload contract in `api_bans_add_comment`'s `match($ctype)` arms
documented in AGENTS.md → "Add a comment-delete trash icon". The
parent-ID tracking arrays (`commsCids`, `submissionSubids`,
`protestPids`) populate during their respective `insert*()` passes
via `lastInsertId()` so the comment-insert pass has the FK pool ready.
**Idempotency contract.** The pre-existing truncate-then-reseed flow
got two refinements:
- **Snapshot filenames BEFORE truncate, unlink AFTER.** Previous
pre-fix shape was wipe-files-then-truncate, which left a transient
window where files were gone but DB rows still pointed at them
(any panel page query during that window 404'd the "Review Demo"
link). New order: snapshot → truncate → unlink, so rows + files
drop visibly together from the panel's POV.
- **Wipe scope is the snapshotted set, not the directory.** Manual
panel uploads (anyone who uploaded a demo against the dev stack)
and `.gitkeep` survive untouched. The `db-reset` companion (see
below) is the only thing that strips by-name across the whole
directory, scoped to the MD5-basename shape.
**Paired surface changes:**
- `.gitignore` — `web/demos/*` + `!.gitkeep` exception. Pre-fix a
fresh `db-seed` polluted `git status` with 80 untracked MD5-named
files; the demo store is also where the panel's `UploadHandler`
writes real ban evidence on production installs, so checking those
in would leak operator evidence into the source tree.
- `sbpp.sh db-reset` — strips MD5-basename files from `web/demos/`
after the DB volume drop. The `_demos` rows that pointed at them
vanish with the volume, so the next `db-seed`'s referenced-only
wipe reads zero rows from the fresh DB and skips them; without the
reset-time strip those files would accumulate as permanent orphans
across worktree lifetimes. `find … -regex '.*/[0-9a-f]{32}$'`
matches both shapes the write path produces (synth + the panel's
own `UploadHandler::handle()` with `renameToHash: true`).
- `AGENTS.md` + `docker/README.md` — exact per-scale counts, demo
payload caveat (not playable in the demoviewer), the new wipe
ordering contract, gitignore + `db-reset` cleanup behavior.
**Verification.** Default-seed run produces exactly 80 demos +
exactly 200 comments split B=120/C=40/S=20/P=20; FK joins return
zero orphans across all six pairs (`demos.B` → bans, `demos.S` →
submissions, `comments.B/C/S/P` → bans/comms/submissions/protests);
two consecutive seeds with the default seed produce identical
filename sets (`md5sum` of `ls` output matches byte-for-byte); a
manually-touched decoy file in `web/demos/` survives every seed.
PHPStan clean; the one PHPUnit test that touches `:prefix_demos` /
`:prefix_comments` (`ExportBundleWriterTest`, 426 assertions) passes
unchanged.
Follow-up: #1483 tracks the pre-existing tech debt where the seeder
writes bare letter codes (`'U'` / `'D'` / `'m'` / `'w'` / `'e'`) into
the column-typed string fields covered by `BanRemoval` / `LogType`
instead of routing through the enum's `->value` per AGENTS.md
"Backed enums for column-typed fields". Out of scope for this PR.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to subscribe to this conversation on GitHub.
Already have an account?
Sign in.
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
./sbpp.sh db-seedpreviously stopped at empty:prefix_demosrowsand ban-only comments — the dev panel rendered with two visible-but-
empty surfaces on every fresh seed:
:prefix_demoswas untouched, so the page handler never emittedthe affordance and the surface was unreachable for manual
exploration / acceptance audits / screenshot work without
hand-uploading a demo first.
public banlist's per-row comment disclosure rendered empty
threads —
:prefix_commentsonly got type'B'(ban-attached),so the C / S / P columns were structurally untestable.
This PR closes both gaps with one expansion of
Sbpp\Tests\Synthesizer:insertDemos()writes ~80 rows at medium scale(64 ban-attached, 16 submission-attached — 80/20 split mirroring
the panel's own bias toward bans-with-evidence) and lands a
paired ~1 KiB opaque text payload on disk under
SB_DEMOS(
web/demos/on the dev bind-mount). Filenames aremd5(seed|demtype|demid)so re-seeds round-trip identically andthe truncate-time wipe can find them by re-reading the table.
insertComments()fans the commentbudget across all four
:prefix_comments.typevalues(B=60% / C=20% / S=10% / P=10%, medium → 120/40/20/20). Matches
the overload contract in
api_bans_add_comment'smatch($ctype)arms documented in AGENTS.md → "Add a comment-delete trash icon".
ordering (closes a transient "rows point at deleted files"
window); scope is the snapshotted set so manual panel uploads +
.gitkeepsurvive every seed..gitignoreforweb/demos/*(with the
.gitkeepexception);sbpp.sh db-resetstripsMD5-basename files from
web/demos/after the DB drop sopost-volume orphans don't accumulate.
Issue #1483 was filed for the pre-existing tech-debt finding from
the adversarial review (bare letter codes for
BanRemoval/LogTypecolumn-typed fields in the seeder) — out of scope for this PR.
Test plan
Local default-seed run:
./sbpp.sh phpstan— 253/253, no errors)./sbpp.sh test tests/integration/ExportBundleWriterTest.php— the one test that touches
:prefix_demos/:prefix_comments:3 tests / 426 assertions all green)
C=40, S=20, P=20). Confirmed via
SELECT type, COUNT(*) FROM sb_comments GROUP BY typeandSELECT demtype, COUNT(*) FROM sb_demos GROUP BY demtype.demos.B/bans,demos.S/submissions,comments.B/C/S/Pagainst
bans/comms/submissions/protests: all returnzero orphans.
web/demos/after a fresh seed; basenames match the:prefix_demos.filenamecolumn byte-for-byte.with the default seed produce identical filename sets
(
md5sumof sortedlsoutput:ce2189fe…both runs;diffreturns empty).touch web/demos/manual-decoy.dem; re-seed; the decoy survives(not referenced by
:prefix_demos, not in the snapshot)..gitkeeppreserved —.gitkeepis also not in:prefix_demos, so it survives every seed unchanged.db-resetcleanup — confirmedfind … -regex '.*/[0-9a-f]{32}$' -deletematches both shapes the writepath produces (synth + panel
UploadHandlerwithrenameToHash: true) while leaving.gitkeepand anynon-MD5 manual uploads (icon / mapimg flows preserve
original filenames) in place.
counts, demo payload caveat (not playable in the demoviewer),
new wipe ordering contract, and
db-resetcleanup behavior.Synthesizerstill refuses anyDB_NAMEother thansourcebansper the file's docblock, sosourcebans_test/sourcebans_e2eare unaffected.