Per CLAUDE.md rule 3 and QA-03, every Phase 11 package (conga, lagoon seams, pact schedule types, bonfire.Call, lighthouse, lighthouse/centrifugo, flare, beachcomber, beachcomber/typesense, the tide recorder) and every fonoteka binding (ws authorizers, Album broadcast binding, Album Searchable, settings Gate, schedule entry) has branch-level tests, with Postgres tests on testcontainers and skipped only under -short.
The VALIDATION map's named tests exist and pass: TestListenPickupLatency, TestDispatchTransactional, TestOutcome*, TestCancel*, TestQueueClear, TestQueueWork, TestSchedule*, TestCall, TestToken*, TestClient*, TestProxy*, TestWsAuthorizer, TestBroadcastTx, TestSuppression, TestBulkEmitsOnce, TestOnDatabaseAfterActivate, TestBroadcastGoldens, TestSync*, TestAlbumSearchable.
Every high threat marked mitigate in plans 11-01..11-06 (T-11-01, T-11-02, T-11-05, T-11-07, T-11-09, T-11-12, T-11-19, T-11-22, T-11-28) has a named test that fails when its mitigation is removed, proven by an anchor-exact removal check recorded as an RC row in 11-SECURITY-REVIEW.md (10.1-04 precedent).
`scripts/check-phase11.sh --all` runs self-test, hygiene, both repositories' vet and tests (including the fonoteka plugin module patterns and -race on the realtime packages) and the evidence check, fails closed on any skipped or missing named test, and prints `phase11 all passed`.
The hygiene stage refuses application names in the new framework modules and tide's recorder files, refuses the Centrifugo, Typesense and Web Push client libraries and a direct cron library in go.mod, and requires River at v0.47.0; its self-test proves each rule refuses a planted violation for its own reason.
11-VALIDATION.md is validated (nyquist_compliant true, every row mapped to a task and a passing command) and REQUIREMENTS.md marks JOBS-01, CLI-04, CLI-06, RT-01, RT-02, RT-03 and SRCH-01 complete for Phase 11.
statement
verification
Real-client behaviour (the unchanged Nuxt app receiving an album event through a real Centrifugo v6 with a Go-issued token, and a real Typesense 26.0 receiving an upsert) is confirmed manually at /gsd-verify-work.
backstop
path
provides
contains
scripts/check-phase11.sh
phase gate with --self-test, --hygiene, --go, --postgres, --evidence, --all
--evidence requires an RC row per high mitigated threat
RC-
from
to
via
pattern
scripts/check-phase11.sh
../fonoteka.go
go vet/test with ./plugins/golem15/fonoteka/... and ./plugins/golem15/user/...
plugins/golem15/fonoteka/...
requirement_id
category
statement
status
verification
JOBS-01
transparency
The phase gate MUST NOT report success when a named test is skipped, reports no tests to run, or a detector stage is disabled
resolved
test
Phase Goal
ROADMAP Phase 11 goal (verbatim, not in user-story form): River jobs run on the correct dual-driver split, Centrifugo publishing and channel authorization match the existing server, and Typesense sync stays a re-gated pre-filter — all brought up before the API phases that depend on them.
This plan's slice: the phase's code is fully covered, the security mitigations are proven to fail when removed, and one fail-closed gate proves the phase in both repositories (all seven requirements).
Bring full unit and integration coverage to everything Phase 11 added, write the failing-when-broken security evidence, and add the `scripts/check-phase11.sh` gate, following the Phase 10.1 plan 04 shape.
Purpose: CLAUDE.md rule 3 (unit tests are the last plan of every phase) and QA-03. Decisions exercised: D-01..D-20 through their plan tests; this plan adds branch coverage and evidence, not features.
Output: test files across both repositories, the phase gate, 11-SECURITY-REVIEW.md, a validated 11-VALIDATION.md, REQUIREMENTS.md traceability.
Repos: summercms.go (framework tests, gate, planning docs) and fonoteka.go (binding tests). A test that uncovers a real bug gets a separate fix commit with its RED evidence in the SUMMARY (10.1-04 precedent) so every commit stays green. Planning docs and code in separate commits. Never add co-author tags.
@.planning/PROJECT.md
@.planning/ROADMAP.md
@.planning/STATE.md
@.planning/phases/11-jobs-realtime-and-search-infrastructure/11-CONTEXT.md
@.planning/phases/11-jobs-realtime-and-search-infrastructure/11-RESEARCH.md
@.planning/phases/11-jobs-realtime-and-search-infrastructure/11-VALIDATION.md
@.planning/phases/11-jobs-realtime-and-search-infrastructure/11-01-SUMMARY.md
@.planning/phases/11-jobs-realtime-and-search-infrastructure/11-02-SUMMARY.md
@.planning/phases/11-jobs-realtime-and-search-infrastructure/11-03-SUMMARY.md
@.planning/phases/11-jobs-realtime-and-search-infrastructure/11-04-SUMMARY.md
@.planning/phases/11-jobs-realtime-and-search-infrastructure/11-05-SUMMARY.md
@.planning/phases/11-jobs-realtime-and-search-infrastructure/11-06-SUMMARY.md
@.planning/phases/10.1-runtime-admin-extension-point/10.1-SECURITY-REVIEW.md
@scripts/check-phase10.1.sh
@scripts/check-phase10.2.sh
- Prior-phase gate shape: scripts/check-phase10.1.sh and check-phase10.2.sh (stage functions, `--self-test` with scratch plants, detector refusing "no tests to run"/SKIP, `KNOWN_APP_FAILURES=""`, fonoteka patterns `./plugins/golem15/fonoteka/... ./plugins/golem15/user/...`), and 10.1-SECURITY-REVIEW.md (threat rows `| T-... |` plus `| RC-NN | T-... |` removal-check rows produced by an anchor-exact mutation harness that restores files byte for byte).
- Test harnesses: modules/lagoon/postgres_test.go and modules/conga/postgres_test.go (testcontainers postgres:16-alpine, ICU pl-PL, -short skip, Docker failure fails TestMain); fonoteka plugin_boot_test.go (bootSQL, bootDB, bootConfig); fonoteka parity TestMain.
- Plan outputs to test are listed in each plan's "Artifacts this phase produces" section and SUMMARY; read the SUMMARYs for final names and deviations.
Artifacts this phase produces
(This plan's share.)
Tests: every file listed in files_modified ending in _test.go, including the VALIDATION-named tests.
Gate: scripts/check-phase11.sh with --self-test, --hygiene, --go, --postgres, --evidence, --all.
Evidence: 11-SECURITY-REVIEW.md (threat rows and RC rows), validated 11-VALIDATION.md, REQUIREMENTS.md traceability rows for the seven requirement IDs.
Task 1: Jobs, scheduler and lagoon seams are covered branch by branch, including the timed LISTEN proof and every job outcome
Plans 11-01..11-06 are executed: all six SUMMARY files exist in the phase directory and `docker info` exits 0.
modules/conga/manager_test.go, modules/conga/worker_test.go, modules/conga/commands_test.go, modules/conga/schedule_test.go, modules/conga/listen_test.go, modules/lagoon/ondatabase_test.go, modules/lagoon/transaction_test.go, modules/lagoon/queue_migrations_test.go, modules/bonfire/call_test.go, modules/pact/capabilities_test.go, ../fonoteka.go/plugins/golem15/fonoteka/schedule_test.go
modules/conga/*.go (all), modules/lagoon/queue_migrations.go, modules/lagoon/ondatabase.go, modules/lagoon/transaction.go, modules/lagoon/connection.go, modules/bonfire/call.go, modules/pact/capabilities.go, modules/surf/serve.go, the 11-01 and 11-02 SUMMARYs (final names, deviations), .planning/phases/11-jobs-realtime-and-search-infrastructure/11-VALIDATION.md (JOBS-01, CLI-04, CLI-06 rows), .planning/phases/11-jobs-realtime-and-search-infrastructure/11-RESEARCH.md (Pitfalls 1-5, 13)
(1) conga (JOBS-01, CLI-06, D-01..D-05, D-17): `TestOutcomeComplete`, `TestOutcomeFailFinalAttemptOnly` (errors on attempts 1..MaxAttempts-1 keep status 1; final attempt sets 3 with metadata error), `TestOutcomePanicBecomesError`, `TestOutcomeSkipIsCompleteWithMetadata`, `TestCancelQueuedNeverRuns`, `TestCancelRunningCancelsCtx` (row stays 4, not 3), `TestStopJobFromWorker`, `TestManagerPHPSemantics` (StartJob/UpdateJobState/UpdateMetadata/CompleteJob/FailJob/GetMetadata table including the stored `""` metadata, updated_at untouched where PHP leaves it, progress_max default 1 on a missing row, river_job_id set), `TestDispatchActorFromPrincipal` (frontend principal → user_id, is_admin false; backend → is_admin true; none → NULL), `TestDispatchDelayUsesScheduledAt`, `TestRegisterRejectsForeignAndDuplicateJobs` (ErrNotCongaJob, duplicate kind, ErrRegistrationClosed), `TestStartWorkerRejectsNonCongaPluginJob`, `TestQueueWork` (filter works only the named queue; unknown queue is ErrUnknownQueue naming known queues; StartServeWorker returns nil when `queue.work_in_serve` is false), `TestQueueClear` (only available/scheduled/retryable removed across the 10000-row loop; running job untouched; output `Cleared N jobs`), config parsing tests for `queue.job_timeout` int and duration forms. Keep TestListenPickupLatency and TestDispatchTransactional and add a `-count=3` stability run in the gate.
(2) lagoon: TestQueueMigrationsUpDown (fresh DB: migrate creates River v7 tables and summer_jobs with the exact column types and defaults; RollbackLast-style gormigrate rollback of the conga set removes summer_jobs and the River schema; rerun is idempotent), extend TestOnDatabaseAfterActivate (errors surface from Publish; per-app isolation of two apps) and TestTransactionAfterCommit (nested failure drops only inner callbacks, panic in a callback is recovered, implicit single-statement flush only on success, plain gdb.Transaction fallback runs with the tx handle).
(3) scheduler (CLI-04, D-18): TestScheduleNext table (Daily at, before and after the boundary; DST spring-forward and fall-back days in Europe/Warsaw; Every(1m, 5m, 1h, 24h) boundaries), TestScheduleValidation (empty command, zero cadence, non-dividing interval, invalid app.timezone each name the plugin and index), TestScheduleRunOnce table (due/not-due for Daily and Every, unknown command warning plus exit 0, first command error returned after all ran), TestScheduleUniqueByPeriod, TestScheduledEntryMismatchSkipped, TestScheduleMissingCatalog, TestScheduleOrdering (activation then declaration order, entry ids). bonfire TestCall table (args passed, unknown wraps ErrUnknownCommand, empty stdin gives prompt defaults, Catalog.Has). pact TestCadence accessors.
(4) fonoteka.go schedule_test.go TestFonotekaScheduleSkipsUnregisteredPrune (Schedule() returns the daily prune entry; running it through the scheduler worker with the real command catalog logs schedule: command not registered; skipping with command fonoteka:prune-notifications until Phase 14; user decision 5).
go vet ./... && go test ./modules/conga ./modules/lagoon ./modules/bonfire ./modules/pact -count=1 && go test ./modules/conga -run '^(TestListenPickupLatency|TestDispatchTransactional|TestOutcome.|TestCancel.|TestQueueClear|TestQueueWork|TestSchedule.*)$' -count=1 -v && go test ./modules/lagoon -run '^(TestQueueMigrationsUpDown|TestOnDatabaseAfterActivate|TestTransactionAfterCommit)$' -count=1 -v && go test ./modules/bonfire -run '^TestCall$' -count=1 -v && (cd ../fonoteka.go && go test ./plugins/golem15/fonoteka -run '^TestFonotekaScheduleSkipsUnregisteredPrune$' -count=1 -v)
<fails_when>Any command exits non-zero; a verbose run lacks "--- PASS" for any test named in its -run pattern, or prints "no tests to run" or "--- SKIP".</fails_when>
<acceptance_criteria>
- go test ./modules/conga -cover -count=1 reports coverage of at least 80.0% for modules/conga.
- grep -c 'func TestQueueClear' modules/conga/commands_test.go and grep -c 'func TestCancelRunningCancelsCtx' modules/conga/manager_test.go each print 1.
- grep -c 'Europe/Warsaw' modules/conga/schedule_test.go prints at least 1.
- grep -c 'func TestQueueMigrationsUpDown' modules/lagoon/queue_migrations_test.go prints 1.
</acceptance_criteria>
Every job outcome, cancellation path, queue command, scheduler cadence and lagoon seam behaviour is pinned by a named passing test, and the LISTEN pickup proof is stable across repeated runs.
Task 2: Realtime, push, search and the tide recorder are covered, and each high threat has a failing-when-broken test
modules/lighthouse/postgres_test.go, modules/lighthouse/lighthouse_test.go, modules/lighthouse/channel_test.go, modules/lighthouse/registry_test.go, modules/lighthouse/route_test.go, modules/lighthouse/broadcast_test.go, modules/lighthouse/centrifugo/token_test.go, modules/lighthouse/centrifugo/client_test.go, modules/lighthouse/centrifugo/proxy_test.go, modules/lighthouse/centrifugo/commands_test.go, modules/flare/flare_test.go, modules/flare/encrypt_test.go, modules/flare/commands_test.go, modules/beachcomber/postgres_test.go, modules/beachcomber/sync_test.go, modules/beachcomber/typesense/engine_test.go, modules/tide/centrifugo_test.go, ../fonoteka.go/plugins/golem15/fonoteka/ws_authorizer_test.go, ../fonoteka.go/plugins/golem15/fonoteka/album_realtime_test.go, ../fonoteka.go/plugins/golem15/fonoteka/album_search_test.go
modules/lighthouse/**/*.go, modules/flare/*.go, modules/beachcomber/**/*.go, modules/tide/centrifugo.go, modules/tide/centrifugo_golden.go, the 11-03..11-06 SUMMARYs, ../fonoteka.go/plugins/golem15/fonoteka/{realtime.go, search.go, classes/ws/*.go, models/album_search.go, realtime_smoke_test.go, search_smoke_test.go}, /media/nvme/dev/golem15/fonoteka/plugins/golem15/websockets/tests/security/*.php (AccessControlTest, AuthenticationTest, DataHandlingTest, InjectionTest — port their cases), .planning/phases/11-jobs-realtime-and-search-infrastructure/11-VALIDATION.md (RT-01..RT-03, SRCH-01 rows), .planning/phases/10.1-runtime-admin-extension-point/10.1-SECURITY-REVIEW.md (removal-check format)
(1) lighthouse (framework, acme names only; Postgres harness copied from conga): `TestFromSelectsDriver` (null default, memory, log, unknown lists names), `TestParseChannelTable` and `TestChannelIDMatchesPHP` (the php -r table from 11-03), `TestFormatChannels` (lowercase, namespace prefix once), `TestRegistry` (duplicate, empty and colon namespaces rejected, sorted Namespaces, concurrent Get under -race), `TestMountSurfaces` (UserAuth group gets UserAuth then Middleware in order; ServerToServer in a raw group; UserAuth route with empty surface is an error; null driver mounts nothing), `TestBroadcastTx` (commit publishes once through the memory driver after the worker runs; rollback publishes nothing), `TestSuppression` (WithoutBroadcasting[acme.Widget] silences Widget but not acme.Gadget; stale outer ctx not suppressed), `TestBulkEmitsOnce` (N suppressed creates plus one Emit give exactly one publication), `TestBroadcastEdges` (zero-PK batch update skipped, delete snapshot from an id-only model, ShouldBroadcast veto, default alias and event name, default payload keys and ttl 60, savepoint keeps the write alive when enqueue fails, publish failure logged and not retried).
(2) centrifugo: TestTokenClaims (ForUser exact claim set sub/exp/info{name} with a fixed clock, null name, Subscription/Anonymous/ForIdentifier [] info/SubscriptionForIdentifier, empty secret ErrNotConfigured), TestTokenHandler (401 no principal, 401 unknown user, 503 empty secret after the user check, 200 body shape, headers, no trailing newline, concurrent requests under -race), TestClientRequests (exact paths, apikey header, ordered body keys, timestamp +00:00, 2xx success, non-2xx error without the key, empty key sends nothing, presence/unsubscribe/info bodies), TestProxy table porting every PHP security test case plus the RT-02 edge truths (secret missing/wrong/empty-configured, user ""/"0"/number/string, presence:presence:, 4 segments, unknown namespace, allow {"result":{"info":[]}} bytes, presence allow/override merge, deny body bytes and HTTP 200, 64 KiB cap, log line never containing the secret), TestHealthCommand.
(5) tide: TestCentrifugoRecorder (records publish/broadcast only, authorization flag without storing the header, non-loopback listen refused, body cap) and TestNormalizePublications (only timestamps, actor and captured ids masked; DiffPublications reports count/path/body differences).
(6) fonoteka: TestWsAuthorizer (member owner and editor allowed; non-member denied; wishlist id under collection namespace denied; presence-prefixed collection channel denied; wishlist subscriber and household peer allowed; stranger denied; editor removal flips to deny; malformed and unknown user denied), TestAlbumBroadcastBinding (channels only for kind=collection, deleted payload without album, created payload with album, actor for frontend/backend/no principal), TestAlbumSearchable (document fields and types against a PHP-shaped expectation, artist pivot order, medium map, collection_id 0 refused, schema fields, settings Gate on/off/error).
(7) Removal checks: for each high mitigated threat (T-11-01 proxy secret compare, T-11-02 parseChannel segment/presence rules and authorizer kind predicate, T-11-05 enqueue inside the write tx and the kind=collection channel rule, T-11-07 positive collection_id refusal, T-11-09 entry-match check, T-11-12 Dispatch on the caller tx, T-11-19 Mount empty-UserAuth refusal, T-11-22 push allowlist, T-11-28 fixture secret references), write an anchor-exact mutation helper (a small Go test helper or script function used by the gate) that removes the protection, runs the named test, requires it to fail, and restores the file byte for byte; record the results for Task 3.
go vet ./... && go test ./modules/lighthouse/... ./modules/flare ./modules/beachcomber/... ./modules/tide -count=1 -race && go test ./modules/lighthouse/... -run '^(TestBroadcastTx|TestSuppression|TestBulkEmitsOnce|TestToken.|TestClient.|TestProxy.)$' -count=1 -v && go test ./modules/beachcomber/... -run '^TestSync.$' -count=1 -v && (cd ../fonoteka.go && go test ./plugins/golem15/fonoteka -run '^(TestWsAuthorizer|TestAlbumBroadcastBinding|TestAlbumSearchable)$' -count=1 -race -v)
<fails_when>Any command exits non-zero; a verbose run lacks "--- PASS" for any test named in its -run pattern, or prints "no tests to run", "--- SKIP" or "DATA RACE".</fails_when>
<acceptance_criteria>
- go test ./modules/lighthouse/... ./modules/beachcomber/... ./modules/flare -cover -count=1 reports at least 80.0% coverage for lighthouse, lighthouse/centrifugo, beachcomber, beachcomber/typesense and flare.
- grep -c 'func TestWsAuthorizer' ../fonoteka.go/plugins/golem15/fonoteka/ws_authorizer_test.go prints 1 and grep -c 'func TestProxy' modules/lighthouse/centrifugo/proxy_test.go prints at least 1.
- Each of the nine high threats listed in action (7) has a removal check that made its named test fail and a byte-for-byte restore (cmp of the mutated file against the copy saved before mutation succeeds).
</acceptance_criteria>
Realtime, push, search and recorder code is covered, every RT/SRCH validation row has its named passing test, and each high threat's test is proven to fail when its protection is removed.
Task 3: One fail-closed phase gate, the security review, the validated test map and requirement traceability
scripts/check-phase11.sh, .planning/phases/11-jobs-realtime-and-search-infrastructure/11-SECURITY-REVIEW.md, .planning/phases/11-jobs-realtime-and-search-infrastructure/11-VALIDATION.md, .planning/REQUIREMENTS.md
scripts/check-phase10.1.sh, scripts/check-phase10.2.sh, .planning/phases/10.1-runtime-admin-extension-point/10.1-SECURITY-REVIEW.md, .planning/phases/11-jobs-realtime-and-search-infrastructure/11-VALIDATION.md, every 11-0N-PLAN.md threat_model block (threat IDs, severities, dispositions), .planning/REQUIREMENTS.md (Traceability table)
(1) scripts/check-phase11.sh (mode 100755, bash strict mode), modelled on check-phase10.1.sh: stages `--hygiene` (application-name regex `pl[yý]tarium|fonoteka|albumy|kolekcj|winyl|p[lł]yt[aęy]` over tracked files in modules/conga, modules/lighthouse, modules/flare, modules/beachcomber, modules/tide/centrifugo*.go; `go list -m all` must not contain the Centrifugo Go client, the Typesense Go client, a Web Push library or a direct cron library, and must pin github.com/riverqueue/river at v0.47.0; each new module has README.md and a root README row), `--go` (summercms.go `go vet ./...` and `go test ./...`; `go test -race` on modules/lighthouse/... modules/beachcomber/... modules/flare; `go test ./modules/conga -run '^TestListenPickupLatency$' -count=3`), `--postgres` (fonoteka.go `go vet ./... ./plugins/golem15/fonoteka/... ./plugins/golem15/user/...` and `go test ./... ./plugins/golem15/fonoteka/... ./plugins/golem15/user/...` with KNOWN_APP_FAILURES empty), `--named` (runs every VALIDATION-named test with -v and refuses any "--- SKIP", "no tests to run" or missing "--- PASS" line, except TestBroadcastGoldens/created and /updated which must be SKIP with the Phase 12 pending text), `--evidence` (11-SECURITY-REVIEW.md has one `| T-11-NN |` row per threat ID declared in the phase plans plus T-11-SC, and an `| RC-NN | T-11-NN |` row for every high mitigated threat; 11-VALIDATION.md has `nyquist_compliant: true` and no pending rows), `--self-test` (scratch copies with one planted violation per rule, each refused with its own reason, plus a clean look-alike accepted), and `--all` (self-test, hygiene, go, postgres, named, evidence) printing `phase11 all passed`. Each detector returns immediately on its first violation (10.2 fail-open lesson).
(2) 11-SECURITY-REVIEW.md: frontmatter with reviewer note, one row per threat T-11-01..T-11-30 and T-11-SC copying each plan's severity and disposition verbatim (T-11-08 accepted with its Phase 13 rationale), the mitigating file:function and the named test; RC-01.. rows for the nine high threats with the mutation anchor and the observed failing test.
(3) 11-VALIDATION.md: fill Task IDs, plans and waves for every row, flip statuses to green with the commands actually run, set status: validated, nyquist_compliant: true, wave_0_complete: true, keep the two manual-only rows as manual.
(4) REQUIREMENTS.md: mark JOBS-01, CLI-04, CLI-06, RT-01, RT-02, RT-03 and SRCH-01 [x] and Complete in the Traceability table (planning-docs commit separate from the gate script commit).
bash -n scripts/check-phase11.sh && scripts/check-phase11.sh --self-test && scripts/check-phase11.sh --all
<fails_when>Non-zero exit from any of the three commands; --all output lacks the line "phase11 all passed" or contains a line starting with "refuse:".</fails_when>
At /gsd-verify-work, collect the two manual rows of 11-VALIDATION.md: (1) start Centrifugo v6 with the production-shaped config, run fonoteka serve with the real secrets, log in through the unchanged Nuxt app, edit an album and confirm the browser receives the event; (2) start typesense/typesense:26.0, enable search_use_typesense in the admin settings, save an album and query the golem15_fonoteka_albums collection for it.
<acceptance_criteria>
- test -x scripts/check-phase11.sh succeeds.
- grep -cE '^\| T-11-(0[1-9]|[12][0-9]|30|SC) \|' .planning/phases/11-jobs-realtime-and-search-infrastructure/11-SECURITY-REVIEW.md prints 31.
- grep -cE '^\| RC-[0-9]+ \| T-11-' .planning/phases/11-jobs-realtime-and-search-infrastructure/11-SECURITY-REVIEW.md prints at least 9.
- grep -c 'nyquist_compliant: true' .planning/phases/11-jobs-realtime-and-search-infrastructure/11-VALIDATION.md prints 1.
- grep -cE '^\| (JOBS-01|CLI-04|CLI-06|RT-01|RT-02|RT-03|SRCH-01) \| Phase 11 \| Complete \|' .planning/REQUIREMENTS.md prints 7.
</acceptance_criteria>
One command proves Phase 11 in both repositories and refuses every planted regression; the security review ties each threat to a test and each high threat to a removal check; validation and requirement traceability are complete.
<threat_model>
Trust Boundaries
Boundary
Description
Gate script → verification verdict
A passing gate is the phase's acceptance signal
Mutation harness → working tree
Removal checks edit source files temporarily
STRIDE Threat Register
Threat ID
Category
Component
Severity
Disposition
Mitigation Plan
T-11-21
Repudiation
phase gate fail-open
medium
mitigate
Each detector returns on its first violation; --self-test proves every rule refuses a planted violation for its own reason; skipped or missing named tests fail the gate (Task 3).
T-11-SC
Tampering
Go module installs
high
mitigate
No module added; the hygiene stage refuses the excluded client libraries and requires River v0.47.0 (Task 3).
</threat_model>
`scripts/check-phase11.sh --all` prints "phase11 all passed"; `scripts/check-phase10.1.sh --all` still passes (no regression of the prior gate); the two manual rows are collected at /gsd-verify-work.
<success_criteria>
Branch-level tests for all Phase 11 code in both repositories, with the VALIDATION-named tests passing and Postgres tests on testcontainers.
Nine high threats proven by removal checks; 11-SECURITY-REVIEW.md complete.
check-phase11.sh fail-closed with a passing self-test; 11-VALIDATION.md validated; seven requirements marked complete.
</success_criteria>
Create `.planning/phases/11-jobs-realtime-and-search-infrastructure/11-07-SUMMARY.md` when done.