feat(modules): venue-module registry — entitled ∩ activated, requireModule, Setup panel

Groundwork for the Car Wash pilot (wiki/decisions/venue-modules.md, build-order
steps 1 + 3). No Car Wash code yet; validation is the first module behind the
seam, unchanged in behaviour.

- @parking/shared: MODULE_IDS, ModuleManifest, MODULES (parking required;
  validation dependsOn parking), parseEntitledModules / resolveModuleActivation
  / effectiveModules as pure functions.
- DB: site_config.modules_json (migration 0026, hand-written + journal;
  additive, nullable = everything entitled).
- Server: modules.ts (entitledModules from MODULES_ENTITLED env, activated
  from site_config, effective set, requireModule preHandler → 403
  module_disabled); modules/index.ts registers folder-based modules by
  iterating the registry (modules/validation); site-config GET exposes
  modules/modulesEntitled/modulesActivated, PUT takes the full desired set,
  enforces entitlement + dependency rules (400 with reason) and signs one
  config_change per module that actually flips; /api/auth/me carries the
  effective set; validation routes guarded requireModule → requirePermission.
- Web: lib/modules.ts + modules/{index,validation}; router.tsx spreads
  WEB_MODULES into nav + route tree (validate route no longer named there);
  Setup → Site "Modules" panel (required shown disabled, dependencies as
  hints, server refusal shown verbatim); validation sections + programs fetch
  gated on the module; App invalidates the router whenever the session
  changes (route-context consumers only re-read on navigation — the nav was
  stale after a flip, and after every other setUser too).
- Lavazh validation station retired (STATIONS = ["bar"]; rows untouched).
- Deploy: MODULES_ENTITLED=parking,validation explicit in both booth stacks;
  documented in .env.example.
- Tests: modules.test.ts (7); suite 329/329; web build clean; Playwright
  round-trip on /setup/site verified live.

Claude-Session: https://claude.ai/code/session_01FWncR69HgGPuei1dLrW3cU
This commit is contained in:
2026-09-05 11:04:39 +02:00
parent db9c3e0e31
commit 23d6379be8
27 changed files with 848 additions and 57 deletions
+15
View File
@@ -2922,3 +2922,18 @@ for the Bar until a Bar module exists and absorbs it. Only the Lavazh station is
Wash ships (Car Wash sponsors parking via its own order event). Two sponsorship mechanisms
coexist for now, accepted. Build order updated; validation is registered as its own module in the
registry so Bar can later depend on or absorb it.
## [2026-09-05] feat | Venue-module registry built (pilot groundwork): entitled ∩ activated, requireModule, Setup panel
Implemented build-order steps 1 + 3 of [[venue-modules]]: Lavazh validation station retired
(STATIONS = ["bar"], rows untouched); @parking/shared gains MODULE_IDS / ModuleManifest / MODULES
(parking required, validation dependsOn parking) plus the pure rule functions; site_config
gets modules_json (migration 0026, hand-written — drizzle-kit generate needs a TTY and the
snapshots end at 0003); server modules.ts adds requireModule(db, id) (403 module_disabled,
composed before requirePermission), modules/index.ts registers folder-based modules by
iterating the registry (validation is the first), site-config GET/PUT expose and set the
activation with dependency rules and one signed config_change per module that actually flips,
/api/auth/me carries the effective set; web gains lib/modules.ts + modules/{index,validation}
and router.tsx spreads WEB_MODULES into nav + route tree, SiteSettings gets a Modules panel and
hides the validation section when the module is off; MODULES_ENTITLED set explicitly in both
booth stacks. 7 new server tests, suite 329/329, web build clean. Car Wash next.