20 KiB
phase, plan, subsystem, tags, requires, provides, affects, actuals, plan_head_before, plan_head_after, tech-stack, key-files, key-decisions, patterns-established, requirements-completed, coverage, duration, completed, status
| phase | plan | subsystem | tags | requires | provides | affects | actuals | plan_head_before | plan_head_after | tech-stack | key-files | key-decisions | patterns-established | requirements-completed | coverage | duration | completed | status | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 11-jobs-realtime-and-search-infrastructure | 06 | testing |
|
|
|
|
|
fb4aed176a |
5382947ef8 |
|
|
|
|
|
|
23min | 2026-09-30 | complete |
Phase 11 Plan 06: Centrifugo broadcast goldens and realtime route parity Summary
tide now has a loopback fake Centrifugo recorder, broadcast golden files, a normaliser and a structural diff, driven by summer parity:broadcasts. PHP's album delete and bulk-add publications were recorded from isolated PHP, and the Go app reproduces them. The created and updated goldens are committed and pending Phase 12. The Nuxt token route and Centrifugo's subscribe proxy are recorded as 10 PHP cases and replay green against Go.
Performance
- Duration: 23 min
- Started: 2026-09-30T11:08:51Z
- Completed: 2026-09-30T11:32:21Z
- Tasks: 3
- Files modified: 39 (10 in summercms.go, 29 in fonoteka.go)
Accomplishments
- tide recorder and goldens (D-10, user decision 5).
NewCentrifugoRecorderrecords POSTs to…/publishand…/broadcastas{method, path, authorization, body}.authorizationis only true when the header wasapikey <key>, and the key is never stored. Bodies are capped at 1 MiB.ListenAndServeuses the proxy's loopback rule (T-02-01, T-11-29).NormalizePublicationsmasks only these values:data.timestampanddata.payload.timestampin the+00:00ISO shape;data.payload.actorwhen it is exactly{user_id, name};- captured
id:*values (T-11-30).
DiffPublicationschecks the count, method, path, authorization flag and body. The body diff is structural and ignores key order.RecordBroadcastsandparity:broadcastsrefuse non-loopback targets. They refuse to write a golden that is empty or contains the API key, and they refuse a token-shaped body (T-11-28).
- PHP evidence. Each PHP event produces exactly one publication. A soft delete publishes only
deleted.fonoteka.album{id, collection_id, action, actor, timestamp}to/api/publish. Bulk publishes onlycollection.bulk_updated{"reason":"bulk_create","count":2}. - Go matches.
TestBroadcastGoldens/deletedand/bulkpass: they boot the app with a River worker and the recorder standing in for Centrifugo.createdandupdatedare reported as SKIP (pending). - Realtime routes (D-12, D-13, RT-01, RT-02).
- The token route has two cases: alice's bearer gets 200 with the token captured as
jwt:centrifugo, and a request without a bearer gets 401Token not provided. - The subscribe proxy has eight cases. Only the member case is allowed, with
{"result":{"info":[]}}. The other seven are HTTP 200 denies. - The corpus now reports 171 recorded, 33 ported, 33 passing, 0 failing and 138 pending, and the routes.php digest lock is unchanged.
- The token route has two cases: alice's bearer gets 200 with the token captured as
Task Commits
summercms.go:
- Task 1: tide recorder, goldens and parity:broadcasts:
9ecbf74(feat) - Task 3 (Rule 1 fix): jwt.auth 401 Cache-Control:
5382947(fix)
fonoteka.go:
- Task 1: deleted golden, php_parity.sh env, TestBroadcastGoldens/deleted:
1223fd7(feat) - Task 2: bulk golden, created/updated pending goldens, README section:
bd17fdb(feat) - Task 3: genre security test expects the PHP 401 header:
2164495(test) - Task 3: realtime routes recorded and replayed:
504f6d6(feat)
Files Created/Modified
modules/tide/centrifugo.go:CentrifugoRecorder,Publication,RecordBroadcasts,BroadcastConfigand the defaults.modules/tide/centrifugo_golden.go:BroadcastGoldenload and write, an ordered JSON codec,NormalizePublicationsandDiffPublications.modules/tide/centrifugo_test.go: smoke tests for the recorder, normaliser, diff, round trip and step recording.modules/tide/README.md: Features, Usage, API rows, and a new CLI commands section with the golden format.cmd/summer/parity.go,main.go,main_test.go: theparity:broadcastscommand.modules/bouncer/jwt.go,jwt_test.go,README.md: the 401 now carriesCache-Control: no-cache, private.- fonoteka.go
parity/:php_parity.sh: theCENTRIFUGO_*values;- broadcast flows and goldens;
broadcast_goldens_test.goandrealtime_seed_test.go;- manifest, snapshot,
check_corpus.goand count updates; - capture rules;
- the README sections "Broadcast goldens" and "Realtime routes".
Decisions Made
See key-decisions in the frontmatter.
Deviations from Plan
Auto-fixed Issues
1. [Rule 1 - Bug] The jwt.auth 401 lacked PHP's Cache-Control header
- Found during: Task 3 (Go replay of the
no-bearercase) - Issue: PHP's 401 carries
Cache-Control: no-cache, private, but bouncer'swrite401did not send it, so the replay failed withheader.Cache-Control: expected no-cache, private actual <missing>. - Fix:
write401now sets the header. The bouncer test asserts it, and the README mentions it.genre_security_test.gohad used a missing Cache-Control header to prove the handler was not reached. It now asserts the header is present and relies on the jwt.auth body for that proof. - Files modified: modules/bouncer/jwt.go, jwt_test.go, README.md; ../fonoteka.go/parity/genre_security_test.go
- Verification:
go test ./...passes in both repos, and so doesTestParityCorpus/GET__api_realtime_token_realtime. - Committed in:
5382947(summercms.go), 2164495 (fonoteka.go)
2. [Rule 3 - Blocking] The token secret was too short for php-jwt
- Found during: Task 3 (recording)
- Issue: PHP returned 500 with
DomainException: Provided key is too short, because firebase/php-jwt requires at least 32 bytes for HS256 and the plannedparity-centrifugo-token-secretis 30 bytes. - Fix: The test-only default is now
parity-centrifugo-token-secret-test-only(40 bytes) in php_parity.sh, the Go constant, the corpus scan list and the README. - Committed in: 504f6d6
3. [Rule 3 - Blocking] The route capture rule fired on the no-bearer case
- Found during: Task 3 (recording)
- Issue: The
capture-rules.yamljwt:centrifugorule is merged into every case without its own captures. It therefore failed on the 401no-bearercase, whose response has no$.token. - Fix: The capture is declared on the manifest
caseitself (the same rule), and recording runs without--rules.capture-rules.yamlgets only akeep_request_headersentry for the subscribe route. It deliberately has no capture: a capture from the request header would overwrite the vars value with the placeholder text or with the wrong-secret literal. - Committed in: 504f6d6
4. [Rule 2 - Missing critical] Corpus secret scan for Centrifugo values (T-11-28)
- The scan in
check_corpus.gonow fails on any of the three php_parity.sh Centrifugo values. It also fails on anX-Centrifugo-Secretheader that is neither a{{var}}reference nor the documentednot-the-proxy-secretdeny literal.TestUniqueAndSecretScancovers both checks. - Committed in: 504f6d6
5. [Scope notes]
- Presence case. The must-have says "allow on a presence channel (allow and override keys)", but PHP cannot allow any presence channel in this app. For
presence:collection:<id>,CollectionChannelAuthorizerparses segment 1 (collection) as the id, gets 0 and denies. As the plan's fallback allows, the corpus records the PHP deny, and Go matches it. The allow-and-override shape stays covered by 11-03's smoke test with a test authorizer. - Normaliser scope.
NormalizePublicationsmasks ids only underid,*_idand*_idskeys, and as the numeric tail of a channel name. The plan said "any string or number equal to a captured id". Masking every equal number would have hiddencount: 2whenever a collection id is 2. This narrower rule masks less than the plan allowed, which is consistent with the transparency prohibition. - Extra API and flags.
RecordBroadcasts,BroadcastConfig,DefaultCentrifugoListen,DefaultBroadcastSettleandMaxPublicationBodyare new exported names, andparity:broadcastsgains--step,--ids,--rules,--settleand--pending. All of these are documented and checked with go doc. - Extra fixture files. The two planned fixture paths hold the
caseandmembercases. The other cases use the corpus's__<case>suffix convention. - Extra modified files.
parity_contract_test.go(the allow-list and count),migrate_test.go(testConfigrealtime keys) andcheck_corpus_test.gowere changed although the plan did not list them. - Artist id. The lifecycle flow also captures
id:artist, so the Phase 12 album subtree has no coincidental id matches. The first recording masked an artist id as{{id:album}}because the two values were equal. The goldens were then re-recorded.
Total deviations: 4 auto-fixed (1 bug, 2 blocking, 1 missing critical), plus the scope notes above. Impact on plan: Fix 1 is a real parity fix in the framework guard, a non-breaking header addition. Fixes 2 and 3 were needed before PHP could be recorded at all. The scope notes narrow the masking and document one PHP behaviour. No scope creep.
Issues Encountered
pkill -f "artisan serve …"matched the invoking shell and exited 144, which left thephp -Schild running with the old environment. The server was then stopped by its PID and restarted.- The deferred item from 11-05 about lighthouse callbacks running after commit for plain
gdb.Createdid not affect these goldens. All Go writes inTestBroadcastGoldensgo throughlagoon.Transaction, so it stays deferred to 11-07.
Known Stubs
None. The pending created/updated goldens are intentional (the plan's user decision 5). They are tracked in .planning/WINDOWS.md as a skipped-test entry for Phase 12.
User Setup Required
None. Recording needs the isolated PHP stack and PARITY_CENTRIFUGO_API_KEY; both are documented in the fonoteka.go parity README.
Next Phase Readiness
- 11-07 (unit tests) should cover these recorder edges:
- 405 and 413;
waitListeningfailures;flowIDNames;decodePlaceholderJSONerror paths;- the
varsOutsideDirrefusal; - the
parity:broadcastsflag errors.
- Phase 12 must turn
created.yamlandupdated.yamlinto assertions (delete thet.Skipand resolve the WINDOWS.md entry). Their album subtree carries literalcreated_atandupdated_atvalues, which Phase 12 must handle, for example with a broadcast normaliser extension or by re-recording with a fixed clock. It uses{{id:album}}and{{id:artist}}.
Phase: 11-jobs-realtime-and-search-infrastructure Completed: 2026-09-30
Self-Check: PASSED
- All 13 key created files checked exist on disk.
- summercms.go commits
9ecbf74and5382947exist, and so do fonoteka.go commits 1223fd7, bd17fdb, 2164495 and 504f6d6. Both working trees are clean apart from the pre-existing.planning/milestone.lockandstate.jsonchanges. - In summercms.go,
go vet ./... && go test ./...passes. In fonoteka.go, vet and test of./...,./plugins/golem15/fonoteka/...and./plugins/golem15/user/...pass. TestBroadcastGoldens: deleted and bulk PASS, created and updated SKIP.TestParityCorpus:recorded 171/171 passing 33 failing 0 unrecorded 0 pending 138.check_corpus.go --require-recorded --require-clients --check-secretspasses.- Every acceptance-criteria grep and go doc check of the three tasks passes.