From 68b622e07302387a43b338eb2e9378926aa1c09f Mon Sep 17 00:00:00 2001 From: Build System Date: Sat, 1 Aug 2026 22:35:36 +0200 Subject: [PATCH 1/2] docs: how to sync skeleton changes into a derived module The prompt covered the first-time bring-up and never said so, so a task that was really 'forward one skeleton improvement' landed in an eight-phase, eight-PR document. Splits the two: README gains a sync checklist, the prompt gains a scope header naming the tell (ci/ layout, ci.yml as sole pull_request entry, ci/linter/) and pointing at the README for the smaller job. Phase 2 also gained the port-band rule. It was absent, which is how modules ended up running prove on Test::Nginx's unarbitrated default 1984 against builder02's six slots; the collision surfaces as a bind() failure that reads as a module regression. --- README.md | 57 ++++++++++++++++++++++++++++++++- ci/PROMPT-standardize-module.md | 24 ++++++++++++++ 2 files changed, 80 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index e1dd5eb..c09515a 100644 --- a/README.md +++ b/README.md @@ -70,7 +70,8 @@ ci/ everything that only exists to test/build the module workflow_policy.py repo-policy checks over .github/workflows/ selftest.sh negative controls for the gate itself fixtures/policy/ trees the policy checks must go RED on - PROMPT-standardize-module.md prompt: bring an existing module to this standard + PROMPT-standardize-module.md prompt: first-time bring-up of an existing + module (see also: syncing, below) .githooks/pre-commit tracked commit gate (opt in: core.hooksPath) .github/workflows/ the CI workflows, see below ``` @@ -274,6 +275,60 @@ vacuously. is nginx-core noise only. A suppression over module frames turns the gate green permanently. +## Syncing skeleton changes into a derived module + +A module is cloned once and then drifts. This is the recurring half of that +relationship — the *first* bring-up of an existing module is a different, much +larger job and has its own document: +**[ci/PROMPT-standardize-module.md](ci/PROMPT-standardize-module.md)**. + +Use that prompt when the target has never been standardised (no `ci/` layout, no +`ci.yml` orchestrator, no `ci/linter/`). Use the list below when the target is +already on the standard and you are forwarding a specific improvement. + +**Per module, one session, one repo:** + +- **Establish the anchor.** Whatever the target last took from here — a + `CHANGES` entry, a `vN` tag, or the merge commit of its standardisation PR. + Without an anchor you are guessing at scope. `git log --oneline ..HEAD` + in this repo is the candidate set; [CHANGES](CHANGES) says what each one was + *for*. +- **Select, don't sweep.** Take one concern per PR. A sync PR carrying four + unrelated skeleton commits is not independently revertible and its CI failure + does not tell you which change broke it. +- **Re-derive, never copy blind.** Every workflow here carries skeleton-specific + paths, file names, runner labels and version pins. A file copied verbatim + compiles, runs, and gates the wrong thing. The reference for what each gate + must *prove* is the Phase 2 table in the prompt. +- **Check the four drift classes first** — none is visible from a green run, + each has bitten a module here: + - `prove` invoked with no `TEST_NGINX_PORT`. Test::Nginx's default 1984 is + unarbitrated and `builder02` runs six slots against one network — the + collision reads as a module regression (`bind() … Address already in use`). + - `--gcov-object-directory` in `ci/tools/coverage.sh` fails argparse on + gcovr < 7.0. Only matters where gcovr is unpinned and the runner is not + controlled — the fork arm's `ubuntu-latest`. + - a `.github/versions.env` consumer that sources the file without validating + it executes any line that is not a pin. + - `ci/linter/workflow_policy.py` older than the YAML-parse rewrite matches + workflow YAML with regexes, and valid YAML (`.yaml` extension, inline + `on: [pull_request]`, a comment after a job key) makes all three policy + checks silently vacuous. + + The last two only exist in a module that already took those files; the first + two apply to any module that runs `prove` or reports coverage. +- **Verify the gate red.** A gate you moved but never saw fail is a gate you did + not verify. State the probe in the PR body. +- **Remote CI green before merge**, then squash, delete the branch, and bump the + superrepo gitlink for that checkout. +- **Send improvements back.** A fix or a layer the target grew that this repo + lacks gets a PR *here*. The template is only worth keeping if it stays ahead + of its clones. + +Record the anchor you synced to in the target's memory mirror +(`/opt/myguard/memory/labs//index.md`), or the next session +re-derives it from scratch. + ## Requirements Build: `build-essential curl libpcre2-dev zlib1g-dev` diff --git a/ci/PROMPT-standardize-module.md b/ci/PROMPT-standardize-module.md index dcef90d..04616e8 100644 --- a/ci/PROMPT-standardize-module.md +++ b/ci/PROMPT-standardize-module.md @@ -8,6 +8,20 @@ The reference implementation is `/opt/myguard/labs/nginx-skeleton-module` (this repo). **Read the reference before changing the target** — the point is not to make the target look similar, it is to give it the same *gates*. +## Scope — is this the right document? + +This is the **first-time** bring-up: a module that has never been standardised. +It is eight phases, eight PRs, and one fresh session per module. + +If the target is **already** on this standard and you are forwarding one later +skeleton improvement into it, this document is the wrong size for the job. Use +the sync checklist in the reference's README instead — *Syncing skeleton changes +into a derived module* — and come back here only for a phase the target never +took at all. + +Tell which you are looking at: a standardised target has a `ci/` layout, a +`ci.yml` orchestrator as its sole `pull_request` entry point, and `ci/linter/`. + --- ## Context you are given @@ -136,6 +150,16 @@ Also port, adapting paths: Members are `workflow_call:`-only; only `ci.yml` has `pull_request:`. Two entry points run everything twice per PR and defeat the laning. +**Every job that runs `prove` must pin its own port band.** Test::Nginx binds +`TEST_NGINX_PORT`, default 1984, and nothing arbitrates it; `builder02` runs six +slots against one network, so two jobs on the default collide and the loser dies +with `bind() to 127.0.0.1:1984 failed (98: Address already in use)` — which reads +as a module regression and is not one. The reference declares a distinct +job-level `TEST_BASE_PORT` per workflow (`build-test.yml` 19200, `ci-deep.yml` +19400), passes it as `TEST_NGINX_PORT`, and proves the band free and +non-ephemeral with `ci/tools/max-port.sh` before binding. Give the target its own +band; two workflows sharing one number is the same bug with more steps. + **Badges:** README badge block must list the workflows in the SAME ORDER as the `## CI` table in that README, and both must match reality — a badge for a workflow that no longer exists renders a permanent grey "no status" and is From 04932de18413aa1860955807162a77987fdd4b2e Mon Sep 17 00:00:00 2001 From: Build System Date: Sat, 1 Aug 2026 22:42:10 +0200 Subject: [PATCH 2/2] docs: correct the port-band and gcovr claims Both from CodeRabbit on #6, both confirmed against the code. max-port.sh runs AFTER prove in build-test.yml and guards the runtime fixture, so the earlier text was wrong to describe the band as verified before binding. The prompt now states where the check belongs, says the reference does not do that for ci/t/, and tells a target to put it earlier rather than copy the order. The reference's own reorder is filed as a follow-up, not done here. The gcovr condition is the runner's gcovr major, not whether a pin exists: --gcov-object-directory arrived in 7.0, --object-directory is accepted by both. --- README.md | 19 +++++++++++++------ ci/PROMPT-standardize-module.md | 14 +++++++++++--- 2 files changed, 24 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index c09515a..89a0af2 100644 --- a/README.md +++ b/README.md @@ -302,12 +302,19 @@ already on the standard and you are forwarding a specific improvement. must *prove* is the Phase 2 table in the prompt. - **Check the four drift classes first** — none is visible from a green run, each has bitten a module here: - - `prove` invoked with no `TEST_NGINX_PORT`. Test::Nginx's default 1984 is - unarbitrated and `builder02` runs six slots against one network — the - collision reads as a module regression (`bind() … Address already in use`). - - `--gcov-object-directory` in `ci/tools/coverage.sh` fails argparse on - gcovr < 7.0. Only matters where gcovr is unpinned and the runner is not - controlled — the fork arm's `ubuntu-latest`. + - a job that binds test ports without its own declared band. Presence of + `TEST_NGINX_PORT` is not the check — the check is a **distinct job-level + band**, verified free with `ci/tools/max-port.sh` before anything binds it. + Test::Nginx's default 1984 is unarbitrated and `builder02` runs six slots + against one network, so the collision reads as a module regression + (`bind() … Address already in use`). Note the reference itself currently + runs that verification before the *runtime* suite only, not before `prove`. + - `--gcov-object-directory` in `ci/tools/coverage.sh`. The condition is the + **gcovr major version the job actually runs**, not whether the pin exists: + the `--gcov-`-prefixed spelling arrived in gcovr 7.0 and fails argparse + below it. `--object-directory` is accepted by both and is the portable + choice for a target whose runner gcovr you do not control — the fork arm's + `ubuntu-latest`. - a `.github/versions.env` consumer that sources the file without validating it executes any line that is not a pin. - `ci/linter/workflow_policy.py` older than the YAML-parse rewrite matches diff --git a/ci/PROMPT-standardize-module.md b/ci/PROMPT-standardize-module.md index 04616e8..0f925c4 100644 --- a/ci/PROMPT-standardize-module.md +++ b/ci/PROMPT-standardize-module.md @@ -156,9 +156,17 @@ slots against one network, so two jobs on the default collide and the loser dies with `bind() to 127.0.0.1:1984 failed (98: Address already in use)` — which reads as a module regression and is not one. The reference declares a distinct job-level `TEST_BASE_PORT` per workflow (`build-test.yml` 19200, `ci-deep.yml` -19400), passes it as `TEST_NGINX_PORT`, and proves the band free and -non-ephemeral with `ci/tools/max-port.sh` before binding. Give the target its own -band; two workflows sharing one number is the same bug with more steps. +19400) and passes it as `TEST_NGINX_PORT`; `ci/linter/lint-ci-ports.sh` fails the +build if a job starts the runtime driver without declaring a band. Give the +target its own band — two workflows sharing one number is the same bug with more +steps. + +`ci/tools/max-port.sh` proves a band is free and clear of the kernel ephemeral +range, and **belongs before the first step that binds it**. In the reference it +currently sits after `prove` and before the runtime suite, so `ci/t/` binds +unverified; put it earlier in the target rather than copying that order. The +whole value of the check is turning "unreproducible timeout" into a named +failure, which it cannot do for a step that already ran. **Badges:** README badge block must list the workflows in the SAME ORDER as the `## CI` table in that README, and both must match reality — a badge for a