feat: complete the template - self-validating CI, security defaults, rehearsed adoption - #6
Conversation
Signed-off-by: Prasanth Baskar <prasanth@8gears.com>
- release-please: grant issues: write. v5 labels its own release PR
(autorelease: pending/tagged) through the Issues API, which is a
separate scope from pull-requests.
- version baseline: ship 0.0.0 in version.txt and the manifest. A
template must seed adopters at zero; 1.1.0 made every new repo start
its first feat: release at 1.2.0.
- docs/RELEASES.md: add fix!: to the breaking-change row, stop claiming
non-feat/fix types produce no release, and document that hidden: true
is a changelog setting rather than a release gate. Add a dry-run
command so the bump can be confirmed before merging.
- docs/RELEASES.md: document the version baseline rule. Switching from
component tags (app-v1.1.0) to vX.Y.Z leaves no tag matching the
manifest, so release-please walks the whole history and repeats old
commits in the next changelog.
- apply-settings.js: collect section failures and fail the job instead
of downgrading every one to a warning. A section that silently did
not apply is drift nobody sees.
- apply-settings.js: distinguish 403/401 from not-configured when
exporting. settings.yml always carries an actions block, so omitting
it after an unreadable GET reported drift forever. Unreadable
sections are now dropped from both sides of the comparison and
warned about.
- CHECKLIST/README: include Taskfile.yml in the adoption copy lists -
release-assets.yml runs task release-assets. Point the release-assets
customization step at the Taskfile, note that new release jobs need
their own permissions block under permissions: {}, and keep
version.txt guidance out of the native release-type list.
Signed-off-by: Vadim Bauer <vb@container-registry.com>
The template had no CI of its own: twelve workflows, a github-script
module and a Go program, none of it checked. That is how an invalid
issue-template config, a release workflow that could never fire, and
four dangling documentation references all shipped at once.
Gates:
- hygiene.yml runs typos, actionlint, yamllint, zizmor, a pin check and
dependency-review on every push and pull request. Language-neutral,
so it keeps working after the Go pack is deleted.
- ci.yml covers build, test, lint, licence check and govulncheck for
the Go pack, and absorbs the old license-check workflow. govulncheck
also runs weekly, since new advisories land against unchanged code.
- .github/scripts/repo-lint.py checks what a linter cannot: YAML that
tokenizes but does not construct, JSON validity, labels the labeler
emits that settings.yml does not declare, release-please packages
that are not directories, version.txt against the manifest,
documentation that links to files the repository does not ship, and
placeholders left unreplaced after adoption. Each check exists
because that mistake actually shipped here.
- versions.env pins every tool with a checksum where one is published,
read by both the Taskfile and CI so local and CI runs agree.
- Taskfile grew build, test, lint, vuln-check, lint:yaml, lint:actions,
lint:repo, lint:pins, license-report, clean and info. `task check`
now runs the same gates CI does.
- lefthook runs the cheap gates pre-commit and `task check` pre-push.
Hardening, applied to every workflow:
- every action pinned to a full SHA with a version comment, enforced by
the pin check
- permissions: {} at the top level with least-privilege job scopes
- persist-credentials: false on every checkout
- concurrency groups, cancel-in-progress false where a run must not be
interrupted, and timeout-minutes everywhere
- fork guards on the jobs that write back or publish. The previous
guard compared repository_owner to event.repository.owner.login,
which is always true.
Defects fixed:
- ISSUE_TEMPLATE/config.yml: quote the placeholder URLs. Unquoted
{{DOCS_URL}} is a flow mapping used as a mapping key, so GitHub
rejected the whole template chooser.
- apply-settings.yml: drop the TODO forcing mode=apply. A scheduled run
is now a drift check and everything else applies and verifies.
- settings.yml: enable Discussions, which SUPPORT.md, ROADMAP.md, the
issue chooser and the welcome bot all link to.
- labeler.yml: component/config globbed *.yml at the root, so it landed
on nearly every pull request. Narrowed to real config paths, and both
labels it emits are now declared in settings.yml.
- delete current-settings.yml, a committed export of live state that
guaranteed the drift check would fail.
- CHANGELOG.md: remove the placeholder tail referring to a workflow
that does not exist.
- .gitignore: ignore only the local parts of .claude so shared agent
configuration can ship.
- drop the dangling scripts/export-settings.sh references; drift
detection is `gh workflow run apply-settings.yml -f mode=check`.
New files the gates demanded:
- SECURITY.md with a coordinated vulnerability disclosure policy,
response targets, embargo terms, safe harbour and a supported-version
table. No repository in the organisation had one.
- CONTRIBUTING.md, linked from SUPPORT.md and the welcome bot, which
both 404'd.
- MAINTAINERS.md.
Signed-off-by: Vadim Bauer <vb@container-registry.com>
No repository in the organisation had a SECURITY.md, secret scanning was off, and no repository had branch protection. A template that leaves all three to the adopter is where that came from. Repository settings, applied by settings-as-code: - secret scanning and push protection enabled. Both are free on public repositories and already the organisation default for new ones; push protection is the one that matters because it blocks the commit rather than reporting it afterwards. - private vulnerability reporting enabled, which is what makes the advisory link in SECURITY.md and the issue chooser actually work. It is not part of security_and_analysis, so apply-settings.js now splits it out to its own endpoint and exports it for drift checks. - a branch ruleset shipped active rather than commented out: pull request required, squash only, force push and deletion blocked, hygiene checks required, no bypass actors. Required approvals is 0 deliberately - a rule a solo maintainer cannot satisfy gets deleted wholesale, and the protections that matter do not depend on it. Supply chain: - dependabot.yml for actions, Go modules and Docker. Pinning to SHAs without an updater just pins to something old; Dependabot rewrites the SHA and its version comment together. - optional/renovate.json for projects that want grouped updates or need versions.env kept in sync, with a custom manager for the annotated pins. Documented as an either/or with Dependabot, not both. - codeql.yml as advanced setup, matrix over actions and go. The `actions` language is worth keeping even for a non-Go project. settings.yml keeps code scanning default setup off so alerts are not duplicated. Governance: - CODE_OF_CONDUCT gets an enforcement address, a confidentiality commitment, and a recusal rule. An enforcement section with nobody to report to enforces nothing. - CODEOWNERS with real rules, weighted towards .github and SECURITY.md since those change what runs with repository credentials. - dco.yml enabling individual remediation commits, so a first-time contributor can add a missing sign-off without rewriting their branch. - NOTICE, with a comment explaining what Apache-2.0 4(d) actually requires and what belongs in a licence report instead. Signed-off-by: Vadim Bauer <vb@container-registry.com>
The template published nothing verifiable. It also never built the Dockerfile in CI, so the one container artifact it shipped was never exercised. CRA Art. 24 steward duties from 11 Sep 2026 make release SBOMs an obligation rather than a nice-to-have, and a bill of materials that covers only the image does not describe the binaries most people download. Release artifacts (release-assets.yml): - syft SPDX SBOM over the source tree, uploaded with the release - actions/attest-build-provenance over the binaries - checksums.txt, generated by the same task that builds them - uses the local ./.github/actions/setup composite instead of a third-party setup-task action Container image (publish-image.yml, new, workflow_call): - multi-arch linux/amd64 and linux/arm64 - defaults to GHCR with GITHUB_TOKEN, so it works in a fresh repository with no secrets configured - cosign keyless signature, syft SPDX attestation, and build provenance, all applied by digest rather than by tag, because a tag can be repointed after the signature is made - buildx's own provenance and sbom are disabled to avoid two competing attestations for the same image release-please.yml calls both on release_created, then a document-artifacts job appends copy-pasteable cosign and gh attestation verify commands to the release body. It replaces its marked section rather than appending, so re-running does not duplicate it. Dockerfile: - base image pinned by digest, kept beside the tag in versions.env - explicit non-root USER, OCI labels, -trimpath and a stamped version - go.sum copied with a glob so the build works with or without dependencies main.go now carries the version variable the build stamps and a -version flag, with a test that guards the ldflags contract. The pipeline had a version stamp pointing at a variable that did not exist. Signed-off-by: Vadim Bauer <vb@container-registry.com>
Six discipline reviews (GitHub Actions supply chain, CISO/CRA compliance, Go and containers, platform and release engineering, OSS governance, adoption DX) produced 47 findings; a consumer advocate judged each for validity, value, genericity and maintenance cost. This lands the ones that survived. Two were severe and both were verified against this repository rather than taken on trust. Severe: settings.yml published private repositories settings.yml is PATCHed onto the repository by apply-settings on the first push to main, and it carried `visibility: public` and `is_template: true`. A repository created private from this template published itself, irreversibly, and became a template in the process. It also overwrote the adopter's description with the template's. All four keys are gone, with a comment saying why they must stay gone. The workflow's fork guard compared github.repository_owner to github.event.repository.owner.login, which is the same value on both sides and therefore always true; it now checks `fork`. Severe: the shipped branch ruleset deadlocked every release GitHub raises no workflow events for a ref pushed with GITHUB_TOKEN. Verified here: PR #4 was opened by release-please and the only run on that branch was PR Title, triggered later by an unrelated bot editing the PR. Every other workflow produced zero runs. A required status check would therefore never report on a release pull request and it could never be merged, and the predictable response is to delete the ruleset rather than the release automation. The ruleset now requires a pull request and no status checks, with the prerequisites and the app-token fix documented in docs/RELEASES.md. hygiene.yml gains a `required-checks` aggregate job with no path filter, which is the only context safe to require: a path-filtered workflow does not report at all on a pull request that misses its filter, so the check waits forever. Settings applier: - vulnerability_alerts is now declarable. It is the precondition for dependabot_security_updates, which was declared without it. - both endpoint-backed toggles are stripped from the PATCH body, which would otherwise 422 on unknown properties, and both are exported so the drift check compares symmetrically. - labels are paginated. Past 100 the missing ones read as permanent drift. - js-yaml is version-pinned and installed with --ignore-scripts, in the one job that can hold an administration token. - without SETTINGS_TOKEN the job now reports what it skipped and succeeds. A new repository should not greet its owner with a red run it has no way to fix. Sign-offs never reached main: squash_merge_commit_message was PR_BODY, and a pull request body carries no Signed-off-by trailer, so under squash-only merging every adopted repository had zero sign-offs in main while the README claimed otherwise. Now COMMIT_MESSAGES. Added dco.yml as an in-repo gate so the check exists without installing an app. Licensing: - LICENSE carried a hardcoded copyright line; now placeholders. - `reciprocal` dropped from the disallowed set: it covers MPL-2.0, EPL and CDDL, and would fail on the first hashicorp dependency. `permissive` stays out - upstream defines it as an empty set, so listing it only looked like policy. Release correctness: - gate on `releases_created`, not `release_created`. The singular form is only emitted for the root package, so it disappears the moment a non-root package is added and every downstream job is skipped on a green run. - exclude-paths: docs, and the inverse gotcha documented. - removed the monorepo example from the README; the release workflows assume a single root package. Workflow triggers: - pr-title moved from pull_request_target to pull_request. It reads the title from the event payload and never needed a write token. - the three workflows that genuinely need pull_request_target carry a loud do-not-check-out warning, and repo-lint parses the workflows and fails if any of them ever does. A comment is advice; this is a gate. - zizmor pinned from versions.env; the action defaults to `latest`, so a new rule release could red main without a commit. - zizmor SARIF upload and the whole Scorecard job now skip on private repositories instead of failing, and Scorecard no longer publishes a score before anyone has decided they want it cited. - the pin gate also rejects `go install ...@latest` inside run blocks, which a perfect `uses:` check cannot see. Deleted the contributor-recognition stack: CONTRIBUTORS.md carried one named person's avatar, .all-contributorsrc configured a bot nobody installed, and all-contributors.yml pushed commits to main for a feature no adopter asked for. Three mechanisms for one job, none of them working. Signed-off-by: Vadim Bauer <vb@container-registry.com>
…workflow self-explaining
Go pack, which until now was decoration: nothing compiled, vetted,
tested, linted or built anything the template shipped.
- .golangci.yaml, standard plus misspell and gofumpt. A starting point,
not a policy an adopter turns off wholesale on day one.
- test runs with CGO_ENABLED=1. Some runner images export 0, and
`go test -race` then quietly does not run the race detector.
- tidy-check fails when go.mod or go.sum is not tidy. It immediately
found that go.mod shipped without a trailing newline.
- an image task and a CI image job that builds both architectures,
loads the native one, runs it, and asserts the image is not root.
The assertion deliberately does not depend on what the demo prints.
- the Dockerfile builder is --platform=$BUILDPLATFORM, so a linux/arm64
build cross-compiles instead of running the whole Go toolchain under
QEMU.
Optional packs are inert, not delete-by-hand. Every Go and Docker job
detects its pack inside the job and skips with a note in the summary.
Deleting release-assets.yml used to leave a dangling local
`uses: ./.github/workflows/release-assets.yml` and take release
automation down with it. Detection is in-job on purpose: a job-level
`if: hashFiles('go.mod') != ''` always fails open, because hashFiles
resolves against GITHUB_WORKSPACE and the workspace does not exist when
a job-level condition is evaluated.
Every workflow now opens with What / Fires on / Needs / Safe to delete
if. Six months from now the question asked of a red workflow is whether
it can just be deleted, and nothing in the repository answered it.
Code of Conduct is Contributor Covenant 2.1 in full. The previous file
was a 34-line abridgement that dropped Scope, Enforcement
Responsibilities and the four-tier Enforcement Guidelines, and closed
with a note addressed to whoever was editing the template rather than
to the community. The identical truncated file is already in
dependencytrack-harbor-adapter.
One support surface. Discussions is off and the links to it are gone;
questions go through a question.yml issue form. A second inbox that a
two-person team does not clear reads worse than not offering one. The
opt-in recipe is in the settings.yml comment.
Signed-off-by: Vadim Bauer <vb@container-registry.com>
…output coverage.out was committed by the previous commit because .gitignore listed only bin, temp, .idea and .task. It now covers build output, test and lint artifacts, and the files that must never be committed. .gitattributes normalises line endings so a Windows checkout does not produce a whole-file diff, and marks CHANGELOG.md generated so it stops dominating diffs and language stats. Signed-off-by: Vadim Bauer <vb@container-registry.com>
Adoption was a macOS-only `sed -i ''` one-liner that skipped .json, .toml and Dockerfile, had no verification step, and left the reader to work out which of twelve workflows applied to them. Nothing ever exercised the template as a template, which is why a whole class of defects only appeared in the adopter's repository. task bootstrap (.github/scripts/bootstrap.py): - substitutes every placeholder across all file types, prompting or taking flags - removes whole marked blocks when an optional value is left blank. Dropping the line was not enough: it left the heading behind, and it left an issue-template contact link with no url, which GitHub rejects for the entire chooser - strips template-only prose so the README stops linking to CHECKLIST.md after bootstrap deletes it - --lang=none removes the Go pack and the release-please jobs that call the workflows it deleted - writes .github/template.yml recording which template commit the repository came from, so it is possible to ask which repositories are behind and which still carry a defect fixed upstream - deletes itself and CHECKLIST.md, then runs repo-lint, so a half-applied template fails loudly rather than quietly The rehearsal, which is the point: hygiene.yml now bootstraps a scratch copy of the current HEAD down both adoption paths and asserts the result is a clean repository. Every other gate reads files; this one walks the adopter's path, and it is the only thing that can catch an interaction between two individually correct pieces. It found three real defects while being written: the README link to a file bootstrap deletes, the orphaned optional headings, and the dangling workflow_call left by removing a pack. repo-lint grew four checks, all of them things a grep in a workflow got wrong by matching its own source: leftover bootstrap markers, local workflow_call targets that do not resolve, contact links missing a required field, and issue forms applying labels settings.yml does not declare. Each was negative-tested by introducing the defect and confirming the check fails. Docs: - README rewritten as a project README with the template instructions in a block bootstrap removes. It is no longer the only map of the automation, and no longer tells the reader to delete itself. - docs/repo-automation.md is that map: every workflow, config file, script and secret, what it needs, when it is safe to delete, and the four things that will bite you. - CHECKLIST.md is now only the decisions bootstrap cannot make, with the warning about required status checks that would otherwise deadlock every release. - CLAUDE.md, with AGENTS.md as a symlink rather than a second copy that drifts, plus a shared .claude/settings.json. Enabled by narrowing .gitignore. Signed-off-by: Vadim Bauer <vb@container-registry.com>
An earlier commit in this branch shipped a 2.6 MB Mach-O binary. A bare `go build ./...` writes one binary per main package into the working directory, named after the package directory, and `git add -A` then commits it. The template would have handed that to every adopter. - build now writes to bin/, which is ignored - .gitignore also ignores a root binary named after the repository, which is what go build produces by default - repo-lint fails on any tracked file whose leading bytes are ELF, Mach-O or PE. Negative-tested by committing one and confirming the check catches it. Signed-off-by: Vadim Bauer <vb@container-registry.com>
|
Warning Review limit reached
Next review available in: 21 minutes Limit details: You’ve used the included review currently available. You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository. How can I continue?Wait for the limit to reset, then comment An organization admin can change what happens after included review limits in Billing. How do review limits work?CodeRabbit enforces per-developer PR review limits within each organization. For paid Pro and Pro+ reviews, CodeRabbit uses a developer's included PR review attempts over the past 7 days to set the current hourly allowance. At typical activity levels, the full plan allowance applies. Higher sustained activity can lower the allowance until earlier attempts leave the 7-day window. Please refer docs for additional details. Review details⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (61)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Dependency Review✅ No vulnerabilities or license issues or OpenSSF Scorecard issues found.OpenSSF Scorecard
Scanned Files
|
|
You are seeing this message because GitHub Code Scanning has recently been set up for this repository, or this pull request contains the workflow file for the Code Scanning tool. What Enabling Code Scanning Means:
For more information about GitHub Code Scanning, check out the documentation. |
There was a problem hiding this comment.
Thanks for your first pull request! 🎉
A maintainer will review this soon. Please ensure:
- Tests pass locally
- Code follows our style guidelines
- Commits are signed off (DCO)
- PR title follows Conventional Commits
Check out our Contributing Guide for more details.
…alled CI failed Lint Go and License Check on the first run: golangci-lint and go-licenses are not on the runner and nothing installed them, while vuln-check passed because it already used go run with a pinned version. All three now use the same pattern, with the version coming from versions.env, so no tool has to be preinstalled and a local run uses exactly the same version as CI. go-licenses is pinned at v2.0.1, whose module path is /v2. Dropped the _require-golangci-lint guard: it existed only to produce a better message for a situation that can no longer happen. Signed-off-by: Vadim Bauer <vb@container-registry.com>
There was a problem hiding this comment.
All reported issues were addressed across 2 files (changes from recent commits).
Reply with feedback, questions, or to request a fix.
Re-trigger cubic
… and stop swallowing 403 on three more sections Three review threads, all valid. Release baseline (coderabbit, cubic P1, copilot). The repository's only release is tagged app-v1.1.0, which the new vX.Y.Z scheme does not match, so release-please would find no previous release, walk the whole history and replay 27 old commits into the first changelog. release-please-config.json now carries a one-time bootstrap-sha at the current main tip. docs/RELEASES.md explains what it is, says to delete it after the first release under the new scheme, and gives the tag-based alternative for anyone who would rather keep the 1.1.0 line. CHECKLIST tells adopters to remove the key, since it points at a commit that does not exist in their repository. Version impact (cubic P3). README claimed "Other types do not trigger a release by themselves", which contradicted docs/RELEASES.md and CHECKLIST. The README was the wrong one. Verified against release-please rather than argued: src/versioning-strategies/default.ts determineReleaseType returns PatchVersionUpdate() for any commit set with no breaking change and no feat, and src/strategies/base.ts skips only when conventionalCommits.length === 0, with postProcessCommits an identity function in the base strategy. Nothing filters on commit type. So every conventional commit not excluded by path produces at least a patch release, and `hidden: true` only removes a section from the changelog. All three tables now say the same thing, and the README says to use exclude-paths if a change really should not release. Drift false positives (cubic P2). The previous commit taught the exporter that 403 is not the same as "not configured", but only for actions and code_scanning. The rulesets, branches and environments reads still turned any failure into an omitted section, so a token without administration access reported permanent drift on exactly the sections this was meant to protect. All five now go through markIfForbidden. Signed-off-by: Vadim Bauer <vb@container-registry.com>
…sion file copilot raised this on both README.md and docs/RELEASES.md and it is correct. Verified against release-please source: - src/strategies/simple.ts: versionFile defaults to 'version.txt' and the updater is always registered. - src/strategies/go.ts: versionFile defaults to '' and the updater is registered only `if (this.versionFile)`. Go modules are versioned by tags, so the strategy maintains the changelog and the tag and nothing else. Listing `go` next to node/python/rust/helm as "switch to update your native version file" therefore leads an adopter to switch, lose version.txt with nothing replacing it, and not notice. It also breaks the release-assets task, which stamps the binary from version.txt. All three documents now say to stay on `simple` or set version-file explicitly. Signed-off-by: Vadim Bauer <vb@container-registry.com>
Brings in the PR #5 review fixes: the bootstrap-sha migration boundary, markIfForbidden extended to rulesets/branches/environments, the corrected version-impact claim, and the go release-type caveat. README.md and CHECKLIST.md are taken from this branch, which rewrote both. Neither carries the claims the base fixed; both now defer to docs/RELEASES.md, where the corrections landed. task bootstrap now strips bootstrap-sha. It is a migration boundary for the template's own history and points at a commit that does not exist in an adopter's repository, so leaving it as a checklist item nobody reads was the wrong place for it. Signed-off-by: Vadim Bauer <vb@container-registry.com>
There was a problem hiding this comment.
All reported issues were addressed
Reply with feedback, questions, or to request a fix.
Re-trigger cubic
… I committed
cubic was right on all four points in its re-review.
Release triggers. My previous commit claimed hidden changelog sections
affect only the changelog and that every conventional commit produces at
least a patch release. That is wrong, and cubic was right that hidden
types do not release. The mechanism is in release-please
src/strategies/base.ts:
if (!bumpOnlyOptions && this.changelogEmpty(releaseNotesBody)) {
... - skipping
return undefined;
}
changelogEmpty is true when the generated entry is a single line, which
is what happens when every commit landed in a `hidden: true` section. So
the release is skipped outright rather than the bump being suppressed.
The complete rule, which neither the previous wording nor cubic's stated
it fully:
- only hidden types since the last release -> no release at all
- any visible commit -> a release is cut, minor for feat, major for a
breaking change, patch for everything else
- so one `docs:` alongside five `chore:` commits is a patch release, and
the changelog shows only the `docs:` line
That last case is why "docs/perf/refactor/revert do not trigger a
release" is also wrong: this repository configures them as visible
sections, unlike release-please's defaults. All three tables now say the
same thing and name the mechanism rather than the commit type.
coverage.out. Committed by my own `git add -A` in dbe6698: the file was
left in the working tree by a task run on another branch, and this
branch's .gitignore predates the entry that would have excluded it. It
is stale, nothing consumes it, and no test exists here to regenerate it.
Removed, and .gitignore now covers coverage and lint output.
Also moved the bootstrap-sha checklist item under
`release-please-config.json`, the file it actually edits, and split the
monorepo sentence back out of the `go` caveat paragraph, where an edit
had glued it onto a 270-character line.
Signed-off-by: Vadim Bauer <vb@container-registry.com>
Brings in the corrected release-trigger rule: hidden changelog sections make release-please skip the release outright via changelogEmpty, rather than the bump being suppressed. docs/RELEASES.md carries it; README.md and CHECKLIST.md are taken from this branch, which rewrote both and defers the release semantics to that document. .gitignore taken from this branch, which is a superset and matches the licenses.csv this branch's Taskfile actually writes. Signed-off-by: Vadim Bauer <vb@container-registry.com>
zizmor, via code scanning: - dependabot: cooldown default-days 7 on all three ecosystems, so a version published minutes ago is not proposed automatically - apply-settings: `npm ci` from a committed package-lock.json instead of an ad-hoc install, in the one job that can hold an administration token - release-assets: `gh release upload` instead of a third-party action; gh is already on the runner and the job holds contents: write Settings drift, all of which made verify and check fail permanently: - exportSettings still returned visibility and is_template after both were deliberately removed from settings.yml, so expected and actual could never match - description is null on a repo without one but "" in settings.yml, and normalize drops null while keeping "" - labels were exported wholesale, so every label the repository has but settings.yml does not declare read as drift. release-please alone adds autorelease: pending and autorelease: tagged. Only declared labels are compared now, which is what settings.yml actually claims to manage - applySecurity ran the PATCH enabling dependabot_security_updates before the PUT enabling vulnerability_alerts, its precondition, so the request was rejected and the whole section threw. The comment already said alerts go first; now the code does Bootstrap: - marker comments were only removed when the optional value was blank, so supplying a DOCS_URL left `<!-- if:DOCS_URL -->` behind and repo-lint rejected the adopted repo. The markers are now always removed and only the content is conditional. The rehearsal covered just the blank path, which is why this shipped: it now runs all four combinations of --lang and filled/blank optional values - --module-path and --homepage-url were collected and never applied. Both are written explicitly rather than through a placeholder: a module path with braces is not a valid module path, and settings.yml is applied to the live repository, so a placeholder there would be written to the template's own homepage - values containing a quote or backslash are rejected rather than silently producing an invalid Dockerfile LABEL - --lang=none prunes the rows in docs/repo-automation.md that document the files it just deleted Workflows: - pr-title moves back to pull_request_target. With pull_request, a pull request that edits the workflow runs its own edited copy and can replace the check that gates it. The trigger is safe here only because nothing checks out the head, which repo-lint now enforces - the cosign identity in the generated verify command pointed at publish-image.yml@refs/tags/<tag>. Fulcio records the reusable workflow at the ref it was resolved at, which for a local call is the caller's ref, so every generated command failed. Now an anchored identity regexp - document-artifacts needs publish-release-assets: it was publishing instructions to verify files that job had not uploaded yet - license-report ran under bare always(), so the no-Go path invoked task without having installed it - codeql concurrency now includes the event name; a push could cancel the scheduled run that exists to catch new queries on old code - the SBOM described the checkout rather than the artifacts, and was not covered by checksums.txt. It now scans dist, checksums run after it, and both the binaries and the SBOM are attested - release-checksums wrote through a redirect that created the file before the glob expanded, so checksums.txt contained a checksum of its own empty self - the Task download verified against a checksum file fetched from the same release, which only proves the release is self-consistent. The digests are committed in versions.env, as they already were for typos and actionlint - SETTINGS_TOKEN needs Issues read/write; labels go through the Issues API and the documented scope omitted it - dco accepted a bare "Signed-off-by:" line; it now requires a name and an address - PyYAML is installed rather than assumed to be on the runner repo-lint: - the pull_request_target guard only understood the mapping trigger form, so `on: pull_request_target` skipped the check entirely - composite actions may be action.yaml - the post-bootstrap scan now covers the same files bootstrap rewrites, including Dockerfile, LICENSE, CODEOWNERS, go.mod and .gitignore - new check: the Dockerfile base-image ARG defaults must match versions.env, which were two independent copies of the same pin Smaller: task check no longer claims CI parity it does not have and now runs build and the pin check; the README quick start points --template at the template rather than at the repository being created; MAINTAINERS explains that CODEOWNERS routes to a team, so adding a row alone changes nothing; .editorconfig covers Python; AGENTS.md is a pointer file rather than a symlink, which does not survive a default Windows clone; main.go checks the write error in both branches; the codeql pins carry their exact version; and docs/RELEASES.md says which paths exclude-paths actually covers. Signed-off-by: Vadim Bauer <vb@container-registry.com>
|
Bot review addressed in
One partial: Two of the valid findings are worth calling out because they were only reachable by reading interactions, not files: the settings exporter still returned |
coderabbit is right that a bare 'None' is imprecise. A push of only hidden types cuts no release, but in a mixed push the release happens and those commits ride along inside it, so the row overstated the case. The prose below each table already described the mixed case; the row now agrees with it. Signed-off-by: Vadim Bauer <vb@container-registry.com>
Wording fix for hidden commit types. README.md and CHECKLIST.md taken from this branch, which rewrote both and does not carry a commit-type table; docs/RELEASES.md is where the rule lives. Signed-off-by: Vadim Bauer <vb@container-registry.com>
Both failures on the last run were mine. Dependency Review failed correctly. The lockfile I added to satisfy zizmor's ad-hoc-install finding pinned js-yaml 4.1.0, which carries two high-severity advisories (GHSA-52cp-r559-cp3m and GHSA-5p4m-2wfm-xmqj, quadratic CPU on merge keys and on !!omap). Rather than pin a vulnerable parser, the dependency is gone: the workflow converts settings.yml to JSON with python3 before the script runs, and apply-settings.js parses JSON. That removes the advisory, the lockfile, and the original zizmor finding together, and keeps every third-party dependency out of the one job that can hold an administration token. PyYAML ships on GitHub's Ubuntu runners - the repo-lint job proved that by passing before it had an install step - so the pip install is a fallback rather than the normal path. Bootstrap Rehearsal failed on `home: unbound variable`. The previous commit added the four rehearse invocations and the "$home"/"$docs" reference but not the parameter block that defines them, because that half of the edit failed to apply and I only noticed the half that did. Restored, and all four combinations pass locally. Signed-off-by: Vadim Bauer <vb@container-registry.com>
There was a problem hiding this comment.
All reported issues were addressed across 7 files (changes from recent commits).
Tip: Review your code locally with the cubic CLI to iterate faster.
Re-trigger cubic
PR #5 was squash-merged, so its content arrived on main as one new commit while this branch carries the original ones. Every conflict is that duplication: main is exactly the PR #5 tip plus one bot commit, and that tip is already an ancestor here, so this branch is the superset in each conflicting file. Verified before resolving, not assumed. Two resolutions are decisions rather than mechanics: CONTRIBUTORS.md stays deleted. main gained it again from `docs: update contributors`, which is the all-contributors workflow pushing to the default branch. This branch removes that whole stack - CONTRIBUTORS.md, .all-contributorsrc and all-contributors.yml - because it was three overlapping mechanisms for one job, none of them working, one of which writes to main. That bot commit is the behaviour being removed. .github/scripts/apply-settings.js keeps the label filter from this branch. main still exports every label the repository has, so any label settings.yml does not declare reads as drift - including the autorelease: pending and autorelease: tagged labels release-please creates on its own release pull request. bootstrap-sha stays at 9d06252, the last commit before the new release scheme. It is still an ancestor of main and still the right boundary. Signed-off-by: Vadim Bauer <vb@container-registry.com>
cubic is right that an unpinned `pip install` in the job that can hold an administration token is a supply-chain hole: it executes third-party package code in the workspace before the github-script step runs, and that code can rewrite apply-settings.js, which the next step loads with SETTINGS_TOKEN. The fallback also bought nothing. PyYAML is present on GitHub's Ubuntu runners - the Repository Consistency job logs `Requirement already satisfied: pyyaml ... (6.0.1)`. A runner without it now fails with a message saying to fix the image rather than fetching a dependency into a privileged job. The install stays in the hygiene job, which holds no token and whose whole purpose is running repo-lint, so a portable install there is worth the far smaller risk. Also corrected the rehearse() comment: $1 is the --lang value, not the flag. Signed-off-by: Vadim Bauer <vb@container-registry.com>
|
P1 on P2 on the same line — I disagree. The premise is that pip 24 on pip falls back to a user install rather than refusing, and PyYAML is already there. The same is true of the This matters beyond the removed line: P3 — adopted, comment corrected. |
There was a problem hiding this comment.
All reported issues were addressed across 2 files (changes from recent commits).
Tip: Review your code locally with the cubic CLI to iterate faster.
Re-trigger cubic
…lback cubic is right that the comment above the step still described a fallback the previous commit deleted, so an operator would have expected the job to install PyYAML on a runner that lacks it. The two comments also duplicated each other; they are now one, saying PyYAML is required rather than fetched and why. Signed-off-by: Vadim Bauer <vb@container-registry.com>
Stacked on #5. Merge that first; this branch already contains its commit plus the fixes for its open review threads.
What this closes
A gap analysis found the template was roughly 60% of a complete reference: it shipped files that were factually broken, documented files that did not exist, had zero CI validating itself, and was missing the entire security and supply-chain layer that
harbor-scanner-trivyanddependencytrack-harbor-adapteralready run in production.An expert panel (six discipline reviews, adversarially judged for validity and for whether a consumer would keep or delete each item) then found things the gap analysis missed. Two were severe and both were verified against this repository rather than taken on trust.
The two severe ones
settings.ymlpublished private repositories. It is PATCHed onto the repo byapply-settingson the first push to main, and it carriedvisibility: publicandis_template: true. A repository created private from this template published itself, irreversibly. The fork guard that should have limited stray runs comparedgithub.repository_ownertogithub.event.repository.owner.login— the same value on both sides, so always true.The branch ruleset deadlocked every release. GitHub raises no workflow events for a ref pushed with
GITHUB_TOKEN. Verified here: PR #4 was opened by release-please and the only run on that branch wasPR Title, triggered later by an unrelated bot editing the PR. A required status check would never report on a release PR, and it could never be merged. The ruleset now requires a PR and no status checks, with the prerequisites documented.What is new
The template validates itself.
hygiene.yml(typos, actionlint, yamllint, zizmor, action-pin gate, dependency review) andci.yml(build, race tests, lint, tidy, licence, govulncheck, container smoke test)..github/scripts/repo-lint.pyadds 11 consistency checks — invalid YAML that tokenizes but does not construct, labels emitted but not declared, release-please packages that are not directories, docs linking to files that do not exist,pull_request_targetworkflows that check out code, committed binaries. Every check exists because that mistake actually shipped here; each was negative-tested.Adoption is rehearsed, not hoped for.
task bootstrapreplaces the macOS-onlysedone-liner.hygiene.ymlbootstraps a scratch copy of HEAD down both adoption paths on every PR and asserts the result is clean. It found three real defects while being written.Security on by default.
SECURITY.mdwith a real CVD policy (no repo in the org had one), secret scanning with push protection, private vulnerability reporting, Dependabot, CodeQL, a branch ruleset, a DCO gate, every action SHA-pinned behind a gate.Verifiable releases. cosign signatures, syft SPDX SBOMs for both binaries and images, build provenance, checksums, and copy-pasteable verify commands appended to the release body. This is the CRA Art. 24 evidence set.
Also fixed: sign-offs never reached
main(squash_merge_commit_message: PR_BODYcarries no trailer);release_createdis path-scoped and silently skips publish jobs;--disallowed_types=...,permissivewas dead config whilereciprocalwould fail on the first MPL-2.0 dependency;LICENSEshipped a hardcoded copyright line; the contributor-recognition stack was three broken mechanisms for one job, one of which pushed tomain.Verification
All gates pass on the template and on the bootstrapped output of both adoption paths:
Needs a human decision
.github/settings.ymlis applied to the live repository. Review it before merge.version.txtand the manifest are reset to0.0.0, and the legacy tag isapp-v1.1.0. See the version-baseline section indocs/RELEASES.mdbefore the first release under the new scheme.SETTINGS_TOKENsecret is needed beforeapply-settingscan do anything beyond labels.