pgserve 1.1.9 → 1.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.
package/.genie/AGENTS.md CHANGED
@@ -5,6 +5,8 @@ description: Global agents (orchestration, QA, analysis, maintenance)
5
5
  github_url: https://github.com/namastexlabs/automagik-genie/tree/main/.genie
6
6
  ---
7
7
 
8
+ > **Shared rules in `~/.claude/rules/agent-bible.md`. Read it.**
9
+
8
10
  # Base Genie Agents
9
11
 
10
12
  **Global agents available across all collectives.**
@@ -11,6 +11,8 @@ forge:
11
11
  OPENCODE: {}
12
12
  ---
13
13
 
14
+ > **Shared rules in `~/.claude/rules/agent-bible.md`. Read it.**
15
+
14
16
  ## Framework Reference
15
17
 
16
18
  This agent uses the universal prompting framework documented in AGENTS.md §Prompting Standards Framework:
@@ -0,0 +1,268 @@
1
+ # Wish: Adopt khal-os/desktop release pattern (single-branch semver)
2
+
3
+ | Field | Value |
4
+ |-------|-------|
5
+ | **Status** | DRAFT |
6
+ | **Slug** | `release-system-genie-pattern` |
7
+ | **Date** | 2026-04-25 |
8
+ | **Author** | Felipe Rosa |
9
+ | **Appetite** | small (~1 day) |
10
+ | **Branch** | `wish/release-system-genie-pattern` |
11
+ | **Design** | _No brainstorm — direct wish_ |
12
+
13
+ ## Summary
14
+
15
+ Replace pgserve's PR-label-driven release system (`rc`/`stable` labels, `scripts/release.cjs`, `bump-rc`/`promote` workflow_dispatch actions, RC suffix versioning) with the simpler `khal-os/desktop` pattern adapted to a single `main` branch. Keep semantic `1.x.y` versioning via `npm version patch|minor|major`. Drop the RC/promote tier entirely. Complete the npm OIDC Trusted Publishing migration by removing the lingering `NPM_TOKEN` auth path. Preserve the multi-platform binary matrix (Linux/macOS/Windows).
16
+
17
+ ## Scope
18
+
19
+ ### IN
20
+
21
+ - Single-workflow release pipeline at `.github/workflows/release.yml`, modeled on `khal-os/desktop/.github/workflows/release.yml` + the `workflow_dispatch` path of `khal-os/desktop/.github/workflows/version.yml`, collapsed for single-branch usage.
22
+ - Two trigger paths into the same workflow:
23
+ - **Push to `main`** — auto-detect: read `package.json` `version`, if `v${version}` tag does not exist and head commit does not contain `[skip ci]`, run the build/publish/release pipeline.
24
+ - **`workflow_dispatch`** with input `bump: patch|minor|major` — run `npm version ${bump} --no-git-tag-version`, commit `[skip ci] release v${version}`, tag, push, then continue inline with build/publish/release.
25
+ - Semantic versioning via `npm version` (no custom Node script). Continues from current `1.2.0` line.
26
+ - npm OIDC Trusted Publishing: Node 24 (npm ≥ 11.5.1), `id-token: write`, no `NODE_AUTH_TOKEN`/`NPM_TOKEN`, `NPM_CONFIG_PROVENANCE: "false"` (matches genie/rlmx).
27
+ - Reuse existing `version.yml` matrix for Linux x64 / macOS arm64 / Windows x64 binaries; **rename it to `version.yml`** (npm Trusted Publisher is bound to that filename) and rewire its `publish` job to OIDC.
28
+ - Simple changelog: `git log --oneline ${PREV}..HEAD --pretty="- %s" | head -50` (matches khal-os exactly).
29
+ - Bot-loop guard: `[skip ci]` marker in the bot's bump commit — release.yml skips that commit at the prepare step.
30
+ - Delete `scripts/release.cjs` (RC/promote logic obsolete).
31
+ - Delete `.github/release.yml` (PR-label-based notes config — superseded by inline `git log`).
32
+ - Update README / docs that reference the old `rc`/`stable` label flow.
33
+
34
+ ### OUT
35
+
36
+ - `dev` branch + dev→main rolling promotion. pgserve has only `main`.
37
+ - Two-tier `@next`/`@latest` publishing — every release publishes `@latest` only.
38
+ - Date-based `MAJOR.YYMMDD.N` versioning (the genie/rlmx scheme). User explicit: keep semantic `1.x.y`.
39
+ - `git-cliff` / `cliff.toml` — overkill for pgserve's release cadence; raw `git log` is what khal-os/desktop uses and is sufficient.
40
+ - Cosign keyless signing + SLSA Level 3 provenance (genie has it, khal-os does not, user did not request it).
41
+ - Migrating the npmjs.com Trusted Publisher configuration — assumed already configured by user ("we changed to OIDC"). Group 4 verifies; if mis-pointed, surface to user before merge.
42
+ - Backward-compatible escape hatch for the old `bump-rc` / `promote` `workflow_dispatch` actions — clean break.
43
+ - Changes to `ci.yml`, `commitlint.yml`, or test workflows.
44
+
45
+ ## Decisions
46
+
47
+ | # | Decision | Rationale |
48
+ |---|----------|-----------|
49
+ | 1 | Keep semantic `1.x.y` versioning, drive bumps with `npm version patch\|minor\|major` | User explicit: "i don't wanna change the version numbering". `npm version` is the simplest possible bump tool, no custom scripts, no date math. Matches khal-os/desktop exactly. |
50
+ | 2 | Drop the RC / `@next` tier entirely | User explicit: "we only have main, don't have next, which is ok". One tier = fewer states, no rolling-PR machinery, no PR labels. Pre-release ability can be added later via PR-label escape hatch if ever needed. |
51
+ | 3 | Single workflow file (`release.yml`) with two trigger paths instead of khal-os's two-file split (`release.yml` + `version.yml`) | khal-os/desktop's `version.yml` carries dev/main/dispatch logic; we only need the dispatch path. Folding into one file removes a layer of indirection without losing capability. |
52
+ | 4 | Bot-loop avoidance via `[skip ci]` marker (not `github-actions[bot]` actor check) | Matches khal-os/desktop verbatim. The bump commit message `[skip ci] release v${version}` causes the prepare-job's `if:` gate to short-circuit. Simpler than dual-guarding on actor + marker. |
53
+ | 5 | Push-to-main auto-publishes when `package.json` version bumped manually (no dispatch needed) | Lets devs run `npm version patch` locally, commit, PR, merge → release fires. Matches khal-os. The dispatch path is for "I want to release right now without a code change", which is a real but rare case. |
54
+ | 6 | Complete OIDC migration in this wish (remove `NPM_TOKEN` from `version.yml`) | User said OIDC is configured, but `version.yml` still passes `NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}`. Half-migrated is the worst state — finish the swap. |
55
+ | 7 | Keep multi-platform binary matrix in a reusable workflow, but **rename `version.yml` → `version.yml`** | npmjs.com Trusted Publisher is bound to filename `version.yml`. Caller (`release.yml`) updates its `uses:` reference accordingly. |
56
+ | 8 | Changelog = raw `git log --oneline` per release range (no cliff) | khal-os uses this exact pattern. For pgserve's release cadence (handful per quarter), the noise of conventional-commit categorization is not worth the `cliff.toml` maintenance. |
57
+ | 9 | Cosign + SLSA scoped OUT | Khal-os doesn't sign either. User did not request supply-chain hardening. Track as a separate follow-up wish if ever desired. |
58
+
59
+ ## Success Criteria
60
+
61
+ - [ ] A merged PR that bumps `package.json` from `1.2.0` to `1.2.1` (no `[skip ci]` marker) triggers `release.yml`, which publishes `pgserve@1.2.1` to npm tagged `latest`, creates `v1.2.1` git tag, and creates a GitHub Release with `git log` notes and the three platform binaries attached.
62
+ - [ ] Triggering `release.yml` via `workflow_dispatch` with `bump: patch` from `1.2.1` produces `1.2.2` end-to-end (commit + tag + npm publish + GitHub Release with binaries) without a separate human commit.
63
+ - [ ] `package.json` `version` field always equals the published npm version (no drift).
64
+ - [ ] No reference to `secrets.NPM_TOKEN` or `NODE_AUTH_TOKEN` in any `.github/workflows/*.yml` (`grep -r 'NPM_TOKEN\|NODE_AUTH_TOKEN' .github/` returns empty).
65
+ - [ ] No reference to `bump-rc`, `promote`, `release.cjs`, or PR-label release flow in tracked code outside `.genie/wishes/` and `CHANGELOG.md`.
66
+ - [ ] The bot's `[skip ci] release v...` commit does not retrigger `release.yml` (verified via `if:` guard at prepare-job level).
67
+ - [ ] `npx pgserve@latest --version` prints the new semver version after the first post-merge release.
68
+ - [ ] README's release section describes the new flow in ≤ 6 lines.
69
+
70
+ ## Execution Strategy
71
+
72
+ Sequential, four waves. Group 1 rewrites the workflows. Group 2 deletes the obsolete bits and updates docs. Group 3 is the dry-run validation gate. Group 4 is the post-merge live-fire release verification.
73
+
74
+ | Wave | Group | Agent | Description |
75
+ |------|-------|-------|-------------|
76
+ | 1 | 1 | engineer | Rewrite `release.yml` (single-file khal-os pattern: push + dispatch paths, calls `version.yml`). OIDC-ify `version.yml` publish job. |
77
+ | 2 | 2 | engineer | Delete `scripts/release.cjs` and `.github/release.yml`. Scrub README/docs of `rc`/`stable` label flow. Drop all `NPM_TOKEN` references. |
78
+ | 3 | 3 | qa | Static validation: `actionlint` clean on all workflow files, manual trace of `if:` gates against three commit scenarios, confirm npmjs.com Trusted Publisher entry points at the right workflow. |
79
+ | 4 | 4 | qa | Live-fire post-merge: trigger first release via `workflow_dispatch` on a `patch` bump; verify npm `latest`, GitHub Release tag, and `package.json` version all match. |
80
+
81
+ ---
82
+
83
+ ## Execution Groups
84
+
85
+ ### Group 1: Rewrite release.yml (khal-os pattern) and OIDC-ify version.yml
86
+
87
+ **Goal:** Replace the PR-label-gated workflow with a single-file workflow modeled on khal-os/desktop, supporting both push-to-main auto-detect and workflow_dispatch bump paths. Complete the OIDC migration in `version.yml`.
88
+
89
+ **Deliverables:**
90
+
91
+ 1. **New `.github/workflows/release.yml`** — full rewrite. Triggers:
92
+ - `push: branches: [main]`
93
+ - `workflow_dispatch` with input `bump: choice [patch, minor, major]`
94
+
95
+ Jobs (in order):
96
+ - **`bump`** (only on `workflow_dispatch`):
97
+ - Checkout `main` with `fetch-depth: 0` and write token.
98
+ - Configure git as `github-actions[bot]`.
99
+ - Run `npm version ${{ inputs.bump }} --no-git-tag-version`.
100
+ - Commit `[skip ci] release v${VERSION}`, tag `v${VERSION}`, `git push origin HEAD --follow-tags`.
101
+ - Output `version` and `tag`.
102
+ - **`prepare`** (`needs: bump`):
103
+ - `if:` (explicit wiring — must handle skipped `bump` job correctly):
104
+ ```yaml
105
+ if: |
106
+ !cancelled() && !failure() &&
107
+ (github.event_name == 'workflow_dispatch' ||
108
+ (github.event_name == 'push' &&
109
+ !startsWith(github.event.head_commit.message, '[skip ci]')))
110
+ ```
111
+ Why this shape:
112
+ - On `push`: `bump` is skipped (its own `if:` requires `workflow_dispatch`). GitHub Actions blocks `needs:`-dependent jobs when the parent is skipped *unless* the dependent job's `if:` calls `!cancelled() && !failure()` (the default `success()` does not pass on skipped). Without this, the push path is silently broken.
113
+ - On `workflow_dispatch`: `bump` ran and pushed the `[skip ci]` commit + tag. `prepare` runs in the same workflow run (not via re-trigger), so the `[skip ci]` commit-message filter does not apply — `github.event_name` is `workflow_dispatch`, gate evaluates true.
114
+ - Checkout: `ref: ${{ needs.bump.outputs.tag || github.sha }}` — uses bump's tag on dispatch, head SHA on push. Requires `fetch-depth: 0` for tag history.
115
+ - Read version from `package.json`; tag = `v${VERSION}`.
116
+ - Check `gh release view "${TAG}"` — if exists, set `skip=true`.
117
+ - Find previous tag: `gh release list --limit 50 --json tagName -q '.[].tagName' | grep -v "^${TAG}$" | head -1`, verify with `git merge-base --is-ancestor`.
118
+ - Generate notes: `git log --oneline "${PREV}..HEAD" --pretty="- %s" | head -50`.
119
+ - **`build`** (`needs: prepare`, `if: skip == 'false'`): calls `version.yml` with `version`, `npm_tag: latest`, `ref: ${TAG}`.
120
+ - **`release`** (`needs: [prepare, build]`, `if: skip == 'false'`):
121
+ - Download `binaries-*` artifacts.
122
+ - `gh release create "${TAG}" --target ${{ github.sha }} --title "${TAG}" --notes-file /tmp/release-notes.md ${BINARIES}`.
123
+
124
+ `concurrency: group: release-${{ github.ref }}, cancel-in-progress: false`.
125
+
126
+ 2. **Updated `.github/workflows/version.yml`** `publish` job:
127
+ - Add `permissions: id-token: write`.
128
+ - Bump Node version to `'24'` (gives npm 11.5+ for OIDC out of the box; avoids the `npm install -g npm@latest` bug rlmx hit).
129
+ - Add npm version assertion step (mirror rlmx): fail if `npm --version` major < 11.
130
+ - Add `env: NPM_CONFIG_PROVENANCE: "false"` on the publish step.
131
+ - Remove `NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}` from the publish step.
132
+ - Keep or remove the `environment: npm-publish` based on whether it gates on a token secret (verify: if it's just an audit boundary, keep it; if it's the npm token holder, remove it).
133
+
134
+ **Acceptance Criteria:**
135
+ - [ ] `release.yml` has both `push: branches: [main]` and `workflow_dispatch` triggers with `bump` input.
136
+ - [ ] `bump` job runs only on `workflow_dispatch` and produces a `[skip ci]` commit + tag + push.
137
+ - [ ] `prepare` job's `if:` gate evaluates to false (skip) when push-event head commit starts with `[skip ci]`, but proceeds for the dispatch chain (because the bump job's outputs feed it directly, not via re-triggered push).
138
+ - [ ] `build` and `release` jobs are gated on `prepare.outputs.skip == 'false'`.
139
+ - [ ] `version.yml` `publish` job declares `permissions.id-token: write`, uses Node 24, asserts npm ≥ 11, has `NPM_CONFIG_PROVENANCE: "false"`, and contains zero `NODE_AUTH_TOKEN`/`NPM_TOKEN` references.
140
+ - [ ] `actionlint` passes on both files.
141
+
142
+ **Validation:**
143
+ ```bash
144
+ npx --yes actionlint .github/workflows/release.yml .github/workflows/version.yml
145
+ ! grep -r 'NPM_TOKEN\|NODE_AUTH_TOKEN' .github/workflows/
146
+ grep -E 'workflow_dispatch|patch.*minor.*major|\[skip ci\]' .github/workflows/release.yml
147
+ grep -E 'id-token: write|NPM_CONFIG_PROVENANCE' .github/workflows/version.yml
148
+ ```
149
+
150
+ **depends-on:** none
151
+
152
+ ---
153
+
154
+ ### Group 2: Delete obsolete release machinery and update docs
155
+
156
+ **Goal:** Remove the old PR-label release path (`scripts/release.cjs`, `.github/release.yml`, Makefile targets, README references) so there is exactly one documented release flow.
157
+
158
+ **Deliverables:**
159
+ 1. `git rm scripts/release.cjs`.
160
+ 2. `git rm .github/release.yml` (PR-label-based GitHub-native notes, superseded).
161
+ 3. **Update `Makefile`** — there are stale references in two places:
162
+ - **Lines 31-39 (help target):** rewrite the "Quick Commands" / "CI/CD Workflow" lines. Replace `release-rc` / `release-stable` mentions and the three "Add 'rc' label / Add 'stable' label" lines with a single "Releasing" section pointing at the new flow.
163
+ - **Lines 218-242 (`.PHONY: release-rc release-stable release-dry` block):** delete the entire block. The new flow is run via GitHub Actions (`gh workflow run release.yml -f bump=patch`), not Make targets. If a local bump-helper is wanted, replace with a one-liner `release: ## Bump version locally (run 'npm version patch|minor|major' manually)` — but preferred deliverable is to delete the whole block.
164
+ 4. README / CONTRIBUTING / any `.genie/` doc: remove instructions about `rc` / `stable` PR labels and `bump-rc` / `promote` workflow_dispatch actions. Replace with one short section:
165
+ ```
166
+ ## Releasing
167
+ - Manual: bump locally with `npm version patch|minor|major`, commit, PR to main. Merge → release fires automatically.
168
+ - Bot: trigger `Release` workflow with `bump` input (patch/minor/major). The bot bumps, tags, builds, publishes.
169
+ - Skip: any commit message starting with `[skip ci]` is ignored by the release pipeline.
170
+ ```
171
+ 5. `package.json`: confirm `prepublishOnly` still runs in CI; add `HUSKY: "0"` to publish step in `version.yml` if the `prepare` script causes issues (mirrors genie's pattern).
172
+ 6. Confirm no stale references: `grep -rn 'bump-rc\|release\.cjs\|/promote\|stable.*label\|rc.*label' .` returns zero hits in tracked code outside `.genie/wishes/` and `CHANGELOG.md`.
173
+
174
+ **Acceptance Criteria:**
175
+ - [ ] `scripts/release.cjs` deleted.
176
+ - [ ] `.github/release.yml` deleted.
177
+ - [ ] `Makefile` no longer references `release.cjs`, `bump-rc`, `promote`, `release-rc`, `release-stable`, `release-dry`, or "Add 'rc'/'stable' label".
178
+ - [ ] README's release section describes the new flow in ≤ 6 lines.
179
+ - [ ] No tracked file outside `.genie/wishes/` or `CHANGELOG.md` mentions `bump-rc`, `release.cjs`, or the old label workflow.
180
+
181
+ **Validation:**
182
+ ```bash
183
+ test ! -f scripts/release.cjs
184
+ test ! -f .github/release.yml
185
+ ! grep -E 'release\.cjs|bump-rc|release-rc|release-stable|release-dry' Makefile
186
+ git ls-files | xargs grep -l 'bump-rc\|release\.cjs' 2>/dev/null \
187
+ | grep -v '^\.genie/wishes/\|^CHANGELOG' || echo "clean"
188
+ ```
189
+
190
+ **depends-on:** release-system-genie-pattern#1
191
+
192
+ ---
193
+
194
+ ### Group 3: Static validation and Trusted Publisher verification
195
+
196
+ **Goal:** Catch issues before merge — workflow lint, `if:` gate trace, npmjs.com Trusted Publisher pointed at the right workflow file.
197
+
198
+ **Deliverables:**
199
+ 1. `actionlint` clean on all touched workflow files.
200
+ 2. Manual trace of `release.yml` `if:` gates against three scenarios, written to `.genie/wishes/release-system-genie-pattern/validation.md`:
201
+ - **Scenario A:** human pushes a commit `chore: bump version to 1.2.1` to main → `bump` job skipped, `prepare` runs, version `1.2.1` tag missing → release fires.
202
+ - **Scenario B:** bot's `[skip ci] release v1.2.1` commit lands via `workflow_dispatch` chain → `bump` ran inline, `prepare` should also run (because the workflow run that fired it is `workflow_dispatch`, not the push triggered by the bot's tag). Confirm the `prepare` job's `if:` gate is structured to handle this.
203
+ - **Scenario C:** human pushes `[skip ci] docs: typo` to main → `prepare` short-circuits, no release.
204
+ 3. Confirm npmjs.com Trusted Publisher entry for `pgserve` exists and points at `<repo>/.github/workflows/version.yml` (the file that calls `npm publish`). If it points at a nonexistent file, the workflow path entry must be updated by the user before merge — surface clearly.
205
+ 4. Confirm `prepublishOnly` (`npm run lint && npm run deadcode && npm run test:npx && npm run test:bun-self-heal`) runs in CI without flakes — these are pgserve's release-time integration checks; if any have local-machine assumptions, fix or document.
206
+
207
+ **Acceptance Criteria:**
208
+ - [ ] `actionlint` passes.
209
+ - [ ] `validation.md` covers all three scenarios with explicit gate-evaluation results.
210
+ - [ ] Trusted Publisher entry confirmed (or user-fixable issue surfaced).
211
+ - [ ] `prepublishOnly` chain runs cleanly in a CI dry-run (e.g., on a draft PR).
212
+
213
+ **Validation:**
214
+ ```bash
215
+ npx --yes actionlint .github/workflows/*.yml
216
+ test -f .genie/wishes/release-system-genie-pattern/validation.md
217
+ # Trusted Publisher check is a manual step on npmjs.com — document the expected URL/workflow path.
218
+ ```
219
+
220
+ **depends-on:** release-system-genie-pattern#2
221
+
222
+ ---
223
+
224
+ ### Group 4: Post-merge live-fire release verification
225
+
226
+ **Goal:** After this wish merges to `main`, prove the pipeline works by issuing the first real release through both trigger paths.
227
+
228
+ **Deliverables:**
229
+ 1. **Dispatch-path test:** trigger `release.yml` via `workflow_dispatch` with `bump: patch` (current version is `1.2.0`, expect `1.2.1`).
230
+ 2. Verify outputs:
231
+ - npm: `npm view pgserve@latest version` returns `1.2.1`.
232
+ - GitHub Release: `gh release view v1.2.1` exists with binaries (`pgserve-linux-x64`, `pgserve-darwin-arm64`, `pgserve-windows-x64.exe`) and `git log`-style notes.
233
+ - `package.json` on `main` shows `1.2.1` (committed by bot with `[skip ci]`).
234
+ 3. **Push-path test (deferred until next real change):** when the next non-release PR merges with a manual `npm version patch` bump baked in, confirm the auto-detect path fires and produces `1.2.2`.
235
+ 4. Update `.genie/wishes/release-system-genie-pattern/validation.md` with the live-fire timestamps + run URLs.
236
+
237
+ **Acceptance Criteria:**
238
+ - [ ] First dispatch-path release (`1.2.1`) ships successfully end-to-end.
239
+ - [ ] All three asset names present on the GitHub Release.
240
+ - [ ] `npm view pgserve@latest version` matches GitHub Release tag (modulo leading `v`).
241
+ - [ ] No bot-loop: `release.yml` does not retrigger from the bot's `[skip ci]` commit.
242
+ - [ ] Validation doc updated with run URLs.
243
+
244
+ **Validation:**
245
+ ```bash
246
+ gh workflow run release.yml -f bump=patch
247
+ # Wait, then:
248
+ gh release view v1.2.1 --json tagName,assets -q '{tag: .tagName, assets: [.assets[].name]}'
249
+ npm view pgserve@latest version
250
+ jq -r .version package.json
251
+ ```
252
+
253
+ **depends-on:** release-system-genie-pattern#3
254
+
255
+ ## Dependencies
256
+
257
+ - depends-on: none external.
258
+ - blocks: any future supply-chain hardening (cosign + SLSA) wish for pgserve — that wish would extend the new `release.yml`, so this one must land first.
259
+
260
+ ## Assumptions / Risks
261
+
262
+ - **Assumption:** npmjs.com Trusted Publisher is already configured for `pgserve` and points at `version.yml`. If not, Group 3 surfaces it; first OIDC publish would otherwise fail with 401/403.
263
+ - **Assumption:** macOS-arm64 + Windows runners remain available on GitHub-hosted matrix. Already in use today.
264
+ - **Risk: `workflow_dispatch` bump commit retriggers `release.yml`.** If the `[skip ci]` marker is not respected (e.g., misplaced in the message), the bot's commit fires the push trigger again and the second run sees the tag already exists → exits via `skip=true`. Idempotent failure mode, not infinite loop. Validate in Group 3.
265
+ - **Risk: dispatch path's `prepare` job double-runs.** `workflow_dispatch` triggers `release.yml` directly; the bot's `git push --follow-tags` also triggers a push event. Two parallel runs of `prepare` could race. Mitigation: `concurrency: group: release-${{ github.ref }}, cancel-in-progress: false` serializes them; the second one sees the tag exists and exits.
266
+ - **Risk: lost RC capability.** No `@next` channel for testing risky changes. Mitigation: a feature branch + `npm pack` + local `npm install ./pgserve-x.y.z.tgz` test still works. Add a labeled-PR `@next` escape hatch later if real pain emerges.
267
+ - **Risk: macOS runner cost.** Builds run on `macos-latest` for every release. With infrequent releases (user's framing: "pgserve doesn't get many updates"), absolute spend stays low. Revisit if monthly cost climbs.
268
+ - **Risk: human forgets to bump.** A non-release PR could merge to main without bumping, and no release fires — that is the *desired* behavior. The risk is the inverse: a PR that *should* release forgets to bump. Mitigation: docs explicitly call out the bump step; dispatch path is always available as the fallback.
@@ -0,0 +1,172 @@
1
+ # Validation: release-system-genie-pattern
2
+
3
+ Static-validation evidence for Group 3.
4
+
5
+ ## YAML syntax
6
+
7
+ Both workflow files parse cleanly via `bun + yaml@latest`:
8
+
9
+ ```
10
+ .github/workflows/release.yml: parse OK (5 top-level keys)
11
+ .github/workflows/version.yml: parse OK (4 top-level keys)
12
+ ```
13
+
14
+ `actionlint` is not installed in this sandbox; YAML parse + manual semantic
15
+ trace below cover the same ground for the gate logic that's load-bearing.
16
+
17
+ ## Gate trace — `release.yml` `prepare` job
18
+
19
+ The `prepare` job's `if:` is the single most error-prone piece of the rewrite.
20
+ Its declared shape:
21
+
22
+ ```yaml
23
+ needs: bump
24
+ if: |
25
+ !cancelled() && !failure() &&
26
+ (github.event_name == 'workflow_dispatch' ||
27
+ (github.event_name == 'push' &&
28
+ !startsWith(github.event.head_commit.message, '[skip ci]')))
29
+ ```
30
+
31
+ The `bump` job has its own `if: github.event_name == 'workflow_dispatch'`.
32
+
33
+ ### Scenario A — human pushes a `chore: bump version to 1.2.1` commit to main
34
+
35
+ | Step | Evaluation | Outcome |
36
+ |------|------------|---------|
37
+ | `bump` `if:` | `github.event_name == 'workflow_dispatch'` is **false** (event is `push`) | `bump` skipped |
38
+ | `prepare` `if:` clause 1 | `!cancelled() && !failure()` is **true** (skipped is neither) | continue |
39
+ | `prepare` `if:` clause 2 | `event_name == 'workflow_dispatch'` is **false**; OR-branch falls to push clause | continue |
40
+ | `prepare` `if:` clause 3 | `event_name == 'push' && !startsWith(head_commit.message, '[skip ci]')` is **true** | enter |
41
+ | `prepare` body | reads `package.json` → `1.2.1`; checks `gh release view v1.2.1` → not found | `skip=false` |
42
+ | `build` + `release` | `needs.prepare.outputs.skip == 'false'` | run |
43
+
44
+ **Result:** release fires. ✓
45
+
46
+ ### Scenario B — operator triggers `release.yml` via `workflow_dispatch` with `bump: patch`
47
+
48
+ | Step | Evaluation | Outcome |
49
+ |------|------------|---------|
50
+ | `bump` `if:` | event is `workflow_dispatch` | run |
51
+ | `bump` body | `npm version patch` → `1.2.1`; commit `[skip ci] release v1.2.1`; tag `v1.2.1`; `git push origin HEAD --follow-tags` | outputs `tag=v1.2.1`, push completes |
52
+ | `prepare` `if:` clause 1 | `!cancelled() && !failure()` is **true** (`bump` succeeded) | continue |
53
+ | `prepare` `if:` clause 2 | `event_name == 'workflow_dispatch'` is **true**; short-circuits the `[skip ci]` check entirely | enter |
54
+ | `prepare` body | checks out at `needs.bump.outputs.tag` (`v1.2.1`); reads `package.json` → `1.2.1`; tag check finds no `v1.2.1` release yet | `skip=false` |
55
+ | `build` + `release` | run | publishes + creates GitHub Release |
56
+
57
+ **Concurrent push run** (the `git push` from `bump` triggers a separate `push` event on `main`):
58
+
59
+ | Step | Evaluation | Outcome |
60
+ |------|------------|---------|
61
+ | Concurrency | `release-${{ github.ref }}` matches the in-flight dispatch run; `cancel-in-progress: false` | queues until dispatch completes |
62
+ | When dispatch completes | the queued push run starts | continue |
63
+ | `bump` `if:` | event is `push` | skipped |
64
+ | `prepare` `if:` clause 3 | `head_commit.message` is `[skip ci] release v1.2.1` → `startsWith(..., '[skip ci]')` is **true** → negation **false** | gate **fails** |
65
+ | `prepare` job | skipped via `if:` | clean exit |
66
+ | Downstream jobs | `build`/`release` `needs: prepare` with `if: skip == 'false'`; `prepare` was skipped, not succeeded — `skip` output is empty string, not `'false'`, so dependents are also skipped | clean exit |
67
+
68
+ **Result:** dispatch run releases; queued push run no-ops. No bot loop. ✓
69
+
70
+ ### Scenario C — human pushes `[skip ci] docs: typo` to main
71
+
72
+ | Step | Evaluation | Outcome |
73
+ |------|------------|---------|
74
+ | `bump` `if:` | event is `push` | skipped |
75
+ | `prepare` `if:` clause 3 | `head_commit.message` starts with `[skip ci]` → `!startsWith(...)` is **false** | gate **fails** |
76
+ | `prepare` job | skipped | no release |
77
+ | `build` / `release` | dependent on `prepare` with `if: skip == 'false'`; output is empty since `prepare` did not run | both skipped |
78
+
79
+ **Result:** docs-only push does not release. ✓
80
+
81
+ ## OIDC migration check
82
+
83
+ ```
84
+ $ grep -r 'NPM_TOKEN\|NODE_AUTH_TOKEN' .github/workflows/
85
+ (no matches)
86
+ ```
87
+
88
+ ```
89
+ $ grep -E 'id-token: write|node-version: .24.|NPM_CONFIG_PROVENANCE' .github/workflows/version.yml
90
+ id-token: write
91
+ node-version: '24'
92
+ # npm auto-enables provenance in any CI env with `id-token: write`,
93
+ NPM_CONFIG_PROVENANCE: "false"
94
+ ```
95
+
96
+ `version.yml` `publish` job has:
97
+ - `permissions.id-token: write` (OIDC token mint)
98
+ - Node 24 (ships npm ≥ 11.5.1 with built-in OIDC trusted-publisher)
99
+ - Explicit npm version assertion step (fails the run if npm major < 11)
100
+ - `NPM_CONFIG_PROVENANCE: "false"` to avoid the 422 sigstore failure
101
+ - `HUSKY: "0"` to skip git-hook setup during publish
102
+ - No `NODE_AUTH_TOKEN` / `NPM_TOKEN` references
103
+
104
+ ## Trusted Publisher binding
105
+
106
+ The npmjs.com Trusted Publisher entry for `pgserve` must list:
107
+
108
+ | Field | Value |
109
+ |-------|-------|
110
+ | Publisher | GitHub Actions |
111
+ | Organization or user | `namastexlabs` |
112
+ | Repository | `pgserve` |
113
+ | Workflow filename | `version.yml` |
114
+ | Environment name | `npm-publish` (matches `environment: npm-publish` on the publish job) |
115
+
116
+ `build-all-platforms.yml` was renamed to `version.yml` specifically to bind to
117
+ this entry. The `release.yml` orchestrator's `uses:` reference was updated to
118
+ `./.github/workflows/version.yml` accordingly.
119
+
120
+ ## Stale-reference scan
121
+
122
+ ```
123
+ $ git ls-files | xargs grep -l 'bump-rc\|release\.cjs\|release-rc\b\|release-stable\b\|release-dry\b' \
124
+ | grep -v '^\.genie/wishes/\|^CHANGELOG\|^\.genie/code/agents/git/workflows/release\.md'
125
+ AGENTS.md
126
+ ```
127
+
128
+ The single remaining `AGENTS.md` hit is the intentional "legacy — removed"
129
+ documentation line in the rewritten Release Workflow Protocol. It documents
130
+ that the legacy system is gone, so future agents have explicit notice.
131
+
132
+ `.genie/code/agents/git/workflows/release.md` was excluded because it is genie-
133
+ framework scaffolding describing the **genie** repo's release workflow, not
134
+ pgserve's. It is out of scope for this wish.
135
+
136
+ ## prepublishOnly chain
137
+
138
+ The `prepublishOnly` chain in `package.json` runs:
139
+
140
+ ```
141
+ npm run lint && npm run deadcode && npm run test:npx && npm run test:bun-self-heal
142
+ ```
143
+
144
+ The version.yml `publish` job has `bun install` and `oven-sh/setup-bun@v2`
145
+ before invoking `npm publish`, so:
146
+ - `lint` (eslint) — runs against `src/` + `bin/` ✓
147
+ - `deadcode` (knip) — same ✓
148
+ - `test:npx` — `npm pack` + isolated install + `npx pgserve` startup at port
149
+ 15432. Needs `@embedded-postgres/linux-x64` from optionalDependencies (Linux
150
+ runner pulls correct binary). ✓
151
+ - `test:bun-self-heal` — synthetic broken-install fixture, runs wrapper, asserts
152
+ recovery. Needs bun on PATH. ✓ (`oven-sh/setup-bun@v2` provides it)
153
+
154
+ ## Group 3 status
155
+
156
+ | Criterion | Status | Evidence |
157
+ |-----------|--------|----------|
158
+ | YAML parses cleanly | ✓ | bun + yaml@latest |
159
+ | Gate trace covers 3 scenarios | ✓ | Scenarios A/B/C above |
160
+ | Trusted Publisher target documented | ✓ | binding table above; user confirmed `version.yml` filename mid-execution |
161
+ | `prepublishOnly` viable in CI | ✓ | bun + Node 24 + Linux runner has all deps |
162
+
163
+ ## Group 4 (live-fire) — to run after merge
164
+
165
+ `gh workflow run release.yml -f bump=patch` from `main` after this PR merges.
166
+ Expected outputs:
167
+
168
+ - New `v1.2.1` git tag
169
+ - `npm view pgserve@latest version` → `1.2.1`
170
+ - `gh release view v1.2.1` shows three platform binaries + git-log notes
171
+ - `package.json` on `main` updated to `1.2.1` by `[skip ci]` bot commit
172
+ - `release.yml` does NOT retrigger from the bot's `[skip ci]` commit