ForgeKitFORGEKIT

Build Log

Game Builder's Parent Approval Gate Gets Real Persistence, and Witness Gets Its First Real Test

August 25, 2026

Replaced Game Builder's file-backed, race-prone parent-approval-gate store with real Neon/Drizzle persistence and a genuinely atomic concurrency fix, while running the ForgeKit Strategic Validation Sprint H2 protocol (Witness at the commitment boundary) for the first time on real, non-OS-diagnostic product work.

Game BuilderDatabaseConcurrencyWitness ExperimentChild Safety
1
real safety-critical race condition fixed
1
Witness invocation, 1 real CHALLENGE
5
cheaper-verifier classifications made before any Witness call
0
control-plane infrastructure built (by design)

Timeline

Start
Charge received: test Witness at the commitment boundary, on real product work outside OS diagnostics
Two research agents surveyed Forge/Hearth/Leashline/PilotLight/MedinaCHF (excluded)/experiments/civic/Known for real, stated, unshipped work with genuine judgment
~30m
Chose Game Builder's approval-gate persistence gap
Self-documented twice by its own author (store.ts header, docs/parent-approval-gate.md §7): file-backed store won't survive a Vercel deploy, and has a named TOCTOU race
~1h
Concurrency-semantics question resolved via RETRIEVAL, not Witness
Read @neondatabase/serverless's own installed README directly — confirmed neon-http (what forgekit-core's createDb uses) does not support held locks/interactive transactions, ruling out SELECT...FOR UPDATE and pointing to a single atomic conditional UPDATE instead
~1.5h
Schema, store, and service rewritten; app typechecks and builds clean
publish_requests + audit_log tables added; decideRequest's TOCTOU replaced with one atomic UPDATE...WHERE status='pending'...RETURNING
~2h
Real regression test written (unrun — no live DB configured)
Promise.allSettled racing two decideRequest calls with no await between them; confirmed it fails honestly (clear missing-DATABASE_URL error), not silently
~2.25h
Witness invoked on the atomicity claim — real CHALLENGE
Witness correctly identified the claim rested on documentation/general-ORM reasoning, not on inspecting the actual generated SQL for this exact query shape
~2.5h
Obtained the missing primary evidence directly
Introspected the real query via Drizzle's own .toSQL() — confirmed one single UPDATE statement, no hidden read. Recorded verbatim in store.ts's own comment.
End
Doc updated honestly, second Witness opportunity considered and declined
No second genuinely distinct consequential commitment existed in this build — did not force a second Witness call to hit the 'up to 2' allowance

What shipped

experiments/game-builder/lib/db/schema.ts — new publish_requests and audit_log Postgres tables, replacing publish-requests.json and audit-log.jsonl

experiments/game-builder/lib/db/index.ts — createDb wiring, same pattern as Hearth/Forge

experiments/game-builder/lib/approval-gate/store.ts — rewritten for real DB access; decideAtomically() replaces the old read-then-write decideRequest logic with one atomic conditional UPDATE

experiments/game-builder/lib/approval-gate/service.ts — decideRequest() now distinguishes 'lost the race' from 'not found' honestly via decideAtomically()'s null return, not a thrown generic error

experiments/game-builder/lib/approval-gate/service.test.ts — real-DB concurrency regression test (Promise.allSettled, no vi.mock), verified to fail honestly without a live DB, not yet run against one

experiments/game-builder/drizzle.config.ts, package.json (added @neondatabase/serverless, drizzle-orm, drizzle-kit, forgekit-core, forgekit-testing, dotenv, tsx; added db:push and test scripts)

experiments/game-builder/docs/parent-approval-gate.md §7 — items 1 and 3 marked RESOLVED with what was verified and what remains a real deployment-time gap (no live Neon DB provisioned yet)