ci: run the wire-compat gate (published pg-core opens HEAD-sealed samples) - #266
Conversation
|
Got it — I'm on it. I'll review the |
There was a problem hiding this comment.
Verified against HEAD 394ef02 and its own CI run (30338358017), where Wire compat (published pg-core) is green in 1m17s — the self-test in the PR body holds. The shape is right and the design notes are accurate: the filter is a step so the job always reports (this is exactly what the reason it was written this way asks for, and it means #262 can make this required without wedging non-wire PRs), the workflow file is in its own filter so a change to the job re-runs it, and the upload genuinely precedes the reader — I confirmed the artifact exists on this run.
Requesting changes on one thing, plus a second one-liner in the same file. Both are in the trigger/filter, not in the gate's body.
Blocking — the filter omits the root lockfile. Details inline at the filter block. Short version: pg-core has no Cargo.lock of its own (I checked — only the root has one, pg-compat/Cargo.lock is the other), and the seal step runs cargo run --locked, so the bytes HEAD produces are a function of the root Cargo.lock. A cargo update/dependabot PR bumping ibe, ibs, serde or bincode-next touches only that file, wire evaluates false, every step skips, and the gate reports green having sealed and opened nothing — on exactly the class of change CLAUDE.md singles out as wire-critical (bincode_next::config::legacy() byte compatibility, and "a bincode-next rc bump" specifically). Two lines fix it.
Also worth folding in — on: pull_request: has no types:, so no edited. Can't anchor this inline; the trigger block at the top of build.yml is outside the diff. Per GitHub's documented default, a bare on: pull_request: subscribes to opened/synchronize/reopened only. A retarget — by hand, or GitHub's automatic one when a stacked base is deleted on merge — fires edited, which nothing here listens for. paths-filter derives its verdict from the base, so after a retarget the last run's wire verdict stays attached to the unchanged head sha and GitHub reports it as current. For a check slated to become required, a stale green is a passing required check on a comparison never made against the actual merge target. Since this PR is the one introducing the first base-dependent job in build.yml:
on:
pull_request:
types: [opened, synchronize, reopened, edited]
branches: [main]Docs now understate the gate. Two lines describe this job as unlanded, and after this PR they sit in the same tree as the job itself — our own note on patch-in-comment gates says that tightening belongs in this PR, not a follow-up, because the pending wording sends a later agent run looking for a patch comment that is spent:
CLAUDE.md:8— "Thewire-compat-rustjob that wires the two together is NOT yet a CI gate: the YAML sits in a comment on PR #265 waiting for a maintainer to apply it, so nothing stops a non-additive wire change until then". False once this merges. The same line also sayspg-compat"is covered by none of the per-crate test/fmt/clippy matrices, so run [fmt and clippy] by hand" — this PR is what starts covering them, so that clause needs the same pass.pg-compat/README.md:144— "The proposedwire-compat-rustjob seals and uploads only when the PR touches the wire surface". Drop "proposed"; the rest of that paragraph's advice to #261 stays correct and is worth keeping.
Three non-blocking notes are inline (missing permissions:, the pg-compat lint sitting in front of the gate, and the artifact name recording the merge commit rather than the head commit — verified: this run's artifact is wire-compat-artifacts-821c3bc2… while HEAD is 394ef022…, which makes pg-compat/README.md's "CI records which commit sealed a set in the uploaded artifact's name" not quite true as written).
Checked and clean: no piped run: line, so the pipefail trap isn't in play (seal and the reader declare shell: bash anyway); title passes the pr-title.yml Conventional Commit check; pg-core/examples/seal-samples and PG_COMPAT_ARTIFACTS both exist at HEAD; steps.seal.outcome == 'success' correctly keeps the upload out of the skipped path.
| - 'pg-core/**' | ||
| - 'pg-wasm/**' | ||
| - 'pg-compat/**' | ||
| - '.github/workflows/build.yml' |
There was a problem hiding this comment.
Blocking. The wire filter omits the root Cargo.lock (and root Cargo.toml).
pg-core has no lockfile of its own — I checked the tree at HEAD: only the repo root has a Cargo.lock, plus the one inside the out-of-workspace pg-compat. pg-core is a workspace member, so it resolves versions from the root lockfile, and the seal step below runs cargo run --locked. The bytes HEAD produces are therefore a function of the root Cargo.lock.
So: a cargo update or dependabot PR that bumps ibe, ibs, serde or bincode-next touches only Cargo.lock, steps.changes.outputs.wire evaluates to false, every step below skips on its if, and the job reports green having sealed and opened nothing. That is precisely the class of change CLAUDE.md flags as wire-critical — the bincode_next::config::legacy() byte-compatibility property, and the explicit warning that a bincode-next rc bump is wire-relevant. The gate would be silent on the one PR shape it most needs to catch, and once #262 makes it required that silence is a passing required check.
| - '.github/workflows/build.yml' | |
| - 'pg-core/**' | |
| - 'pg-wasm/**' | |
| - 'pg-compat/**' | |
| - 'Cargo.lock' | |
| - 'Cargo.toml' | |
| - '.github/workflows/build.yml' |
Root Cargo.toml earns its place too: it holds the workspace dependency table and the exclude list that keeps pg-compat pointed at crates.io pg-core rather than ../pg-core. CLAUDE.md calls that exclusion load-bearing, and a change to it would quietly turn the gate into a self-comparison.
| # pending forever on PRs that do not touch the wire surface. This shape always | ||
| # reports and only does the expensive work when it needs to. | ||
| # --------------------------------------------------------------------------- | ||
| wire-compat-rust: |
There was a problem hiding this comment.
No permissions: block on the job. dorny/paths-filter defaults token to ${{ github.token }} and, on pull_request, fetches the changed-file list from the REST API — its README lists pull-requests: read as required. It works here because the repo default is permissive, but this is the first GITHUB_TOKEN-consuming step in build.yml (I grepped: zero permissions: keys in this file, versus 7 in delivery.yml). For a check slated to become required, worth pinning:
permissions:
contents: read
pull-requests: readGood news on the failure mode: the action throws rather than falling back, so a missing permission reds the job instead of silently returning zero changed files. Non-blocking.
|
|
||
| # pg-compat is outside the workspace, so the per-crate matrices above | ||
| # do not lint it. | ||
| - name: Format pg-compat |
There was a problem hiding this comment.
Format pg-compat and Clippy pg-compat sit in front of the gate in the same job. Filling the lint gap for the out-of-workspace crate is the right call — nothing else covers it — but coupling it to the wire verdict has two costs: (a) clippy -D warnings under dtolnay/rust-toolchain@stable reds this check whenever a new stable rustc adds a lint, and (b) when it does, the steps that actually answer "do published readers still open our bytes?" never run, because the lint precedes them and the implicit success() on their if skips them.
Once #262 makes Wire compat (published pg-core) a required check, that is a required check going red for reasons unrelated to what its name claims. Either split the pg-compat lint into its own job, or move these two steps after the seal/read steps. Non-blocking, but cheaper to decide now than after it is required.
| if: steps.seal.outcome == 'success' | ||
| uses: actions/upload-artifact@v4 | ||
| with: | ||
| name: wire-compat-artifacts-${{ github.sha }} |
There was a problem hiding this comment.
wire-compat-artifacts-${{ github.sha }} records the merge commit on pull_request events, not the head commit. Verified on this PR's own run: the artifact is wire-compat-artifacts-821c3bc26f8e7710cb776c2e9479f6bf1abbba28 while the PR head is 394ef0228042548e8d550d979ce525fb6b0c00e2.
pg-compat/README.md says "CI records which commit sealed a set in the uploaded artifact's name", and leans on that because the manifest deliberately carries no commit hash. But that merge sha belongs to no branch and is GC'd, so the name does not identify a tree anyone can re-seal from.
| name: wire-compat-artifacts-${{ github.sha }} | |
| name: wire-compat-artifacts-${{ github.event.pull_request.head.sha || github.sha }} |
Keeps the intra-run handoff to #261 working (same run, same expression on both sides) and makes the name resolvable afterwards. Nit.
| - '.github/workflows/build.yml' | ||
|
|
||
| - if: steps.changes.outputs.wire == 'true' | ||
| uses: dtolnay/rust-toolchain@stable |
There was a problem hiding this comment.
Nit on the pinning rationale, not the pin. The comment above pins paths-filter because "this step decides whether the gate's teeth engage" — but dtolnay/rust-toolchain@stable here is a mutable branch ref, which is weaker than the retagged-tag case the pin defends against, and it supplies the compiler that both seals the samples and builds the reader. It has at least as much say in the verdict. actions/checkout@v4 and actions/upload-artifact@v4 are likewise floating.
Consistent with the rest of build.yml, so fine to leave as-is — but the stated reasoning does not survive being applied to only one of the four actions. Either pin all four or soften the comment.
|
Reviewed #266 and requested changes — review here. Confirmed the self-test worked: One blocker: the Also flagged: the Everything else checked out clean — Conventional Commit title, no piped |
… edited trigger, lint split, permissions, head-sha artifact
|
/dobby all six addressed:
Docs updated in the same pass: CLAUDE.md's 'NOT yet a CI gate / run by hand' clauses replaced with the landed reality; 'proposed' dropped in pg-compat/README.md. The push re-runs the gate (build.yml is in the filter). Please re-review. |
|
Thanks for the detailed rundown — I'm on it. Kicking off a fresh review of PR #266 now that all six points are addressed and the gate has re-run. |
There was a problem hiding this comment.
All six from the last round check out, and the gate is live rather than proposed: run 30339618753 on 62f2585 sealed the sample set with HEAD, uploaded wire-compat-artifacts-62f2585093d5a679dfc144fc9046e24b9e9bc102, and opened it with published pg-core. Every step green, Lint pg-compat included. CLAUDE.md's present-tense rewrite is now backed by a workflow file in the same tree, which is the direction that wording is supposed to travel.
One thing left, and it is the same class of defect the head-sha change was made to fix. pg-compat/README.md still tells #261 to download wire-compat-artifacts-${{ github.sha }}. On a pull_request event that is the merge commit (4bbbbdb on this run), not the head sha the job actually used (62f2585), so a wire-compat-js job written against that sentence 404s on every PR. The description says the README's claim about resolvable commit names is true again; that one expression is what still makes it false. Mirror head.sha || github.sha there and this is done.
Three nits inline, none of them blocking.
Checked and cleared, so you do not have to re-litigate them:
editedearns its place.on: pull_request:with notypes:key subscribes to opened, synchronize and reopened only. A base retarget fireseditedand nothing else, whether a human does it or GitHub does it automatically when a stacked base is deleted on merge, so a paths-filter verdict would otherwise stay attached to the old base while GitHub reports the check as current. We watched that happen on cryptify#201, where an automatic retarget fired no run at all. The comment above the key is accurate.workflow_dispatchdoes not silently misreport. dorny/paths-filter defaultsbaseto the repository default branch on events that are neither push nor pull_request, so a manual dispatch from a branch that touches the wire surface does run the seal and read. Only a dispatch onmainitself compares against the previous commit and no-ops. If you want a force-run from main,|| github.event_name == 'workflow_dispatch'on the conditional steps buys it, but nothing is broken today.- The step-level filter is the right shape for something destined to be a required check, and build.yml sits in both filter lists, so a change to the jobs re-runs them.
| One thing for [#261] to plan around. The `wire-compat-rust` job seals | ||
| and uploads only when the PR touches the wire surface; on every other PR it | ||
| reports success with no artifact attached. A `wire-compat-js` job with | ||
| `needs: wire-compat-rust` that downloads |
There was a problem hiding this comment.
Docs still name the old artifact, three lines below this one: wire-compat-artifacts-${{ github.sha }}.
The job now uploads wire-compat-artifacts-${{ github.event.pull_request.head.sha || github.sha }} (build.yml:153). On a pull_request event github.sha is the merge commit, not the head sha, and this very run proves it: the artifact landed as wire-compat-artifacts-62f2585093d5a679dfc144fc9046e24b9e9bc102 (head) while the PR's merge commit is 4bbbbdb. A wire-compat-js job written against this sentence therefore 404s on every PR, which is the silent-gate failure mode the head-sha change was made to avoid.
Mirror the head.sha || github.sha expression here. The advice that follows, about repeating the path condition on the download or sealing locally in the JS job, stays correct.
| # `edited` included for base retargets (e.g. a stacked PR's base merging): | ||
| # paths-filter's verdict depends on the base, and without `edited` a stale | ||
| # verdict stays attached to the unchanged head sha. | ||
| types: [opened, synchronize, reopened, edited] |
There was a problem hiding this comment.
Keep edited — it is doing real work. A base retarget fires edited and none of the three default types, so without it this job's paths-filter verdict would stay pinned to the old base while GitHub reports the check as current. The comment is right.
The cost is the other half of edited: it also fires on every title and description edit, and build.yml has no concurrency group, so each edit re-runs the whole workflow (4x Test, 4x Clippy, 4x Format, plus 3 wasm browser jobs including a macOS runner) with nothing cancelling the superseded run. Worth pairing with:
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: truePre-existing gap rather than something this PR introduced; edited just makes it cheap to hit. Non-blocking.
| - if: steps.changes.outputs.wire == 'true' | ||
| uses: dtolnay/rust-toolchain@stable | ||
| with: | ||
| components: clippy, rustfmt |
There was a problem hiding this comment.
Leftover from the lint split: this job no longer runs fmt or clippy, so components: clippy, rustfmt installs two components nothing in it uses. A bare - uses: dtolnay/rust-toolchain@stable (keeping the if:) is enough for the seal and read steps. The components belong in pg-compat-lint, where they already are.
| - 'pg-compat/**' | ||
| - 'Cargo.lock' | ||
| - 'Cargo.toml' | ||
| - '.github/workflows/build.yml' |
There was a problem hiding this comment.
The two filters: blocks now duplicate Cargo.lock, Cargo.toml and .github/workflows/build.yml (lines 179-184 here and 121-132 in wire-compat-rust). For a pair of gates whose whole value is not going silent, drift between the two lists would be invisible, so a one-line comment on each saying they are kept in step would earn its keep. Non-blocking.
|
Re-reviewed #266 — all six items from last round check out, and the gate itself is live now: run One thing still needs a tweak, so I requested changes: Also left 3 non-blocking nits inline (no Checked the Review: #266 (review) |
…ATIBILITY.md Review follow-ups on the Node half of the wire-compat gate. An error raised before the first reader call left `verifyCase` as an unhandled rejection: the child exited 1 with an empty stdout, and `childFailures` fell through to the last stderr line that is not a stack frame, which for Node's uncaught-error dump is its own version banner. A missing `stream.bin` reported `reader exited with status 1 before it could report: Node.js v22.23.1`, with the errno and the path gone. That is the state a partial artifact download in CI reaches. It is reported on stdout now like every other failure, and `gate-teeth.test.mjs` holds it there. `COMPATIBILITY.md` and the package README both promised that `test/manifest.test.mjs` fails when `src/readers.mjs` and the document's reader list drift. It did not: the test compared `readers()` against a literal in the test file, so adding a version to the document alone stayed green, which is the drift direction that matters. `src/support-window.mjs` parses the fenced `<registry> <package> <versions...>` block instead and the test compares the `npm` rows, refusing a document with no block and one with no npm row rather than comparing against nothing. Also: each reader's declared `version` is compared against the `package.json` of the alias npm resolved, so the two-place edit cannot run one version while labelling it another; the coverage invariant counts readers pointed at a case rather than readers that pushed a name whether or not the case opened; `process.exitCode` replaces `process.exit()` so a hand-run on macOS, where pipe writes are asynchronous, cannot lose the failure lines it is being read for; and `pg-compat/README.md` describes the JS job as pending and gating on the seal outcome the sealing job publishes, which is what the proposed YAML does, rather than as a job that re-runs the path filter. CLAUDE.md's pointer to the `wire-compat-rust` YAML follows it from the merged #265 to #266. Refs #261, #251, #247. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
/dobby fixed — pg-compat/README.md:148 now names the artifact with the same |
There was a problem hiding this comment.
Third round, on head a9138a0. The round-2 blocker is cleared: pg-compat/README.md now names the artifact with the same github.event.pull_request.head.sha || github.sha expression the job uses, and I checked the end state rather than the diff text — this PR's run (30341907759) uploaded wire-compat-artifacts-a9138a074264b08478bfce3b78af017e257ed224, which is the head sha, so #261 written from that sentence will download an artifact that exists.
Also verified on this head:
- All 23 checks pass, including both new jobs:
Wire compat (published pg-core)green in 1m15s andLint pg-compatin 32s. The self-test in the PR body holds. dorny/paths-filter@7b450fff21473bca461d4b92ce414b9d0420d706really is v4.0.2 — I cloned the action and confirmed the tag points at that commit (and so, currently, does the floatingv4).- The step-filter shape is correct for a future required check, and build.yml is in both filters so a change to the jobs re-runs them.
- Both
run:blocks with multi-line scripts declareshell: bash; no unguarded pipes anywhere in the new YAML. - Docs accuracy both ways: CLAUDE.md's move from "NOT yet a CI gate: the YAML sits in a comment on PR #265" to present tense is now backed by a workflow file in the same tree, which is exactly the direction that wording is supposed to travel once the patch lands. Repo-specific CI knowledge stayed in the repo's own CLAUDE.md.
One blocking item, one line: the artifact upload is not idempotent across run attempts. upload-artifact@v4 defaults to overwrite: false, artifacts are scoped to the run and not the attempt, so Re-run failed jobs after the reader step reds dies at the upload with a 409 and never computes the wire verdict it was re-run to get. overwrite: true fixes it. Raising it as blocking rather than a note for one reason: the bot cannot push .github/workflows/, so fixing this after merge costs another patch-in-comment plus a maintainer apply cycle, and once #262 makes this check required an unre-runnable red is a stuck PR. Details inline at line 151.
Everything else is non-blocking: the workflow-level edited trigger now re-runs all 17 of build.yml's check runs including the macOS wasm job with no concurrency: block to cancel superseded runs (line 9); no outputs: on wire-compat-rust, which is what pushes #261 toward a third copy of the path list (line 108); workflow_dispatch falls back to last-commit detection (line 118); the unused components: clippy, rustfmt from the last round is still there (line 137); and the README paragraph needs a re-wrap after the edit (README line 144).
| # red gate still leaves the bytes that broke it. | ||
| - name: Upload the sample set | ||
| if: steps.seal.outcome == 'success' | ||
| uses: actions/upload-artifact@v4 |
There was a problem hiding this comment.
Blocking, one line. actions/upload-artifact@v4 defaults to overwrite: false, and its own action.yml says that means "the action will fail if an artifact for the given name already exists". Artifact names are scoped to the workflow run, not the run attempt — I checked this PR's own run: the REST list endpoint is /actions/runs/30341907759/artifacts with no attempt parameter, and the returned object carries only workflow_run: {id, head_branch, head_sha, ...}, no run_attempt.
The failure mode is exactly the path this gate exists to walk. Attempt 1: seal OK, upload OK, Open it with published pg-core reds — either a real wire break or a flake (that step resolves pg-core 0.6.1 from crates.io under --locked, so a network blip is a normal cause). Maintainer clicks Re-run failed jobs. Attempt 2: seal succeeds, and the job dies here on a 409 conflict before ever reaching the reader. The re-run reports an artifact-plumbing error instead of the wire verdict it was re-run to get, and "Re-run all jobs" hits the same wall.
This is a well-trodden v4 problem, not a theory — a search for the conflict text plus "re-run" returns 36 issues/PRs, including several whose whole content is this fix: nasa/fprime-actions#11 ("set overwrite: true on artifact upload so re-run works"), apache/datafusion-comet#3723 ("resolve CI artifact upload conflicts on workflow re-runs"), icook/tiny-congress#616 ("gitleaks SARIF artifact upload fails with 409 Conflict on re-runs").
uses: actions/upload-artifact@v4
with:
name: wire-compat-artifacts-${{ github.event.pull_request.head.sha || github.sha }}
path: ${{ runner.temp }}/wire-compat-artifacts
retention-days: 7
overwrite: trueoverwrite has been an input since v4.2.0 and the floating v4 tag currently resolves to v4.6.2 (ea165f8), so this works as written. Worth doing in this PR rather than after: once #262 makes Wire compat (published pg-core) required, a re-run that cannot go green is a stuck PR — and the bot has no workflows permission, so the follow-up fix costs another patch-in-comment plus a maintainer apply cycle. You are already in the file.
| # `edited` included for base retargets (e.g. a stacked PR's base merging): | ||
| # paths-filter's verdict depends on the base, and without `edited` a stale | ||
| # verdict stays attached to the unchanged head sha. | ||
| types: [opened, synchronize, reopened, edited] |
There was a problem hiding this comment.
Keeping edited is right and I confirmed the reasoning in the comment: a base retarget fires edited and none of the three default types, so without it the paths-filter verdict stays pinned to the old base while GitHub reports the check as current. Not asking you to drop it.
The cost is that types: is a workflow-level key, so this re-triggers all of build.yml, not the two jobs the comment reasons about. I counted this PR's own run (30341907759): 17 check runs from 9 job definitions, including the three Run wasm tests in browsers jobs, each of which builds wasm-pack from source, one of them on macos-latest at 10x billing (5m3s here). And GitHub fires pull_request.edited for title and body edits, not just base retargets. build.yml has no concurrency: block, so nothing cancels the superseded run either — after this change, fixing a typo in a PR description buys a second full CI run including a macOS build.
Either of these settles it, both non-blocking:
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: trueor, on the expensive jobs only, if: github.event.action != 'edited' || github.event.changes.base. The edited payload carries changes.base only when the base actually moved, so that separates retargets from prose edits precisely; when action is not edited the whole changes object is absent and the expression short-circuits on the left. The concurrency block is the smaller change and helps every trigger, not just this one.
| # pending forever on PRs that do not touch the wire surface. This shape always | ||
| # reports and only does the expensive work when it needs to. | ||
| # --------------------------------------------------------------------------- | ||
| wire-compat-rust: |
There was a problem hiding this comment.
Non-blocking, but cheapest to do while you are here: this job never surfaces the filter verdict, so a downstream job cannot read it. That is what forces pg-compat/README.md:150 to advise #261 to "Repeat the same path condition on the download" — which would be the third copy of the path list, since pg-compat-lint at line 176 already carries a second one, deliberately different in content but identically named wire.
Copies of a gate's trigger list drift in the dangerous direction: add pg-ffi/** (or any future wire-adjacent tree) to one and forget the others, and the gate silently stops running with no job going red — for a pair of gates whose entire value is not going silent, that is the failure mode to design against.
wire-compat-rust:
name: Wire compat (published pg-core)
runs-on: ubuntu-latest
outputs:
wire: ${{ steps.changes.outputs.wire }}That turns #261's half into a one-line if: needs.wire-compat-rust.outputs.wire == 'true' and keeps one source of truth. Since #261 will be written from this file, adding the output now is what makes the README advice unnecessary rather than duplicated.
| - uses: actions/checkout@v4 | ||
| # SHA-pinned as the one action new to this repo; the rest follow the | ||
| # file's existing floating-ref convention. | ||
| - uses: dorny/paths-filter@7b450fff21473bca461d4b92ce414b9d0420d706 # v4.0.2 |
There was a problem hiding this comment.
Nit, and it partly contradicts a note in my own memory that I have now corrected. I read src/main.ts at the pinned commit 7b450ff: on a non-pull_request event the action calls getChangedFilesFromGit(base, ref, ...), where head falls back to github.ref and base falls back to repository.default_branch. On a workflow_dispatch from main those are the same short name, so isBaseSameAsHead is true; beforeSha is set only when eventName === 'push', so it is null here, and the action logs 'before' field is missing in event payload - changes will be detected from last commit and returns getChangesInLastCommit().
So a manual dispatch on main decides whether to run the gate from main's single most recent commit, and reports Wire compat (published pg-core) green whenever that one commit happens to miss the wire surface. That sits a little oddly with the header comment's "always reports and only does the expensive work when it needs to" — dispatch is the one entry point where a human is explicitly asking for the work. (Dispatch from a non-default branch is fine: base resolves to main and you get a real diff.)
If dispatch should force the gate, steps.changes.outputs.wire == 'true' || github.event_name == 'workflow_dispatch' on the three conditional steps covers it.
| - if: steps.changes.outputs.wire == 'true' | ||
| uses: dtolnay/rust-toolchain@stable | ||
| with: | ||
| components: clippy, rustfmt |
There was a problem hiding this comment.
Carried forward from the last round and still open — flagging once more, still non-blocking. After the lint split this job runs only cargo run (seal) and cargo test (read); neither uses clippy or rustfmt, so these two components are installed and never used. A bare - uses: dtolnay/rust-toolchain@stable with the same if: is enough. pg-compat-lint needs them and already declares them at line 188.
| ### Consuming the set from another job | ||
|
|
||
| One thing for [#261] to plan around. The proposed `wire-compat-rust` job seals | ||
| One thing for [#261] to plan around. The `wire-compat-rust` job seals |
There was a problem hiding this comment.
The fix is correct and I verified the end state rather than just the text: the artifact this PR's run actually produced is wire-compat-artifacts-a9138a074264b08478bfce3b78af017e257ed224, i.e. the head sha, matching both build.yml:153 and this sentence — so #261, written from here, will download something that exists.
Only the reflow is left ragged. This paragraph otherwise wraps at 74-76 columns, but dropping "proposed" left this line at 69 and the longer artifact name orphaned unconditionally therefore hard-fails onto a 36-column line 149. Re-wrap the paragraph.
|
Re-reviewed on I still had to request changes, but on a new, unrelated issue I found this pass: Everything else is non-blocking (4 nits + a leftover, all in the review) — CI is fully green, 10 rules checked with no violations. Full review: #266 (review) |
|
/dobby if there is nothing wrong with the pr, please approve it. |
|
On it! I'll take a look at PR #266 and approve it if everything checks out. |
There was a problem hiding this comment.
Fourth round, on head a9138a0 — approving.
- All 23 checks pass, nothing in flight.
Wire compat (published pg-core)green in 1m15s,Lint pg-compatin 32s, and the run uploadedwire-compat-artifacts-a9138a074264b08478bfce3b78af017e257ed224— the head sha the README sentence now names, so #261's download step will find it. - Ran the gate locally against this branch as well: the sealer writes 11 files,
cargo test --manifest-path pg-compat/Cargo.toml --lockedopens all of them with published pg-core=0.6.1, and pg-compat's fmt/clippy are clean. - Rule check over the CI-and-docs rules that apply here found nothing: both multi-line
run:blocks declareshell: bashand no block pipes; the filter is a step (noton: paths:) so the job always reports, which is what #262 needs;build.ymlis in both filter lists so a change to the jobs re-runs them; neither job touchessecrets.*, so it also works on the Dependabot Cargo.lock PRs it most needs to guard; and CLAUDE.md's move to present tense is backed by a workflow file in the same tree. workflow_dispatchand theeditedtrigger are unchanged from round 2 and still correct as written — no action.
On the round-3 blocker (overwrite: true), I'm not holding it: the upload's name is keyed on the head sha rather than the run attempt, so a "Re-run failed jobs" after a red reader step may hit a 409 at the upload. What I could not confirm either way is the premise that decides it — whether an artifact from the previous attempt is still attached to the run. Without that, calling it merge-blocking overstated what I knew, and it costs a re-run convenience path rather than the gate's verdict. It stays a one-line change you can land any time; worth doing before #262 makes this check required, not before merge.
Four non-blocking notes inline: no concurrency group now that edited re-runs the full 20-job matrix including macOS Safari (line 9), unused toolchain components after the lint split (137), upload-artifact@v4 against delivery.yml's v7/v8 (151), and a ragged re-wrap in the README paragraph (149).
| # `edited` included for base retargets (e.g. a stacked PR's base merging): | ||
| # paths-filter's verdict depends on the base, and without `edited` a stale | ||
| # verdict stays attached to the unchanged head sha. | ||
| types: [opened, synchronize, reopened, edited] |
There was a problem hiding this comment.
types: is a workflow-level key, so edited re-triggers all of build.yml, not just the two jobs the comment is about — and build.yml has no concurrency key anywhere (delivery.yml:73 does). Every PR title/description edit therefore starts a full duplicate run of ~20 jobs without cancelling the in-flight one, including the three Run wasm tests in browsers jobs; the macOS Safari one took 5m3s here, and macOS runners bill at 10x.
Keeping edited is right — a base retarget fires it and none of the three default types, so the paths-filter verdict would otherwise stay pinned to the old base. The fix belongs on the other side:
concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: ${{ github.event_name == 'pull_request' }}at the top of build.yml caps it at one live run per PR while leaving main pushes uncancelled. Non-blocking.
| - if: steps.changes.outputs.wire == 'true' | ||
| uses: dtolnay/rust-toolchain@stable | ||
| with: | ||
| components: clippy, rustfmt |
There was a problem hiding this comment.
These two components are installed but never used: after the lint split this job runs only cargo run (seal) and cargo test (read). A bare - uses: dtolnay/rust-toolchain@stable with the same if: is enough. pg-compat-lint does need them and declares them itself at line 188.
| # red gate still leaves the bytes that broke it. | ||
| - name: Upload the sample set | ||
| if: steps.seal.outcome == 'success' | ||
| uses: actions/upload-artifact@v4 |
There was a problem hiding this comment.
Version skew with the repo's own convention: upload-artifact@v4 here, while delivery.yml:139 uses upload-artifact@v7 and delivery.yml:157/:201 use download-artifact@v8. v4 works — this PR's run proves it — but #261's JS consumer will be written against v8 download, and the only artifact producer in build.yml sitting three majors behind the only other producer in the repo is the kind of drift that stays invisible until someone bumps one of them.
| if: steps.seal.outcome == 'success' | ||
| uses: actions/upload-artifact@v4 | ||
| with: | ||
| name: wire-compat-artifacts-${{ github.event.pull_request.head.sha || github.sha }} |
There was a problem hiding this comment.
The round-3 item, downgraded to a note. upload-artifact's action.yml documents overwrite defaulting to false = "the action will fail if an artifact for the given name already exists", and this name is keyed on the head sha, not on github.run_attempt — so a re-run of this job on an unchanged head would upload the same name twice into one run id.
What I could not settle is whether artifacts from a previous attempt stay attached to the run: actions/upload-artifact#585 shows prior-attempt artifacts becoming inaccessible, but its repro used attempt-numbered names. If they do stay attached, "Re-run failed jobs" after a flaky reader step dies at the upload before the reader ever runs. overwrite: true is one line of insurance either way — your call, and it can land after merge.
| `needs: wire-compat-rust` that downloads | ||
| `wire-compat-artifacts-${{ github.sha }}` unconditionally therefore hard-fails | ||
| `wire-compat-artifacts-${{ github.event.pull_request.head.sha || github.sha }}` | ||
| unconditionally therefore hard-fails |
There was a problem hiding this comment.
Re-wrap left ragged by the edit: this paragraph wraps at 69-79 columns on every other line (144-148, 150-151), but line 149 is 36 characters. Rewrapping 147-149 as `needs: wire-compat-rust` that downloads `wire-compat-artifacts-${{ github.event.pull_request.head.sha || github.sha }}` / unconditionally therefore hard-fails on unrelated PRs rather than restores it. Cosmetic — the content is correct, and I checked the end state rather than the prose: this PR's run uploaded wire-compat-artifacts-a9138a074264b08478bfce3b78af017e257ed224, the head sha this sentence names.
|
Done — approved. Nothing wrong with it: all 23 checks pass, and I re-verified the CI/docs rules that apply here (nothing flagged) plus re-ran the wire-compat gate locally to confirm. I also dropped last round's |
#267) * test(compat): published pg-wasm/pg-js readers open HEAD-sealed samples The Node half of the wire-compat gate (#261). `pg-compat-js` opens the sample set the Rust half seals with the npm readers in COMPATIBILITY.md's support window: `@e4a/pg-wasm` 0.6.1 through both of its unsealers, and `@e4a/pg-js` 2.3.3 and 1.11.0 through `PostGuard#open`, the entry point consumers actually use. Versions are installed side by side as `npm:` aliases and pinned by a committed lockfile. Each case runs in its own process for the reason the Rust half does it: a reader handed a shifted header can die on the allocation rather than returning an error, and one dead case should not take the run down. Two limitations of the published SDKs are declared rather than hidden, because both would otherwise look like a gate that reads nothing. pg-js is stream-mode only in both directions, so the memory-mode cases are the pg-wasm reader's alone; and pg-js 1.x never surfaces a private signing policy, so for that reader the gate requires it absent. The run prints which reader opened which case, and fails when a case is opened by no reader or a reader opens no case. Also corrects `pg-compat/README.md`, which had memory mode as what pg-js `toBytes` produces — it seals with `sealStream`. The `wire-compat-js` job that runs this in CI cannot be pushed by the app; the YAML is in a PR comment. Refs #247, #251 Closes #261 * docs(compat): drop em dashes from pg-compat-js README prose * test(compat): report reader errors, and check the window against COMPATIBILITY.md Review follow-ups on the Node half of the wire-compat gate. An error raised before the first reader call left `verifyCase` as an unhandled rejection: the child exited 1 with an empty stdout, and `childFailures` fell through to the last stderr line that is not a stack frame, which for Node's uncaught-error dump is its own version banner. A missing `stream.bin` reported `reader exited with status 1 before it could report: Node.js v22.23.1`, with the errno and the path gone. That is the state a partial artifact download in CI reaches. It is reported on stdout now like every other failure, and `gate-teeth.test.mjs` holds it there. `COMPATIBILITY.md` and the package README both promised that `test/manifest.test.mjs` fails when `src/readers.mjs` and the document's reader list drift. It did not: the test compared `readers()` against a literal in the test file, so adding a version to the document alone stayed green, which is the drift direction that matters. `src/support-window.mjs` parses the fenced `<registry> <package> <versions...>` block instead and the test compares the `npm` rows, refusing a document with no block and one with no npm row rather than comparing against nothing. Also: each reader's declared `version` is compared against the `package.json` of the alias npm resolved, so the two-place edit cannot run one version while labelling it another; the coverage invariant counts readers pointed at a case rather than readers that pushed a name whether or not the case opened; `process.exitCode` replaces `process.exit()` so a hand-run on macOS, where pipe writes are asynchronous, cannot lose the failure lines it is being read for; and `pg-compat/README.md` describes the JS job as pending and gating on the seal outcome the sealing job publishes, which is what the proposed YAML does, rather than as a job that re-runs the path filter. CLAUDE.md's pointer to the `wire-compat-rust` YAML follows it from the merged #265 to #266. Refs #261, #251, #247. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * test(compat): check the alias package, not just its version The installed-version check closed the version half of the two-place edit and left the package half open: an alias resolving to the right version of the wrong package passed while every message carried the declared package name, which is the mislabelled support window the check exists to catch. `installedPackage` returns `{name, version}` and the test asserts both. Two docs pointers with it. `pg-compat/README.md` called #261 a PR; the work item is issue #261 and the YAML is a comment on PR #267. `COMPATIBILITY.md` claimed the whole `crates.io` line is spelled out in `pg-compat/Cargo.toml`, which pins 0.6.1 only; 0.5.10 is declared in the window and opened by no gate. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * ci: run the JS half of the wire-compat gate * docs: the reader list is enforced by both gate halves, not a review rule * docs: state the gates' real reader-list coverage; track the gaps in #268 * fix(pg-compat-js): close three silent-green gaps found in review - stderrTail kept the LAST non-frame stderr line, which for a Node crash dump is the 'Node.js vX' banner — the real error was dropped on exactly the run where the message matters most (a child that died before it could report). Now takes the first informative line and filters the banner/preamble too. - readManifest refused a set with no cases but not a case with no recipients; verify.mjs loops over recipients, so an empty list reported as opened cleanly without checkOpened ever running. - The coverage invariants were computed from the manifest, so a sealer that stopped emitting the memory-mode cases kept them satisfied and both halves went green having lost the only coverage memory mode has. The case-name set is now asserted explicitly. Verified against a freshly sealed set: 22/22 pass, and removing the mem cases from the manifest now fails the assertion (it previously passed). * style(pg-compat-js): rewrap the lines over the package's ~100-char limit Ten measured lines plus the one prose line I left over 80 when editing pg-compat/README.md in place. No behaviour change; the suite still passes 22/22. Cheaper now than as churn when a formatter lands. * fix(pg-compat-js): unref the pkg stub so a leaked one cannot hang the case process The child ends with `process.exitCode = ...` rather than process.exit(), because pipe writes are async on macOS and exiting outright truncates the failure lines the gate exists to print. So it exits only when the loop drains, and a listening server is a live handle: a stub that outlived its case would hold the child open until runCase's 300s spawnSync timeout, turning a one-line failure into a stall. Today nothing reaches that: startPkgStub is the last fallible statement in the pg-js reader's load(), and verify.mjs always unloads. But that invariant lives in two other files and nothing stated or enforced it, which is what made this worth closing rather than just recording. Three parts: unref() so the leak cannot hang anything, a comment at the one place the invariant can be violated, and a test that spawns a child leaking a stub and asserts it still exits. Verified: the probe exits in 0s (it would previously have run to the timeout), and the full gate still passes 23/23 against a freshly sealed set with all five cases opened — unref does not stop the stub serving, since the in-flight request and pg-js's own client socket keep the loop alive. --------- Co-authored-by: dobby-yivi-agent[bot] <275734547+dobby-yivi-agent[bot]@users.noreply.github.com> Co-authored-by: Claude Opus 5 <noreply@anthropic.com> Co-authored-by: Ruben Hensen <ruben.hensen@protonmail.com>
The workflow half of #265, which merged without it (the App can't push workflows), leaving pg-compat inert — same pattern as the dotnet pin. This is dobby's YAML from the #265 comment with two changes:
dorny/paths-filterbumped v3→v4.0.2 and SHA-pinned, since that step decides whether the gate's teeth engage.Design notes carried over from the comment: the path filter is a step, not
on: paths:, so the job always reports a status and can become a required check (#262) without wedging non-wire PRs; the artifact upload happens before the reader runs, so a red gate still leaves the bytes that broke it for #261's JS half.Self-testing: this PR touches build.yml, which is in the filter, so the full gate runs here — expect
Wire compat (published pg-core)green below.Refs #260 #251. Part of #247 (workstream C).