pgserve 2.1.3 → 2.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (228) hide show
  1. package/CHANGELOG.md +86 -0
  2. package/README.md +105 -1
  3. package/bin/autopg-wrapper.cjs +16 -0
  4. package/bin/pgserve-wrapper.cjs +31 -6
  5. package/bin/postgres-server.js +56 -0
  6. package/console/README.md +131 -0
  7. package/console/api.js +173 -0
  8. package/console/app.jsx +483 -0
  9. package/console/colors_and_type.css +227 -0
  10. package/console/components.jsx +167 -0
  11. package/console/console.css +1666 -0
  12. package/console/data.jsx +350 -0
  13. package/console/index.html +31 -0
  14. package/console/screens/databases.jsx +5 -0
  15. package/console/screens/health.jsx +5 -0
  16. package/console/screens/ingress.jsx +5 -0
  17. package/console/screens/optimizer.jsx +5 -0
  18. package/console/screens/rlm-sim.jsx +5 -0
  19. package/console/screens/rlm-trace.jsx +5 -0
  20. package/console/screens/security.jsx +5 -0
  21. package/console/screens/settings.jsx +611 -0
  22. package/console/screens/sql.jsx +5 -0
  23. package/console/screens/sync.jsx +5 -0
  24. package/console/screens/tables.jsx +5 -0
  25. package/console/tweaks-panel.jsx +425 -0
  26. package/package.json +11 -1
  27. package/src/cli-config.cjs +310 -0
  28. package/src/cli-install.cjs +98 -11
  29. package/src/cli-restart.cjs +228 -0
  30. package/src/cli-ui.cjs +580 -0
  31. package/src/cluster.js +43 -38
  32. package/src/postgres.js +141 -19
  33. package/src/settings-loader.cjs +235 -0
  34. package/src/settings-migrate.cjs +212 -0
  35. package/src/settings-pg-args.cjs +146 -0
  36. package/src/settings-schema.cjs +422 -0
  37. package/src/settings-validator.cjs +416 -0
  38. package/src/settings-writer.cjs +288 -0
  39. package/.claude/context/windows-debug.md +0 -119
  40. package/.genie/AGENTS.md +0 -15
  41. package/.genie/agents/README.md +0 -110
  42. package/.genie/agents/analyze.md +0 -176
  43. package/.genie/agents/forge.md +0 -290
  44. package/.genie/agents/garbage-cleaner.md +0 -324
  45. package/.genie/agents/garbage-collector.md +0 -596
  46. package/.genie/agents/github-issue-gc.md +0 -618
  47. package/.genie/agents/review.md +0 -380
  48. package/.genie/agents/semantic-analyzer/find-duplicates.md +0 -90
  49. package/.genie/agents/semantic-analyzer/find-orphans.md +0 -99
  50. package/.genie/agents/semantic-analyzer.md +0 -101
  51. package/.genie/agents/update.md +0 -182
  52. package/.genie/agents/wish.md +0 -357
  53. package/.genie/brainstorms/pgserve-v2/DESIGN.md +0 -174
  54. package/.genie/code/AGENTS.md +0 -694
  55. package/.genie/code/agents/audit/risk.md +0 -173
  56. package/.genie/code/agents/audit/security.md +0 -189
  57. package/.genie/code/agents/audit.md +0 -145
  58. package/.genie/code/agents/challenge.md +0 -230
  59. package/.genie/code/agents/change-reviewer.md +0 -295
  60. package/.genie/code/agents/code-garbage-collector.md +0 -425
  61. package/.genie/code/agents/code-quality.md +0 -410
  62. package/.genie/code/agents/commit-suggester.md +0 -255
  63. package/.genie/code/agents/commit.md +0 -124
  64. package/.genie/code/agents/consensus.md +0 -204
  65. package/.genie/code/agents/daily-standup.md +0 -722
  66. package/.genie/code/agents/docgen.md +0 -48
  67. package/.genie/code/agents/explore.md +0 -79
  68. package/.genie/code/agents/fix.md +0 -100
  69. package/.genie/code/agents/git/commit-advisory.md +0 -219
  70. package/.genie/code/agents/git/workflows/issue.md +0 -244
  71. package/.genie/code/agents/git/workflows/pr.md +0 -179
  72. package/.genie/code/agents/git/workflows/release.md +0 -460
  73. package/.genie/code/agents/git/workflows/report.md +0 -342
  74. package/.genie/code/agents/git.md +0 -432
  75. package/.genie/code/agents/implementor.md +0 -161
  76. package/.genie/code/agents/install.md +0 -515
  77. package/.genie/code/agents/issue-creator.md +0 -344
  78. package/.genie/code/agents/polish.md +0 -116
  79. package/.genie/code/agents/qa.md +0 -653
  80. package/.genie/code/agents/refactor.md +0 -294
  81. package/.genie/code/agents/release.md +0 -1129
  82. package/.genie/code/agents/roadmap.md +0 -885
  83. package/.genie/code/agents/tests.md +0 -557
  84. package/.genie/code/agents/tracer.md +0 -50
  85. package/.genie/code/agents/update/upstream-update.md +0 -85
  86. package/.genie/code/agents/update/versions/generic-update.md +0 -305
  87. package/.genie/code/agents/vibe.md +0 -1317
  88. package/.genie/code/spells/agent-configuration.md +0 -58
  89. package/.genie/code/spells/automated-rc-publishing.md +0 -106
  90. package/.genie/code/spells/branch-tracker-guidance.md +0 -28
  91. package/.genie/code/spells/debug.md +0 -320
  92. package/.genie/code/spells/emoji-naming-convention.md +0 -303
  93. package/.genie/code/spells/evidence-storage.md +0 -26
  94. package/.genie/code/spells/file-naming-rules.md +0 -35
  95. package/.genie/code/spells/forge-code-blueprints.md +0 -195
  96. package/.genie/code/spells/genie-integration.md +0 -153
  97. package/.genie/code/spells/publishing-protocol.md +0 -61
  98. package/.genie/code/spells/team-consultation-protocol.md +0 -284
  99. package/.genie/code/spells/tool-requirements.md +0 -20
  100. package/.genie/code/spells/triad-maintenance-protocol.md +0 -154
  101. package/.genie/code/teams/tech-council/council.md +0 -328
  102. package/.genie/code/teams/tech-council/jt.md +0 -352
  103. package/.genie/code/teams/tech-council/nayr.md +0 -305
  104. package/.genie/code/teams/tech-council/oettam.md +0 -375
  105. package/.genie/neurons/README.md +0 -193
  106. package/.genie/neurons/forge.md +0 -106
  107. package/.genie/neurons/genie.md +0 -63
  108. package/.genie/neurons/review.md +0 -106
  109. package/.genie/neurons/wish.md +0 -104
  110. package/.genie/product/README.md +0 -20
  111. package/.genie/product/cli-automation.md +0 -359
  112. package/.genie/product/environment.md +0 -60
  113. package/.genie/product/mission.md +0 -60
  114. package/.genie/product/roadmap.md +0 -44
  115. package/.genie/product/tech-stack.md +0 -34
  116. package/.genie/product/templates/context-template.md +0 -218
  117. package/.genie/product/templates/qa-done-report-template.md +0 -68
  118. package/.genie/product/templates/review-report-template.md +0 -89
  119. package/.genie/product/templates/wish-template.md +0 -120
  120. package/.genie/scripts/helpers/analyze-commit.js +0 -195
  121. package/.genie/scripts/helpers/bullet-counter.js +0 -194
  122. package/.genie/scripts/helpers/bullet-find.js +0 -289
  123. package/.genie/scripts/helpers/bullet-id.js +0 -244
  124. package/.genie/scripts/helpers/check-secrets.js +0 -237
  125. package/.genie/scripts/helpers/count-tokens.js +0 -200
  126. package/.genie/scripts/helpers/create-frontmatter.js +0 -456
  127. package/.genie/scripts/helpers/detect-markers.js +0 -293
  128. package/.genie/scripts/helpers/detect-todos.js +0 -267
  129. package/.genie/scripts/helpers/detect-unlabeled-blocks.js +0 -135
  130. package/.genie/scripts/helpers/embeddings.js +0 -344
  131. package/.genie/scripts/helpers/find-empty-sections.js +0 -158
  132. package/.genie/scripts/helpers/index.js +0 -319
  133. package/.genie/scripts/helpers/validate-frontmatter.js +0 -578
  134. package/.genie/scripts/helpers/validate-links.js +0 -207
  135. package/.genie/scripts/helpers/validate-paths.js +0 -373
  136. package/.genie/spells/README.md +0 -9
  137. package/.genie/spells/ace-protocol.md +0 -118
  138. package/.genie/spells/ask-one-at-a-time.md +0 -175
  139. package/.genie/spells/backup-analyzer.md +0 -542
  140. package/.genie/spells/blocker.md +0 -12
  141. package/.genie/spells/break-things-move-fast.md +0 -56
  142. package/.genie/spells/context-candidates.md +0 -72
  143. package/.genie/spells/context-critic.md +0 -51
  144. package/.genie/spells/defer-to-expertise.md +0 -278
  145. package/.genie/spells/delegate-dont-do.md +0 -292
  146. package/.genie/spells/error-investigation-protocol.md +0 -328
  147. package/.genie/spells/evidence-based-completion.md +0 -273
  148. package/.genie/spells/experiment.md +0 -65
  149. package/.genie/spells/file-creation-protocol.md +0 -229
  150. package/.genie/spells/forge-integration.md +0 -281
  151. package/.genie/spells/forge-orchestration.md +0 -514
  152. package/.genie/spells/gather-context.md +0 -18
  153. package/.genie/spells/global-health-check.md +0 -34
  154. package/.genie/spells/global-noop-roundtrip.md +0 -25
  155. package/.genie/spells/install-genie.md +0 -1232
  156. package/.genie/spells/install.md +0 -82
  157. package/.genie/spells/investigate-before-commit.md +0 -112
  158. package/.genie/spells/know-yourself.md +0 -288
  159. package/.genie/spells/learn.md +0 -828
  160. package/.genie/spells/mcp-diagnostic-protocol.md +0 -246
  161. package/.genie/spells/mcp-first.md +0 -124
  162. package/.genie/spells/multi-step-execution.md +0 -67
  163. package/.genie/spells/orchestration-boundary-protocol.md +0 -256
  164. package/.genie/spells/orchestrator-not-implementor.md +0 -189
  165. package/.genie/spells/prompt.md +0 -746
  166. package/.genie/spells/reflect.md +0 -404
  167. package/.genie/spells/routing-decision-matrix.md +0 -368
  168. package/.genie/spells/run-in-parallel.md +0 -12
  169. package/.genie/spells/session-state-updater-example.md +0 -196
  170. package/.genie/spells/session-state-updater.md +0 -220
  171. package/.genie/spells/track-long-running-tasks.md +0 -133
  172. package/.genie/spells/troubleshoot-infrastructure.md +0 -176
  173. package/.genie/spells/upgrade-genie.md +0 -415
  174. package/.genie/spells/url-presentation-protocol.md +0 -301
  175. package/.genie/spells/wish-initiation.md +0 -158
  176. package/.genie/spells/wish-issue-linkage.md +0 -410
  177. package/.genie/spells/wish-lifecycle.md +0 -100
  178. package/.genie/state/provider-status.json +0 -3
  179. package/.genie/state/version.json +0 -16
  180. package/.genie/wishes/canonical-pgserve-pm2-supervision/WISH.md +0 -290
  181. package/.genie/wishes/pgserve-v2/BRIEF-from-genie-pgserve.md +0 -99
  182. package/.genie/wishes/pgserve-v2/WISH.md +0 -442
  183. package/.genie/wishes/release-system-genie-pattern/WISH.md +0 -268
  184. package/.genie/wishes/release-system-genie-pattern/validation.md +0 -205
  185. package/.gitguardian.yaml +0 -29
  186. package/.gitguardianignore +0 -16
  187. package/.github/workflows/ci.yml +0 -122
  188. package/.github/workflows/release.yml +0 -289
  189. package/.github/workflows/version.yml +0 -228
  190. package/.husky/pre-commit +0 -2
  191. package/AGENTS.md +0 -433
  192. package/CLAUDE.md +0 -1
  193. package/Makefile +0 -285
  194. package/assets/icon.ico +0 -0
  195. package/bun.lock +0 -435
  196. package/bunfig.toml +0 -28
  197. package/ecosystem.config.cjs +0 -23
  198. package/eslint.config.js +0 -63
  199. package/examples/multi-tenant-demo.js +0 -104
  200. package/install.sh +0 -123
  201. package/knip.json +0 -9
  202. package/scripts/test-bun-self-heal.sh +0 -163
  203. package/scripts/test-npx.sh +0 -60
  204. package/tests/audit.test.js +0 -189
  205. package/tests/backpressure.test.js +0 -167
  206. package/tests/benchmarks/runner.js +0 -1197
  207. package/tests/benchmarks/vector-generator.js +0 -368
  208. package/tests/cli-install.test.js +0 -322
  209. package/tests/control-db.test.js +0 -285
  210. package/tests/daemon-args.test.js +0 -86
  211. package/tests/daemon-control.test.js +0 -171
  212. package/tests/daemon-fingerprint-integration.test.js +0 -111
  213. package/tests/daemon-pr24-regression.test.js +0 -198
  214. package/tests/fingerprint.test.js +0 -263
  215. package/tests/fixtures/240-orphan-seed.sql +0 -30
  216. package/tests/multi-tenant.test.js +0 -374
  217. package/tests/orphan-cleanup.test.js +0 -390
  218. package/tests/pg-version-regex.test.js +0 -129
  219. package/tests/quick-bench.js +0 -135
  220. package/tests/router-handshake-retry.test.js +0 -119
  221. package/tests/router-handshake-watchdog.test.js +0 -110
  222. package/tests/sdk.test.js +0 -71
  223. package/tests/stale-postmaster-pid.test.js +0 -85
  224. package/tests/stress-test.js +0 -439
  225. package/tests/sync-perf-test.js +0 -150
  226. package/tests/tcp-listen.test.js +0 -368
  227. package/tests/tenancy.test.js +0 -403
  228. package/tests/wrapper-supervision.test.js +0 -107
@@ -1,442 +0,0 @@
1
- # Wish: pgserve v2 — portless, fingerprinted, dogfooded
2
-
3
- | Field | Value |
4
- |-------|-------|
5
- | **Status** | DRAFT |
6
- | **Slug** | `pgserve-v2` |
7
- | **Date** | 2026-04-26 |
8
- | **Author** | Felipe Rosa (via genie-pgserve agent) |
9
- | **Appetite** | large (~3-4 weeks; 8 execution groups across 6 waves + parallel dogfood loop) |
10
- | **Branch** | `wish/pgserve-v2` |
11
- | **Design** | [DESIGN.md](../../brainstorms/pgserve-v2/DESIGN.md) |
12
-
13
- ## Summary
14
-
15
- Cut **pgserve v2.0.0** — breaking semver bump that bundles GC, singleton daemon mode, Unix-socket-by-default, kernel-rooted package.json fingerprint, database-per-fingerprint enforcement, opt-in TCP, and `pgserve.persist: true` flag. Drop the staged ABI-compat plan from the original design (`pgserve-roadmap-design.md`) in favor of one clean cut. Validate the cut by migrating the `automagik-dev/genie` consumer in lockstep — a dedicated dogfooder twin agent runs a real genie dev environment against pgserve v2 throughout the build, reporting breakage daily. Other 5 consumer apps (brain, omni, rlmx, hapvida-eugenia, email) remain on v1.x and migrate in separate per-app wishes after v2 ships.
16
-
17
- ## Scope
18
-
19
- ### IN
20
-
21
- - Singleton pgserve daemon mode — one process per host, supervised, listening on `$XDG_RUNTIME_DIR/pgserve/control.sock` (fallback `/tmp/pgserve/control.sock`).
22
- - Per-pid sockets remain for direct embed; PR #24's invariants (`_stopping` flag, exit-handler reset, router fallback-on-missing-socket) regression-tested.
23
- - SO_PEERCRED-based identity: read peer (pid, uid, gid) from kernel on Unix socket connect.
24
- - Walk `/proc/$pid/cwd` to nearest-ancestor `package.json`; fingerprint = `sha256(realpath(package.json) + name + uid).slice(0, 12)`.
25
- - Script fallback fingerprint: `sha256(uid + cwd + cmdline[1]).slice(0, 12)`.
26
- - Database-per-fingerprint: `app_<sanitized-name>_<12hex>`, auto-created on first connect.
27
- - `pgserve_meta` control table in pgserve admin DB (schema in DESIGN.md §9).
28
- - 3-layer lifecycle: ephemeral default + liveness signal + 24h TTL; `pgserve.persist: true` in package.json overrides.
29
- - GC sweep: on-connect (sampled) + hourly + on-startup; one `gcSweep()` function, three call sites.
30
- - Enforcement default-ON with `PGSERVE_DISABLE_FINGERPRINT_ENFORCEMENT=1` deprecation kill switch.
31
- - Audit log: `~/.pgserve/audit.log` JSONL default, `pgserve.audit.target: "syslog"` opt-in (webhook deferred to v2.1).
32
- - `--listen :PORT` opt-in TCP for k8s/remote use.
33
- - Migration: `automagik-dev/genie` repo updated to consume pgserve v2 — proves zero TCP ports + zero credentials + visible fingerprint in DB name.
34
- - Dogfooder twin agent spawned at wish start, runs genie dev environment against work-in-progress builds, reports daily.
35
- - Release: `pgserve@2.0.0` published to npm; CHANGELOG includes migration guide for v1 consumers.
36
-
37
- ### OUT
38
-
39
- - Migration of brain, omni, rlmx, hapvida-eugenia, email consumer apps (one wish per app, dispatched after v2 ships).
40
- - One-time inventory + classification + cleanup of existing 240 orphans on prod hosts (separate ops task).
41
- - Backward-compat default TCP listener — replaced by `--listen` opt-in (clean break, hence v2 major).
42
- - Multi-host coordination — pgserve v2 is single-host by design.
43
- - Cross-DB foreign keys / cross-app SELECT — still impossible, by design.
44
- - `pgserve.audit.target: "url"` (HTTP webhook) — deferred to v2.1.
45
- - `pgserve.toml` allowlist form for TCP token auth (Group 6 deliverable #2 alternative) — deferred to v2.1; the JSONB `pgserve_meta.allowed_tokens` path ships in v2.0.
46
- - `pgserve.fingerprintRoot: "monorepo-root"` escape hatch — deferred until demand surfaces.
47
- - Encryption-at-rest, TLS for control socket, multi-tenant role permissions — separate hardening wishes.
48
- - Cosign + SLSA provenance — separate supply-chain hardening wish.
49
-
50
- ## Decisions
51
-
52
- | # | Decision | Rationale |
53
- |---|----------|-----------|
54
- | 1 | Single v2.0.0 cut, not the original 5-stage ABI-compat rollout | Felipe 2026-04-26: cycle time over compat. Align breaking semver with actual breaking change. Dogfood loop is the safety net — `automagik-dev/genie` migrates in lockstep. |
55
- | 2 | Portless default — Unix socket only at well-known control path | Eliminates port conflicts (#1 embedded-server failure mode); enables SO_PEERCRED for kernel-rooted identity. |
56
- | 3 | Identity tuple = `(realpath(package.json), name, uid)` hashed to 12 hex | Stable across npm install, runtime swap, git pull, sub-cd. Birthday-bound at ~16M projects. |
57
- | 4 | Database-per-fingerprint, NOT schema-per | Real mechanical isolation under shared superuser; atomic GC via DROP DATABASE; pg_dump/drizzle/prisma compat preserved. |
58
- | 5 | Default-ON enforcement with `PGSERVE_DISABLE_FINGERPRINT_ENFORCEMENT=1` deprecation kill switch | Happy path stays simple; ops gets one panic button. |
59
- | 6 | 3-layer lifecycle (ephemeral / liveness+TTL / `persist:true`) | Cures 240-orphan disease at source; zero new cognitive overhead for devs who don't need persist. |
60
- | 7 | GC: opportunistic + hourly + boot, single sweep function | Bounds worst-case orphan lifetime ≤ 1h on idle; near-instant on active hosts. |
61
- | 8 | Audit tiered (file default → syslog opt-in → webhook v2.1) | Zero-config promise honored; ops opts into separate sink. |
62
- | 9 | Monorepo: nearest-ancestor package.json wins (matches `require.resolve`) | Familiar Node mental model. Workspace edge case documented. |
63
- | 10 | Dogfood `automagik-dev/genie` in parallel from t=0 | Empirical safety net for the breaking cut; first canary before broader migration. |
64
- | 11 | DELETE PR #16 schema/role machinery | Replaced by database boundary + peer-creds routing. |
65
- | 12 | Pin v1.x for non-genie consumers (brain/omni/rlmx/eugenia/email) until each gets a migration wish | Prevents accidental breakage during the v2 rollout window. |
66
-
67
- ## Success Criteria
68
-
69
- - [ ] `pgserve@2.0.0` published to npm with provenance (no `NPM_TOKEN`, OIDC only — already in place from `release-system-genie-pattern`).
70
- - [ ] `automagik-dev/genie` repo running against pgserve v2 in dev mode, verified by:
71
- - [ ] No TCP ports bound by pgserve daemon (`ss -tlnp | grep -i pgserve` returns empty unless `--listen` is set).
72
- - [ ] No credentials in genie's env or code paths (libpq connstring uses Unix socket via `host=/run/.../pgserve` or equivalent).
73
- - [ ] `psql -l` shows genie's DB named `app_<sanitized-name>_<12hex>` with the visible fingerprint.
74
- - [ ] Dogfooder twin reports PASS on the scenario suite (defined in Group 0 below) covering: connect, fingerprint mismatch denied, persist-flag honored, TTL reaped, `--listen` TCP fallback, kill-switch bypass.
75
- - [ ] `pgserve_meta` schema present; every user DB has a row at creation time.
76
- - [ ] Synthetic 240-orphan fixture reduced to 0 after first sweep (test under `tests/multi-tenant.test.js` or new file).
77
- - [ ] Audit log populated with all 7 event types under realistic workload.
78
- - [ ] PR #24's invariants regression-tested in a dedicated test in the daemon group (no socketDir leak across stop/start cycles, double-start no-op, exit-handler resets state).
79
- - [ ] CHANGELOG includes a v1→v2 migration guide for consumers (env var changes, persist flag introduction, TCP opt-in, breakage list).
80
- - [ ] README updated: zero-config promise restated for v2 (still `npx pgserve`); fingerprint behavior documented; persist flag documented.
81
- - [ ] All 6 Namastex apps explicitly pinned to `pgserve@^1.x` until their migration wishes ship.
82
-
83
- ## Execution Strategy
84
-
85
- | Wave | Groups | Parallel? | Notes |
86
- |------|--------|-----------|-------|
87
- | **0** | **0** (dogfooder twin spawn + scenario harness scaffold) | Independent — runs continuously from t=0 | Sets up genie dev env, scenario suite skeleton; idle-watches for builds to consume |
88
- | **1** | **1** (control DB + `pgserve_meta` schema + audit log infra) | Sequential foundation | Foundation for all later groups |
89
- | **2** | **2** (singleton daemon + control socket + PR #24 regression) ‖ **3** (fingerprint derivation + SO_PEERCRED) | Yes — disjoint surfaces | Group 2 = transport layer; Group 3 = identity layer |
90
- | **3** | **4** (database-per-fingerprint + enforcement + kill switch) | Sequential after Wave 2 | Wires identity to tenancy |
91
- | **4** | **5** (lifecycle + persist + GC sweep) ‖ **6** (`--listen` opt-in TCP) | Yes — disjoint surfaces | Group 5 = lifecycle; Group 6 = transport opt-in |
92
- | **5** | **7** (`automagik-dev/genie` consumer migration) | Sequential — proof | Migrates genie repo to consume pgserve v2; dogfooder validates |
93
- | **6** | **8** (release prep — semver 2.0.0, CHANGELOG, migration guide, README, npm publish) | Sequential — ship gate | Final release through `release-system-genie-pattern` workflow |
94
-
95
- Group 0 runs in parallel throughout — its job is to consume each Wave's output as it lands and report breakage to the engineer group leads via `genie send`.
96
-
97
- ---
98
-
99
- ## Execution Groups
100
-
101
- ### Group 0: Dogfooder twin spawn + scenario harness
102
-
103
- **Goal:** Stand up an independent genie agent (the "dogfooder twin") that runs a local `automagik-dev/genie` dev environment against pgserve v2 work-in-progress builds throughout this wish, exercises a defined scenario suite, and reports breakage continuously to the engineer working each group.
104
-
105
- **Deliverables:**
106
- 1. Spawn dogfooder twin via `genie spawn dogfooder --team genie` with cwd `/home/genie/workspace/repos/genie`. Brief: consume pgserve v2 from `npm pack` of the active feature branch, run scenario suite daily, report PASS/FAIL via `genie send` to the engineer.
107
- 2. Scenario suite scaffold at `genie/.genie/dogfood/pgserve-v2/scenarios.md`, covering:
108
- - **S1 connect**: genie boots, requests a DB, gets one (named with fingerprint), CRUD a row, disconnect.
109
- - **S2 fingerprint mismatch denied**: genie boots from `/tmp/fake-project` (different package.json) — must NOT reach the real genie DB; gets a fresh fingerprint instead.
110
- - **S3 persist honored**: package.json has `pgserve.persist: true`; kill genie process, wait 25h (or fast-forward via test hook), restart genie, original DB still present.
111
- - **S4 TTL reaped**: package.json has no persist flag; kill genie, wait 25h, restart with same fingerprint — DB was reaped, fresh empty one provisioned.
112
- - **S5 `--listen` TCP fallback**: pgserve started with `--listen :5432`; genie configured with `host=localhost port=5432` instead of socket — connects.
113
- - **S6 kill-switch bypass**: `PGSERVE_DISABLE_FINGERPRINT_ENFORCEMENT=1` env, two genie processes from different fingerprints — second reaches first's DB (proves the kill switch is a real bypass; deprecation warning logged).
114
- 3. Each scenario script callable as `bun .genie/dogfood/pgserve-v2/scenarios/sN.ts`, returns exit 0 on PASS / non-zero on FAIL with diagnostic.
115
- 4. Daily summary cron from twin: post a one-line status to genie team-lead `genie send "dogfood D=$(date +%Y%m%d): S1✅ S2✅ S3⚠ S4✅ S5✅ S6✅" --to team-lead`.
116
-
117
- **Acceptance Criteria:**
118
- - [ ] Twin agent visible in `genie ls --json` with `team: genie`, `status: idle` or `running`.
119
- - [ ] Scenario harness exists at the documented path, all 6 scripts present (may be stubs that return WIP until matching wave ships).
120
- - [ ] Twin posts at least one daily status during the wish lifecycle.
121
- - [ ] After Wave 5 (Group 7) ships, all 6 scenarios return PASS.
122
-
123
- **Validation:**
124
- ```bash
125
- genie ls --json | jq '.[] | select(.name=="dogfooder")'
126
- test -f /home/genie/workspace/repos/genie/.genie/dogfood/pgserve-v2/scenarios.md
127
- ls /home/genie/workspace/repos/genie/.genie/dogfood/pgserve-v2/scenarios/ | wc -l # expect 6
128
- ```
129
-
130
- **depends-on:** none (runs from t=0 in parallel with Wave 1)
131
-
132
- ---
133
-
134
- ### Group 1: Control DB schema + audit log infrastructure
135
-
136
- **Goal:** Land the foundational `pgserve_meta` table in pgserve's admin DB, plus the JSONL audit log writer with rotation. Both are prerequisites for every later group's metadata writes and visibility events.
137
-
138
- **Deliverables:**
139
- 1. New module `src/control-db.js` exposing:
140
- - `ensureMetaSchema(client)` — idempotently creates `pgserve_meta` table per DESIGN.md §9 schema.
141
- - `recordDbCreated({ databaseName, fingerprint, peerUid, packageRealpath, livenessPid, persist })`.
142
- - `touchLastConnection({ databaseName, livenessPid })`.
143
- - `markPersist(databaseName, value)`.
144
- - `forEachReapable({ now }) -> AsyncIterable<{databaseName, fingerprint, lastConnectionAt, livenessPid}>` (used by Group 5 sweep).
145
- 2. New module `src/audit.js` exposing:
146
- - `audit(event, fields)` — appends one JSON line to `~/.pgserve/audit.log`.
147
- - File rotation at 50MB × 5 files (use a thin in-process rotator, no external dep).
148
- - Event types defined as a TypeScript-style JSDoc enum: `db_created | db_reaped_ttl | db_reaped_liveness | db_persist_honored | connection_routed | connection_denied_fingerprint_mismatch | enforcement_kill_switch_used`.
149
- 3. `src/audit.js` reads `pgserve.audit.target` from the active package.json (when daemon resolves a peer's package.json in Group 3); supported values: `"file"` (default), `"syslog"`. Webhook deferred to v2.1.
150
- 4. Tests in `tests/control-db.test.js` and `tests/audit.test.js`:
151
- - schema idempotency, insert/update/select round-trip, rotation triggers at 50MB, syslog target spawns `logger -t pgserve-audit` per event.
152
-
153
- **Acceptance Criteria:**
154
- - [ ] `src/control-db.js` and `src/audit.js` exist and export the documented surface.
155
- - [ ] `bun test tests/control-db.test.js tests/audit.test.js` green.
156
- - [ ] `~/.pgserve/audit.log` written on test run; rotated when size threshold crossed.
157
- - [ ] No external runtime deps added (rotation is in-process).
158
-
159
- **Validation:**
160
- ```bash
161
- bun test tests/control-db.test.js tests/audit.test.js
162
- test -f src/control-db.js && test -f src/audit.js
163
- node -e "console.log(Object.keys(require('./src/audit.js')))" | grep -q audit
164
- ```
165
-
166
- **depends-on:** none
167
-
168
- ---
169
-
170
- ### Group 2: Singleton daemon + well-known control socket + PR #24 regression
171
-
172
- **Goal:** Add daemon mode to pgserve. One process per host, accepts client connections on `$XDG_RUNTIME_DIR/pgserve/control.sock` (or fallback). Preserve every invariant from PR #24's socketDir lifecycle fix.
173
-
174
- **Deliverables:**
175
- 1. New CLI subcommand `pgserve daemon` (long-running) and `pgserve daemon stop`.
176
- 2. Singleton lock file at `${controlSocketDir}/pgserve.pid` — `flock` exclusive; second invocation of `pgserve daemon` exits with "already running, pid N".
177
- 3. Control socket server in `src/daemon.js`:
178
- - Bind `$XDG_RUNTIME_DIR/pgserve/control.sock` (mode 0700) — fallback `/tmp/pgserve/control.sock` if XDG_RUNTIME_DIR unset.
179
- - Reuse `PostgresManager` lifecycle from `src/postgres.js` for the underlying PG instance — singleton per daemon.
180
- - On SIGTERM: graceful shutdown, unlinks socket and lock file.
181
- 4. Router updates in `src/router.js`:
182
- - When connecting client provides only a libpq connstring like `host=/path/to/socket`, the router strips it and connects to the daemon's control socket instead, then proxies through.
183
- - Per-pid socket fallback path (existing) untouched — direct-embed callers still get per-pid sockets.
184
- 5. **Regression tests for PR #24** in `tests/daemon-pr24-regression.test.js`:
185
- - `stop()` nulls socketDir.
186
- - `start()`+`stop()`+`start()` yields fresh socketDir, no leak, new path.
187
- - Double `start()` is a no-op (re-entry guard preserved).
188
- - Daemon mode does NOT introduce a new socketDir leak path under abnormal exit (kill -9): orphaned socket file + lock file are cleaned by the next `pgserve daemon` boot via stale-pid detection.
189
- 6. README section "Running as daemon" — single-page how-to with PM2 + systemd snippets.
190
-
191
- **Acceptance Criteria:**
192
- - [ ] `pgserve daemon` boots, binds control socket, accepts a `psql -h $XDG_RUNTIME_DIR/pgserve` connection (after Group 4 wires routing).
193
- - [ ] Second `pgserve daemon` invocation refuses with "already running, pid N".
194
- - [ ] `pgserve daemon stop` graceful — unlinks socket + lock.
195
- - [ ] All 4 regression tests pass.
196
- - [ ] No regression in existing test suite (`bun test`).
197
-
198
- **Validation:**
199
- ```bash
200
- bun test tests/daemon-pr24-regression.test.js
201
- bun test tests/multi-tenant.test.js # PR #24's original tests
202
- pgserve daemon &; sleep 1; test -S "${XDG_RUNTIME_DIR:-/tmp}/pgserve/control.sock"; pgserve daemon stop
203
- ```
204
-
205
- **depends-on:** Group 1
206
-
207
- ---
208
-
209
- ### Group 3: Fingerprint derivation + SO_PEERCRED
210
-
211
- **Goal:** Land the kernel-rooted identity layer. On every accept on the daemon's control socket, derive a 12-hex fingerprint for the peer.
212
-
213
- **Deliverables:**
214
- 1. New module `src/fingerprint.js` exposing:
215
- - `getPeerCred(socket): {pid, uid, gid}` — reads SO_PEERCRED via `node:net`'s underlying handle (bun supports this; verify on macOS — fall back to `getpeereid` if needed).
216
- - `findNearestPackageJson(startCwd: string): string | null` — synchronous walk up to filesystem root, returns realpath of nearest `package.json`.
217
- - `derivePackageFingerprint({ packageRealpath, name, uid }): string` — `sha256(packageRealpath + '\0' + name + '\0' + String(uid)).slice(0, 12)`.
218
- - `deriveScriptFingerprint({ uid, cwd, cmdline1 }): string` — fallback when no package.json found.
219
- - `fingerprintForPeer(socket): { fingerprint, packageRealpath, name, uid, mode: 'package' | 'script' }`.
220
- 2. Integration in `src/daemon.js`:
221
- - On every new control-socket accept: read peer creds → walk `/proc/$pid/cwd` → find nearest package.json (or script fallback) → compute fingerprint → log `connection_routed` audit event.
222
- 3. Tests in `tests/fingerprint.test.js`:
223
- - Stable across `cwd` change in the same project.
224
- - Different across two projects with same `name` field but different paths.
225
- - Different across same path but different `uid`.
226
- - Script fallback triggered when no package.json above cwd.
227
- - Monorepo: nested package.json wins (deepest match).
228
-
229
- **Acceptance Criteria:**
230
- - [ ] `src/fingerprint.js` exports the documented surface.
231
- - [ ] All 5 tests pass.
232
- - [ ] Daemon logs `connection_routed` with fingerprint for every accept.
233
- - [ ] macOS support verified via dogfooder twin's S1 scenario when twin runs on macOS-arm64 (or explicit deferral note in CHANGELOG).
234
-
235
- **Validation:**
236
- ```bash
237
- bun test tests/fingerprint.test.js
238
- # Connect a real client, check audit log:
239
- psql -h "${XDG_RUNTIME_DIR:-/tmp}/pgserve" -c 'select 1' >/dev/null
240
- tail -1 ~/.pgserve/audit.log | jq -e '.event=="connection_routed" and (.fingerprint|length==12)'
241
- ```
242
-
243
- **depends-on:** Group 1
244
-
245
- ---
246
-
247
- ### Group 4: Database-per-fingerprint + enforcement + kill switch
248
-
249
- **Goal:** Wire identity (Group 3) to tenancy. Daemon auto-creates a DB per fingerprint on first connect, routes the peer's session into it, denies cross-fingerprint reads. Honor the kill-switch env var.
250
-
251
- **Deliverables:**
252
- 1. In `src/daemon.js`, on accept after Group 3 fingerprint derivation:
253
- - Look up `pgserve_meta` for `fingerprint`. If absent: `CREATE DATABASE app_<sanitize(name)>_<12hex>`, `INSERT INTO pgserve_meta`, audit `db_created`.
254
- - Rewrite the peer's libpq startup-message `database` parameter to the resolved DB name (proxy logic).
255
- - Update `pgserve_meta.last_connection_at = now()`, `liveness_pid = peer.pid`.
256
- 2. Enforcement: if peer attempts to connect to a `database=X` that does NOT match its fingerprint's row:
257
- - With enforcement ON (default): close the connection with an error frame `28P01 invalid_authorization — database fingerprint mismatch`. Audit `connection_denied_fingerprint_mismatch`.
258
- - With `PGSERVE_DISABLE_FINGERPRINT_ENFORCEMENT=1`: proxy through anyway. Audit `enforcement_kill_switch_used` (deprecated; warning logged at daemon boot if env var observed).
259
- 3. Sanitizer: `sanitize(name)` replaces non-`[a-z0-9]` runs with `_`, lowercases, truncates to 30 chars to keep DB name ≤ 63 chars.
260
- 4. Tests in `tests/tenancy.test.js`:
261
- - Two peers with different fingerprints get different DBs.
262
- - Same peer reconnecting reaches its existing DB.
263
- - Cross-fingerprint connection attempt denied with the correct SQLSTATE.
264
- - Kill-switch env: cross-fingerprint succeeds + audit event logged.
265
- - Sanitization: name `"@scope/foo bar"` → `_scope_foo_bar`.
266
-
267
- **Acceptance Criteria:**
268
- - [ ] `bun test tests/tenancy.test.js` green.
269
- - [ ] Manual cross-fingerprint test: spin up two `psql` clients with different cwds, second-one's queries against first's DB return SQLSTATE `28P01`.
270
- - [ ] Kill-switch path emits `enforcement_kill_switch_used` audit event.
271
- - [ ] Daemon boots with deprecation warning on stderr when env var is set.
272
-
273
- **Validation:**
274
- ```bash
275
- bun test tests/tenancy.test.js
276
- # Spin two clients from /tmp/proj-a (package.json name=a) and /tmp/proj-b (name=b)
277
- # Confirm each gets app_a_<hex> and app_b_<hex>; cross attempt from a-client targeting b-db is denied.
278
- ```
279
-
280
- **depends-on:** Group 2, Group 3
281
-
282
- ---
283
-
284
- ### Group 5: Lifecycle + persist flag + GC sweep
285
-
286
- **Goal:** Implement the 3-layer lifecycle. Default ephemeral (liveness + 24h TTL since last connection); `pgserve.persist: true` in package.json overrides. GC sweep called on-connect (sampled), hourly, and on daemon startup.
287
-
288
- **Deliverables:**
289
- 1. In `src/daemon.js` accept hook (Group 4 path), after fingerprint derivation: read `pgserve.persist` from the resolved package.json; set/update `pgserve_meta.persist`.
290
- 2. New `src/gc.js`:
291
- - `gcSweep({ now, dryRun=false })` — iterates `forEachReapable`, decides reap-or-keep per row:
292
- - skip if `persist=true`.
293
- - skip if liveness alive (`/proc/$liveness_pid` exists) — touches `last_connection_at` to slide window.
294
- - reap if liveness dead AND `now - last_connection_at > 24h` → `DROP DATABASE` + `DELETE FROM pgserve_meta` + audit `db_reaped_ttl` or `db_reaped_liveness`.
295
- - `installSweepTriggers(daemon)` — hourly timer + on-connect (sample 1/N where N = max(1, dbCount/10)) + boot-time call once at daemon startup.
296
- 3. Synthetic 240-orphan fixture at `tests/fixtures/240-orphan-seed.sql` plus harness `tests/orphan-cleanup.test.js`:
297
- - Seed 240 DBs with stale `last_connection_at` and dead `liveness_pid`.
298
- - Run one sweep.
299
- - Assert all 240 reaped, audit log has 240 `db_reaped_*` entries.
300
- 4. Tests for the persist override and the slide-window-on-active-pid path.
301
-
302
- **Acceptance Criteria:**
303
- - [ ] `bun test tests/orphan-cleanup.test.js` green; 240 → 0 in one sweep.
304
- - [ ] Persist-flagged DB never reaped even past TTL.
305
- - [ ] On-connect sweep does not block accept latency past 50ms (P99 measured in test).
306
- - [ ] Daemon logs first sweep at boot with summary counts.
307
-
308
- **Validation:**
309
- ```bash
310
- bun test tests/orphan-cleanup.test.js
311
- # Inspect audit log
312
- grep -c db_reaped_ ~/.pgserve/audit.log # >= 240 after the test
313
- ```
314
-
315
- **depends-on:** Group 1, Group 4
316
-
317
- ---
318
-
319
- ### Group 6: `--listen` opt-in TCP
320
-
321
- **Goal:** Bring back TCP — but as opt-in only. Ops who need k8s pods or remote sync set `pgserve daemon --listen :PORT` (or `--listen :5432`). Identity model still applies: TCP peers cannot use SO_PEERCRED, so they MUST present a credential; default deny otherwise.
322
-
323
- **Deliverables:**
324
- 1. Daemon CLI accepts `--listen [host:]port` (repeatable for multiple binds).
325
- 2. TCP accept hook: requires `?fingerprint=<hex>&token=<bearer>` style auth in libpq application_name. Tokens hashed at rest, verified with constant-time compare. Without auth: connection refused. (`pgserve.toml` allowlist form deferred to v2.1 — see OUT.)
326
- 3. Auth tokens issued via `pgserve daemon issue-token --fingerprint <hex>` CLI command — prints token once, hashes into `pgserve_meta.allowed_tokens` jsonb column (added in this group's schema migration).
327
- 4. Audit events: `tcp_token_issued`, `tcp_token_used`, `tcp_token_denied` added to the audit enum.
328
- 5. Tests:
329
- - TCP connect without token denied.
330
- - TCP connect with correct token reaches the right fingerprint's DB.
331
- - Token revoke via `pgserve daemon revoke-token <id>` works.
332
-
333
- **Acceptance Criteria:**
334
- - [ ] `pgserve daemon --listen :5432` binds; `ss -tlnp | grep 5432` shows pgserve.
335
- - [ ] Test suite covers all three TCP paths (deny, allow, revoke).
336
- - [ ] Audit log has `tcp_*` events.
337
- - [ ] Without `--listen`, no TCP port bound (verify via `ss -tlnp`).
338
-
339
- **Validation:**
340
- ```bash
341
- bun test tests/tcp-listen.test.js
342
- pgserve daemon --listen :5432 &; sleep 1
343
- ss -tlnp | grep -q 5432
344
- pgserve daemon stop
345
- ```
346
-
347
- **depends-on:** Group 2
348
-
349
- ---
350
-
351
- ### Group 7: `automagik-dev/genie` consumer migration (the dogfood proof)
352
-
353
- **Goal:** Migrate the `automagik-dev/genie` repo to consume pgserve v2. This is THE proof. Removes all pgserve TCP host/port/credential references, switches to Unix socket, relies on auto-fingerprint. Dogfooder twin's S1–S6 must all return PASS after this group ships.
354
-
355
- **Deliverables:**
356
- 1. In `automagik-dev/genie` repo (separate PR, depends-on this wish merging first):
357
- - Remove all `PGHOST`, `PGPORT`, `PGUSER`, `PGPASSWORD` references where they exist purely for pgserve.
358
- - Update libpq connstring helper to default to `host=$XDG_RUNTIME_DIR/pgserve` (no port, no user, no password).
359
- - Add `pgserve.persist: true` to genie's package.json (genie holds long-lived state — wishes, agents, events).
360
- - Pin `pgserve@^2.0.0` in `package.json`.
361
- 2. Genie's startup banner prints the resolved DB name once (so dev sees the visible fingerprint).
362
- 3. Smoke test in genie's CI: `genie wish list` round-trips through pgserve v2 successfully.
363
- 4. Migration note in genie's CHANGELOG.
364
-
365
- **Acceptance Criteria:**
366
- - [ ] `automagik-dev/genie` PR merged.
367
- - [ ] Dogfooder twin's S1–S6 all PASS after this group ships.
368
- - [ ] `genie wish list` works in genie's CI against pgserve v2.
369
- - [ ] No port bound (`ss -tlnp` clean) when genie is the only pgserve consumer.
370
-
371
- **Validation:**
372
- ```bash
373
- # In automagik-dev/genie repo:
374
- grep -rE 'PGHOST|PGPORT|PGUSER|PGPASSWORD' src/ packages/ 2>/dev/null # zero hits expected (or only in test fixtures)
375
- jq '.dependencies.pgserve' package.json # ^2.0.0
376
- genie wish list >/dev/null && echo OK
377
- ```
378
-
379
- **depends-on:** Group 4, Group 5
380
-
381
- ---
382
-
383
- ### Group 8: Release prep — semver 2.0.0, CHANGELOG, migration guide, npm publish
384
-
385
- **Goal:** Ship `pgserve@2.0.0` to npm. Migration guide for v1 consumers. README updated. Pin guidance for the 5 non-genie consumer apps.
386
-
387
- **Deliverables:**
388
- 1. `npm version major` → `2.0.0`. Commit with `[skip ci]`-aware message; tag.
389
- 2. CHANGELOG entry for v2.0.0:
390
- - Breaking changes list (TCP no longer default, fingerprint enforcement default-ON, etc).
391
- - Migration guide: connstring changes, `pgserve.persist` flag, `--listen` for TCP, kill switch env var.
392
- - Pin guidance: "Existing consumers should pin `pgserve@^1.x` in package.json until they migrate."
393
- 3. README:
394
- - Headline still "npx pgserve and it just works, no credentials needed".
395
- - New section "Fingerprint isolation" — what it is, what `\l` will show, monorepo rules.
396
- - New section "Daemon mode" — PM2/systemd snippets.
397
- - Section "Long-running apps: pgserve.persist" — when and how.
398
- - Section "Compat TCP via --listen" — when to use it.
399
- 4. Trigger the existing release workflow (`gh workflow run release.yml -f bump=major`) — this consumes the work from Group 1 of `release-system-genie-pattern` (already SHIPPED).
400
- 5. Verify `npm view pgserve@latest version` returns `2.0.0`, GitHub Release exists with binaries for Linux x64 / macOS arm64 / Windows x64.
401
-
402
- **Acceptance Criteria:**
403
- - [ ] `pgserve@2.0.0` published to npm with provenance.
404
- - [ ] GitHub Release `v2.0.0` exists with all 3 binary assets.
405
- - [ ] CHANGELOG migration guide present and accurate.
406
- - [ ] README updated and lints clean.
407
- - [ ] Dogfooder twin posts final status: all scenarios PASS on the published artifact.
408
-
409
- **Validation:**
410
- ```bash
411
- npm view pgserve@latest version # 2.0.0
412
- gh release view v2.0.0 --json tagName,assets -q '{tag: .tagName, assets: [.assets[].name]}'
413
- test -f CHANGELOG.md && grep -q "## 2.0.0" CHANGELOG.md
414
- ```
415
-
416
- **depends-on:** Group 7
417
-
418
- ## Dependencies
419
-
420
- - depends-on: none external. (`release-system-genie-pattern` is already SHIPPED — its workflow is this wish's release vehicle.)
421
- - blocks: per-app migration wishes for `brain`, `omni`, `rlmx`, `hapvida-eugenia`, `email` consumers — those wishes can be drafted now but cannot ship until pgserve@2.0.0 is on npm.
422
-
423
- ## QA Criteria
424
-
425
- After merge to `main` and release of `pgserve@2.0.0`:
426
- - [ ] On a fresh dev host, `npx pgserve@2 daemon &` boots cleanly without prompts.
427
- - [ ] A throwaway `mkdir /tmp/foo && cd /tmp/foo && npm init -y && bun -e "import pg from 'postgres'; const sql = pg('postgres://postgres:postgres@/test?host=/run/user/$UID/pgserve'); console.log(await sql\`select 1\`); await sql.end()"` works without further config.
428
- - [ ] `psql -l` from the daemon-owning user shows `app_foo_<12hex>`.
429
- - [ ] Audit log under `~/.pgserve/audit.log` shows the connect events.
430
- - [ ] No bound TCP port (verified via `ss -tlnp`).
431
-
432
- ## Assumptions / Risks
433
-
434
- - **Assumption:** `automagik-dev/genie` is the right canary. Its data model is non-trivial (wishes, agents, events) and it's actively developed — high signal-to-noise. If turns out genie under-exercises a code path that brain/email rely on, dogfood loop won't catch it. Mitigation: Group 7 includes a smoke test that exercises every audit event, not just connect.
435
- - **Assumption:** macOS support for SO_PEERCRED via Bun is available. If not, fall back to `getpeereid` syscall via FFI; if that's also blocked, document as Linux-only for v2.0 and revisit for v2.1.
436
- - **Risk: brain/omni/rlmx/eugenia/email apps accidentally upgrade to v2.0** before their migration wishes run → outage. Mitigation: Group 8 migration guide explicitly tells consumers to pin `^1.x`; we also send notice to each repo's owner before publish.
437
- - **Risk: `automagik-dev/genie` migration reveals a fundamental design flaw** mid-build. Mitigation: dogfooder twin reports daily; if a Wave 4+ scenario fails irreparably, pause wish, reconvene `/council`, possibly revert to the original staged plan.
438
- - **Risk: PR #24 invariants regress in Group 2 daemon work.** Mitigation: explicit regression test required in Group 2's deliverables.
439
- - **Risk: 24h TTL is wrong for some workloads.** Mitigation: `pgserve.persist: true` covers production; for dev workloads with long debug cycles, document that any new connection slides the window. If real friction emerges, expose `pgserve.ttlHours` in v2.1.
440
- - **Risk: daemon as single point of failure.** Mitigation: supervised by PM2/systemd per the README snippets; pgserve already tolerates restarts (per-app spawn pattern was effectively the same SPF).
441
- - **Risk: dogfooder twin idle-burns tokens** while waiting for early-wave builds. Mitigation: twin has explicit instruction to sleep 1800s between scenario runs and only spike on a `genie send` trigger from the engineer.
442
- - **Risk: Bun + Node compatibility for SO_PEERCRED.** Verify in Group 3; if Bun blocks the syscall surface, must drop in a small native addon or use `getpeereid` fallback.