feat(releases): publish immutable standards bundles #8
No files matched your search
@@ -4,6 +4,7 @@
|
||||
name: ci
|
||||
|
||||
on:
|
||||
workflow_call:
|
||||
pull_request:
|
||||
push:
|
||||
branches: [main]
|
||||
@@ -92,7 +93,43 @@ jobs:
|
||||
shell: pwsh
|
||||
run: ./tools/Test-RepoConformance.ps1 -SelfTest
|
||||
|
||||
- name: Standards package tests
|
||||
shell: pwsh
|
||||
run: ./tools/Test-StandardsPackage.ps1
|
||||
|
||||
# Static mode reads only the checkout: no API calls, no admin scope needed.
|
||||
- name: Static conformance of this repository
|
||||
shell: pwsh
|
||||
run: ./tools/Test-RepoConformance.ps1 -LocalPath . -StaticOnly
|
||||
|
||||
release-please:
|
||||
name: release-please
|
||||
needs: [security, codeql, docs, conformance]
|
||||
if: github.event_name == 'push' && github.ref == 'refs/heads/main' && vars.RELEASE_APP_CLIENT_ID != ''
|
||||
runs-on: ubuntu-24.04
|
||||
timeout-minutes: 10
|
||||
# EX-0006: a main-only bot key, never a repository secret.
|
||||
environment: release-automation
|
||||
concurrency:
|
||||
group: standards-release-pr
|
||||
cancel-in-progress: false
|
||||
permissions: {}
|
||||
steps:
|
||||
# An App token lets the generated PR trigger CI; GITHUB_TOKEN does not.
|
||||
- name: Release PR token
|
||||
id: app
|
||||
uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 # v3.2.0
|
||||
with:
|
||||
client-id: ${{ vars.RELEASE_APP_CLIENT_ID }}
|
||||
private-key: ${{ secrets.RELEASE_APP_PRIVATE_KEY }}
|
||||
permission-contents: write
|
||||
permission-pull-requests: write
|
||||
|
||||
- name: Propose a semantic release
|
||||
uses: googleapis/release-please-action@45996ed1f6d02564a971a2fa1b5860e934307cf7 # v5.0.0
|
||||
with:
|
||||
token: ${{ steps.app.outputs.token }}
|
||||
target-branch: main
|
||||
config-file: release-please-config.json
|
||||
manifest-file: .release-please-manifest.json
|
||||
skip-github-release: true
|
||||
@@ -0,0 +1,175 @@
|
||||
name: release-standards
|
||||
|
||||
on:
|
||||
workflow_call:
|
||||
|
||||
permissions: {}
|
||||
|
||||
jobs:
|
||||
validate:
|
||||
name: validate
|
||||
runs-on: ubuntu-24.04
|
||||
timeout-minutes: 10
|
||||
permissions:
|
||||
contents: read
|
||||
issues: read
|
||||
pull-requests: read
|
||||
outputs:
|
||||
version: ${{ steps.tag.outputs.version }}
|
||||
checklist: ${{ steps.tag.outputs.checklist }}
|
||||
pr: ${{ steps.tag.outputs.pr }}
|
||||
steps:
|
||||
- name: Checkout tagged source
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
with:
|
||||
fetch-depth: 0
|
||||
persist-credentials: false
|
||||
|
||||
- name: Validate version, main ancestry, and checklist
|
||||
id: tag
|
||||
env:
|
||||
TAG: ${{ github.ref_name }}
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
GH_REPO: ${{ github.repository }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
[[ "$GITHUB_REF" == refs/tags/v* ]]
|
||||
[[ "$TAG" =~ ^v(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)$ ]] || {
|
||||
echo '::error::Standards bundles require a stable vMAJOR.MINOR.PATCH tag.'
|
||||
exit 1
|
||||
}
|
||||
git merge-base --is-ancestor HEAD origin/main
|
||||
version="${TAG#v}"
|
||||
[[ "$(cat version.txt)" == "$version" ]]
|
||||
[[ "$(jq -r '."."' .release-please-manifest.json)" == "$version" ]]
|
||||
echo "version=$version" >> "$GITHUB_OUTPUT"
|
||||
commit="$(git rev-parse HEAD)"
|
||||
pr="$(gh api "repos/$GH_REPO/commits/$commit/pulls" | jq -r --arg sha "$commit" '.[] | select(.merge_commit_sha == $sha and .merged_at != null and .base.ref == "main" and any(.labels[]; .name == "autorelease: pending")) | .number')"
|
||||
[[ "$pr" =~ ^[1-9][0-9]*$ ]] || {
|
||||
echo '::error::Tag the merge commit of the pending release PR.'
|
||||
exit 1
|
||||
}
|
||||
echo "pr=$pr" >> "$GITHUB_OUTPUT"
|
||||
# Preflight is approved before tagging; shipping checks follow publication.
|
||||
checklist=''
|
||||
if [[ "$version" == *.0 ]]; then
|
||||
annotation="$(git for-each-ref --format='%(contents)' "refs/tags/$TAG")"
|
||||
prefix="https://github.com/$GH_REPO/issues/"
|
||||
checklist="$(printf '%s\n' "$annotation" | grep -F 'Release checklist: ' | head -n 1 | sed 's/^Release checklist: //')"
|
||||
[[ "$checklist" == "$prefix"* ]]
|
||||
issue="${checklist#"$prefix"}"
|
||||
[[ "$issue" =~ ^[1-9][0-9]*$ ]]
|
||||
gh api "repos/$GH_REPO/issues/$issue" | jq -e \
|
||||
'.pull_request == null and any(.labels[]; .name == "release: ready")' >/dev/null
|
||||
fi
|
||||
echo "checklist=$checklist" >> "$GITHUB_OUTPUT"
|
||||
|
||||
gates:
|
||||
name: gates
|
||||
needs: validate
|
||||
uses: ./.github/workflows/ci.yml
|
||||
permissions:
|
||||
contents: read
|
||||
security-events: write
|
||||
|
||||
build:
|
||||
name: build
|
||||
needs: [validate, gates]
|
||||
runs-on: ubuntu-24.04
|
||||
timeout-minutes: 15
|
||||
permissions:
|
||||
contents: read
|
||||
id-token: write
|
||||
attestations: write
|
||||
steps:
|
||||
- name: Checkout tagged source
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Build committed standards bundle
|
||||
shell: pwsh
|
||||
env:
|
||||
K22_VERSION: ${{ needs.validate.outputs.version }}
|
||||
RELEASE_CHECKLIST: ${{ needs.validate.outputs.checklist }}
|
||||
run: ./tools/Build-StandardsPackage.ps1 -Version $env:K22_VERSION -ChecklistUrl $env:RELEASE_CHECKLIST
|
||||
|
||||
- name: Attest every release asset
|
||||
uses: actions/attest-build-provenance@4d101475d8b20a2381f78447822ac1eab6504dd8 # v4.2.2
|
||||
with:
|
||||
subject-path: artifacts/standards/*
|
||||
|
||||
- name: Upload release assets
|
||||
uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
|
||||
with:
|
||||
name: standards-release-assets
|
||||
path: artifacts/standards/*
|
||||
if-no-files-found: error
|
||||
|
||||
publish:
|
||||
name: publish
|
||||
needs: [validate, build]
|
||||
runs-on: ubuntu-24.04
|
||||
timeout-minutes: 15
|
||||
environment: release
|
||||
permissions:
|
||||
contents: write
|
||||
pull-requests: write
|
||||
steps:
|
||||
- name: Download release assets
|
||||
uses: actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333 # v8.0.2
|
||||
with:
|
||||
name: standards-release-assets
|
||||
path: release
|
||||
|
||||
- name: Settings read token
|
||||
id: app
|
||||
uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 # v3.2.0
|
||||
with:
|
||||
client-id: ${{ vars.RELEASE_APP_CLIENT_ID }}
|
||||
private-key: ${{ secrets.RELEASE_APP_PRIVATE_KEY }}
|
||||
permission-administration: read
|
||||
|
||||
- name: Require immutable releases
|
||||
env:
|
||||
GH_TOKEN: ${{ steps.app.outputs.token }}
|
||||
GH_REPO: ${{ github.repository }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
[[ "$(gh api "repos/$GH_REPO/immutable-releases" --jq '.enabled')" == true ]]
|
||||
|
||||
- name: Create draft with all assets
|
||||
working-directory: release
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
GH_REPO: ${{ github.repository }}
|
||||
TAG: ${{ github.ref_name }}
|
||||
K22_VERSION: ${{ needs.validate.outputs.version }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
sha256sum --check SHA256SUMS
|
||||
# Creation fails if this tag already has a release. Never replace assets.
|
||||
gh release create "$TAG" --draft --verify-tag \
|
||||
--title "KofTwentyTwo standards $K22_VERSION" \
|
||||
--notes-file "KofTwentyTwo-standards-$K22_VERSION.notes.md" ./*
|
||||
|
||||
- name: Publish immutable release
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
GH_REPO: ${{ github.repository }}
|
||||
TAG: ${{ github.ref_name }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
gh release edit "$TAG" --draft=false
|
||||
[[ "$(gh api "repos/$GH_REPO/releases/tags/$TAG" --jq '.immutable')" == true ]]
|
||||
|
||||
# Release Please will not propose another version while a merged PR is pending.
|
||||
- name: Mark the release PR as published
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
GH_REPO: ${{ github.repository }}
|
||||
PR_NUMBER: ${{ needs.validate.outputs.pr }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
gh label create 'autorelease: tagged' --color 0e8a16 --force
|
||||
gh pr edit "$PR_NUMBER" --remove-label 'autorelease: pending' --add-label 'autorelease: tagged'
|
||||
@@ -0,0 +1,24 @@
|
||||
name: standards-release
|
||||
|
||||
on:
|
||||
push:
|
||||
tags: ['v*']
|
||||
workflow_dispatch:
|
||||
|
||||
permissions: {}
|
||||
|
||||
concurrency:
|
||||
group: standards-release-${{ github.ref }}
|
||||
cancel-in-progress: false
|
||||
|
||||
jobs:
|
||||
release:
|
||||
if: startsWith(github.ref, 'refs/tags/v')
|
||||
uses: ./.github/workflows/release-standards.yml
|
||||
permissions:
|
||||
contents: write
|
||||
issues: read
|
||||
pull-requests: write
|
||||
security-events: write
|
||||
id-token: write
|
||||
attestations: write
|
||||
@@ -0,0 +1 @@
|
||||
{}
|
||||
@@ -0,0 +1,65 @@
|
||||
# Repository Guidelines
|
||||
|
||||
## Project Structure & Module Organization
|
||||
|
||||
This repository publishes KofTwentyTwo engineering standards and enforcement tooling.
|
||||
`policies/` defines requirements; `standards/` provides implementation and language
|
||||
profiles; `architecture/` contains reference designs. `configs/` holds IDE, formatter,
|
||||
linter, and scanner settings. `templates/` contains starter repository files and
|
||||
workflow callers; `.github/workflows/` contains reusable workflows and repository CI.
|
||||
PowerShell tooling lives in `tools/`, workstation setup in `setup/`, framework
|
||||
crosswalks in `compliance/`, and approved deviations in `exceptions/`.
|
||||
|
||||
## Build, Test, and Development Commands
|
||||
|
||||
There is no application build or local server. Run these checks from the repository
|
||||
root with the corresponding tools installed:
|
||||
|
||||
```powershell
|
||||
npx markdownlint-cli2 "**/*.md"
|
||||
lychee --offline --include-fragments --no-progress './**/*.md'
|
||||
pwsh tools/Test-RepoConformance.ps1 -SelfTest
|
||||
pwsh tools/Test-RepoConformance.ps1 -LocalPath . -StaticOnly
|
||||
zizmor .github/workflows
|
||||
actionlint
|
||||
gitleaks detect --source . --no-banner
|
||||
```
|
||||
|
||||
These check Markdown, internal links and anchors, checker behavior, local conformance,
|
||||
workflow security, workflow syntax, and secrets respectively. For GitHub settings,
|
||||
run the checker with `-Repository KofTwentyTwo/standards -LocalPath .`; this requires
|
||||
authenticated GitHub access and sufficient permissions.
|
||||
|
||||
## Coding Style & Naming Conventions
|
||||
|
||||
Follow `.editorconfig`: UTF-8, LF, final newline, three-space indentation by default,
|
||||
and two spaces for YAML. Follow language profiles in `standards/coding/` and their
|
||||
matching `configs/`; PowerShell uses approved verbs and Allman braces. Markdown
|
||||
follows `.markdownlint-cli2.yaml`, which disables line-length limits. Use descriptive
|
||||
kebab-case document names, such as `security-program.md`.
|
||||
|
||||
Write requirements as `K22-<AREA>-<NN>` with MUST, SHOULD, or MAY, followed by
|
||||
*Why* and *Verified by*. Never reuse or renumber IDs. Extend enforcement for new MUSTs
|
||||
and update applicable compliance crosswalks in the same change.
|
||||
|
||||
## Testing Guidelines
|
||||
|
||||
Checker regression tests are built into `-SelfTest`; there is no separate test
|
||||
directory or repository-wide coverage gate. Extend those tests when changing checker
|
||||
behavior. Validate documentation with Markdown and internal-link checks; validate
|
||||
workflow changes with actionlint and zizmor. All required CI checks must pass.
|
||||
|
||||
## Commit & Pull Request Guidelines
|
||||
|
||||
History uses Conventional Commits, such as `feat(ci): ...` and `fix(docs): ...`.
|
||||
Use topic branches like `docs/clarify-dco`; target `main`. Sign commits and add DCO
|
||||
sign-off with `git commit -s`. PR titles must also be Conventional Commits. Describe
|
||||
the change and validation, link relevant issues, and resolve review threads before
|
||||
squash merging. Discuss requirement changes in an issue first; label incompatible
|
||||
new MUSTs `breaking`.
|
||||
|
||||
## Security & Agent Instructions
|
||||
|
||||
Keep content original; never copy employer or client documents. Pin external Actions
|
||||
to full commit SHAs with version comments and images to digests. Follow `SECURITY.md`
|
||||
for private reports and `policies/ai-assisted-development.md` for agent permissions.
|
||||
@@ -0,0 +1,3 @@
|
||||
# Changelog
|
||||
|
||||
Release Please maintains this file from Conventional Commits.
|
||||
@@ -35,6 +35,22 @@ themselves: everything below comes from the [SDLC policy](policies/sdlc.md).
|
||||
|
||||
## Running the checks locally
|
||||
|
||||
Documentation and package checks:
|
||||
|
||||
```powershell
|
||||
npx markdownlint-cli2 "**/*.md"
|
||||
lychee --offline --include-fragments --no-progress './**/*.md'
|
||||
pwsh tools/Test-RepoConformance.ps1 -SelfTest
|
||||
pwsh tools/Test-StandardsPackage.ps1
|
||||
pwsh tools/Test-RepoConformance.ps1 -LocalPath . -StaticOnly
|
||||
```
|
||||
|
||||
Package tests use an isolated temporary Git fixture; they do not commit changes in
|
||||
this repository. For version proposals and release bundles, see
|
||||
[bundle releases](docs/releases.md).
|
||||
|
||||
Workflow and security checks:
|
||||
|
||||
```powershell
|
||||
zizmor .github/workflows
|
||||
actionlint
|
||||
|
||||
@@ -11,7 +11,7 @@ reusable GitHub Actions workflows, repository templates, and a conformance check
|
||||
|
||||
## Status
|
||||
|
||||
> **Draft, before the first release (`v1.0.0`).** The requirements are written and the
|
||||
> **Draft standards, before `v1.0.0`.** The requirements are written and the
|
||||
> enforcement tooling exists, but no KofTwentyTwo repository has been verified against
|
||||
> them yet; adoption starts with this repository, then
|
||||
> [AppKit](https://github.com/KofTwentyTwo/AppKit) and
|
||||
@@ -303,6 +303,12 @@ the shared release workflow ([release standard](standards/releases.md)).
|
||||
|
||||
## Staying current
|
||||
|
||||
Download a versioned ZIP or tar.gz standards bundle from
|
||||
[GitHub Releases](https://github.com/KofTwentyTwo/standards/releases). Bundles preserve
|
||||
the repository layout, include checksums and provenance attestations, and become
|
||||
immutable on publication. See [bundle releases](docs/releases.md) for semantic version
|
||||
proposals, maintainer setup, publishing, verification, and local reproduction.
|
||||
|
||||
- This repository is versioned with [Semantic Versioning](https://semver.org). A new
|
||||
**MUST** that existing repositories do not meet is a MAJOR release; read its release
|
||||
notes before updating.
|
||||
|
||||
+1
-1
@@ -17,7 +17,7 @@ conformance check that passes when it should fail.
|
||||
| Fix released | Critical 7 days, High 30 days, Medium 90 days, Low next release, from confirmation |
|
||||
| Public disclosure | coordinated with the reporter; by default 90 days after the report, sooner once fixed |
|
||||
|
||||
Until this repository's first release (`v1.0.0`) these timeframes are targets the
|
||||
Before `v1.0.0` these timeframes are targets the
|
||||
maintainer works to; from `v1.0.0` they are commitments.
|
||||
|
||||
Fixed vulnerabilities are published as GitHub Security Advisories with a CVE. These
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
# ADR-0001: Release standards as immutable GitHub bundles
|
||||
|
||||
- **Status:** Accepted with the reviewed implementation PR
|
||||
- **Date:** 2026-10-10
|
||||
- **Decider:** Repository maintainer
|
||||
|
||||
## Context
|
||||
|
||||
Consumers need a versioned package of the policies, standards, configs, templates,
|
||||
and tooling. Content spans several languages and two primary licenses; it has no
|
||||
application runtime or build. Existing policies require Conventional Commits,
|
||||
maintainer-created protected tags, gated reusable release workflows, checksums,
|
||||
attestations, and immutable GitHub Releases.
|
||||
|
||||
## Options considered
|
||||
|
||||
- **GitHub ZIP/tar.gz bundle:** preserves the full repository layout and works for
|
||||
every consumer with no package-manager dependency. Extraction and copying configs
|
||||
remain explicit consumer actions.
|
||||
- **npm or NuGet package:** convenient for one ecosystem, but imposes that ecosystem
|
||||
on documentation and polyglot tooling. Adds a registry and publishing policy.
|
||||
- **Automatic tags on every merge:** fewer maintainer steps, but conflicts with the
|
||||
current tag-creation ruleset and release-checklist process.
|
||||
|
||||
## Decision
|
||||
|
||||
Use Release Please for version/changelog PRs and publish ZIP/tar.gz bundles through
|
||||
the reusable `release-standards.yml` workflow when the maintainer tags the merged
|
||||
release PR. Build from the Git commit, attest all assets, upload everything to a
|
||||
draft, and publish only with repository immutability enabled.
|
||||
|
||||
## Consequences
|
||||
|
||||
- Consumers can pin, verify, and extract a standards snapshot across platforms.
|
||||
- Hidden configuration and license notices remain part of the package.
|
||||
- Version proposals pass normal review; maintainers retain tag creation and release
|
||||
approval. No ruleset bypass or registry publishing secret is added.
|
||||
- A scoped GitHub App is needed for release PR CI and the settings-read check. Its
|
||||
key is held in environment secrets; installation tokens are scoped per job and
|
||||
revoked. Main-only access for the version bot is covered by
|
||||
[EX-0006](../../exceptions/register.md#ex-0006); publishing remains tag-only.
|
||||
- Packaging excludes local edits and untracked files. Rebuilds require the same
|
||||
source commit, version, checklist URL, and compatible Git tooling.
|
||||
- Complete App/environment setup and the first-release checklist before activating
|
||||
the pipeline. This ADR is reviewed with the implementation PR.
|
||||
- Review the [distribution threat model](../security/threat-model.md), including the
|
||||
version-bot credential boundary and its scoped exception, in the same PR.
|
||||
@@ -0,0 +1,128 @@
|
||||
# Standards bundle releases
|
||||
|
||||
The official distribution channel is
|
||||
[GitHub Releases](https://github.com/KofTwentyTwo/standards/releases). Each stable
|
||||
`vMAJOR.MINOR.PATCH` release contains ZIP and tar.gz archives of the **committed
|
||||
repository**, with the directory layout intact, including hidden configurations,
|
||||
licenses, workflows, templates, and tooling. Git metadata, untracked files, and
|
||||
uncommitted edits are excluded. Extract either archive to use the standards offline
|
||||
or copy its configs and templates into another repository.
|
||||
|
||||
## Semantic versions
|
||||
|
||||
[Release Please](https://github.com/googleapis/release-please) opens and updates a
|
||||
release PR after successful `main` CI, changing `version.txt`, `CHANGELOG.md`, and
|
||||
`.release-please-manifest.json`. The first proposal is `0.1.0`; `0.0.0` is the
|
||||
unreleased bootstrap placeholder. The tag is the published version's authority; the files are checked
|
||||
against it before packaging.
|
||||
|
||||
| Commit or squash PR title | Bump | Example |
|
||||
| --- | --- | --- |
|
||||
| `fix:`, `docs:`, `security:` | PATCH | `docs(testing): clarify coverage calculation` |
|
||||
| `feat:` | MINOR | `feat(coding): add a language profile` |
|
||||
| Any type with `!` or `BREAKING CHANGE:` | MAJOR | `feat(repository)!: require a new control` |
|
||||
|
||||
Use `feat` for new guidance and `!` for new obligations or removed allowances as
|
||||
defined in [the versioning policy](../policies/README.md#versioning-of-the-standards).
|
||||
Infrastructure changes appear in release notes and can produce PATCH releases;
|
||||
`test`, `style`, and `chore` alone do not produce release notes. Review the proposed
|
||||
version and notes before merging. A `breaking` label alone does not change the version.
|
||||
|
||||
## One-time maintainer setup
|
||||
|
||||
1. Keep **Settings → General → Releases → Enable release immutability** enabled.
|
||||
The publisher checks the setting before creating a release and refuses to proceed
|
||||
if it is disabled or unreadable. GitHub locks assets and the tag on publication;
|
||||
immutability applies to future releases, not retroactively.
|
||||
2. Keep [EX-0006](../exceptions/register.md#ex-0006) open and unexpired before
|
||||
enabling version proposals. `K22-CI-16` otherwise restricts remaining secrets to
|
||||
tag-only environments; the release-PR bot needs a narrow main-only exception.
|
||||
Approval is recorded in the reviewed release-bundle implementation PR.
|
||||
3. Create a GitHub App installed **only on this repository**, with Contents and Pull
|
||||
requests **write**, Administration **read**, and no ruleset bypass. Store its client
|
||||
ID in the Actions variable `RELEASE_APP_CLIENT_ID`. Short-lived App tokens let
|
||||
release PRs trigger CI; a PR made using `GITHUB_TOKEN` would not.
|
||||
4. Configure `release-automation` with deployment access for `main` only, and
|
||||
`release` with a maintainer reviewer and deployment access for `v*` tags only.
|
||||
Store `RELEASE_APP_PRIVATE_KEY` separately in these **environment secrets**;
|
||||
never as a repository secret or committed file. Environments in the reusable
|
||||
publisher resolve their own secrets; caller workflows do not pass the key.
|
||||
Keep `protect-main` and `protect-release-tags` intact; the App does not create
|
||||
tags or bypass review. Allow the SHA-pinned Release Please action if repository
|
||||
action restrictions require an explicit allowlist entry.
|
||||
|
||||
No npm/NuGet publishing credential or registry package is required. Upstream App-token
|
||||
and Release Please actions are pinned and tracked by Dependabot. See the
|
||||
[distribution ADR](adr/0001-release-standards-bundles.md).
|
||||
|
||||
## Publish a version
|
||||
|
||||
1. Review the release PR, complete its CI and review gates, and squash-merge it.
|
||||
2. For MINOR and MAJOR releases, complete the pre-tag checks in an issue made from
|
||||
[the release checklist](../templates/release-checklist.md). Attach the full
|
||||
conformance result and security assessment. PATCH releases still need conformance
|
||||
and scan results. Keep the README's draft/adoption status accurate before `v1.0.0`.
|
||||
Apply the maintainer-only `release: ready` label after sections 1–4 and the
|
||||
green-main/release-notes check in section 5 are complete. Leave publication checks
|
||||
unchecked until verified, then close the issue after shipping.
|
||||
3. Tag the **release PR's merge commit** with its proposed version. Only the maintainer
|
||||
can create protected tags. For a MINOR or MAJOR, the annotation must contain
|
||||
`Release checklist: https://github.com/KofTwentyTwo/standards/issues/<number>`.
|
||||
|
||||
```bash
|
||||
git fetch origin main
|
||||
git tag -s v0.1.0 <release-pr-merge-sha> \
|
||||
-m "Release checklist: https://github.com/KofTwentyTwo/standards/issues/123"
|
||||
git push origin v0.1.0
|
||||
```
|
||||
|
||||
4. The tag workflow validates ancestry, version files, the pending release PR, and
|
||||
the checklist; reruns all repository CI gates; builds and attests the assets;
|
||||
and waits for the `release` environment approval. Publication creates a draft with
|
||||
all assets and notes, then publishes it. The release PR is marked
|
||||
`autorelease: tagged` so the next version can be proposed.
|
||||
|
||||
These bundles ship no compiled software, so the compiled-artifact SBOM requirement
|
||||
does not apply. The included workflows/configs retain their upstream pins; source
|
||||
provenance and checksums identify the published bundle.
|
||||
|
||||
## Verify and reproduce
|
||||
|
||||
Every release includes `KofTwentyTwo-standards-<version>.zip`, `.tar.gz`,
|
||||
`.manifest.json`, `.notes.md`, and `SHA256SUMS`. The manifest records the exact commit
|
||||
and license split. Download assets from a specific version, then verify:
|
||||
|
||||
```bash
|
||||
gh release download v0.1.0 --repo KofTwentyTwo/standards --dir standards-0.1.0
|
||||
cd standards-0.1.0
|
||||
sha256sum --check SHA256SUMS
|
||||
gh attestation verify KofTwentyTwo-standards-0.1.0.zip --repo KofTwentyTwo/standards
|
||||
```
|
||||
|
||||
On Windows, compare `Get-FileHash -Algorithm SHA256` to the corresponding entry in
|
||||
`SHA256SUMS`. Pin reusable workflow callers to the release's full commit SHA, with a
|
||||
version comment; do not use a moving branch or tag as a workflow pin.
|
||||
|
||||
To reproduce from a checkout containing the release commit:
|
||||
|
||||
```powershell
|
||||
pwsh tools/Build-StandardsPackage.ps1 -Version 0.1.0 -Ref v0.1.0 -OutputPath artifacts/rebuild-0.1.0
|
||||
pwsh tools/Test-StandardsPackage.ps1
|
||||
```
|
||||
|
||||
Use the original checklist URL with `-ChecklistUrl` to reproduce `.notes.md` and
|
||||
`SHA256SUMS` exactly. ZIP and tar.gz output is deterministic for a commit using the
|
||||
same Git/archive tool versions. The builder refuses an existing output directory.
|
||||
|
||||
## Failed releases
|
||||
|
||||
Never replace published assets or move/reuse a version tag. Fix a published error
|
||||
with a new release. A failed upload or publication leaves a draft; inspect its run
|
||||
and assets, then remove **only the unpublished draft** before rerunning the tag's
|
||||
`standards-release` workflow using **Run workflow** with that tag selected. Keep the
|
||||
tag unchanged. If publication succeeded but PR labeling failed, change the PR label
|
||||
from `autorelease: pending` to `autorelease: tagged`; do not rebuild the release.
|
||||
|
||||
References: [GitHub immutable releases](https://docs.github.com/en/code-security/concepts/supply-chain-security/immutable-releases),
|
||||
[enabling immutability](https://docs.github.com/en/code-security/how-tos/secure-your-supply-chain/establish-provenance-and-integrity/prevent-release-changes),
|
||||
and [Release Please token behavior](https://github.com/googleapis/release-please-action#other-actions-on-release-please-prs).
|
||||
@@ -0,0 +1,22 @@
|
||||
# Scorecard finding dispositions
|
||||
|
||||
Reviewed on 2026-10-10 for the v0.1.0 release. These are repository-process
|
||||
heuristics, not identified vulnerabilities in shipped code or dependencies.
|
||||
Static review inspected the reported main revision, current rulesets, the security
|
||||
policy, and EX-0001. It does not establish an independent human review of historical
|
||||
changes or predict future maintenance.
|
||||
|
||||
| Alert | Disposition and evidence | Reassess |
|
||||
| --- | --- | --- |
|
||||
| [#4 Code-Review](https://github.com/KofTwentyTwo/standards/security/code-scanning/4) | Accepted single-maintainer risk under [EX-0001](../../exceptions/register.md#ex-0001). Outside contributors cannot merge; PR checks, AI review, CodeQL, signed commits, and maintainer merge remain required. Scorecard counts human approvals, so it does not represent these compensating controls. | 2027-01-10, or when a second maintainer joins |
|
||||
| [#3 Maintained](https://github.com/KofTwentyTwo/standards/security/code-scanning/3) | Age heuristic: GitHub reports creation on 2026-10-09, with recent feature PRs and active CI. The message identifies a repository younger than 90 days, rather than abandoned shipped software. | 2027-01-10 |
|
||||
| [#1 Branch-Protection](https://github.com/KofTwentyTwo/standards/security/code-scanning/1) | Accepted human-approval gap under EX-0001. Active `protect-main` has no bypass and enforces PR-only squash merges, nine required checks, signed commits, linear history, and resolved threads. Human approval by another maintainer is unavailable. | 2027-01-10, or when a second maintainer joins |
|
||||
|
||||
Keep the accepted risk visible; dismissal is not a claim that the missing second
|
||||
maintainer exists. Do not disable Scorecard or weaken protections. Reopen and
|
||||
reassess these dispositions when their assumptions change. The LOW best-practices
|
||||
badge and MEDIUM fuzzing recommendations remain open for follow-up.
|
||||
|
||||
No OpenVEX statement is fabricated for these process findings: no vulnerable shipped
|
||||
component or exploit was identified. Any actual vulnerability present but not
|
||||
exploitable needs the OpenVEX statement required by K22-SEC-42.
|
||||
@@ -0,0 +1,83 @@
|
||||
# Threat model: standards distribution
|
||||
|
||||
- **Version reviewed:** v0.1.0 release-bundle implementation
|
||||
- **Last updated:** 2026-10-10
|
||||
- **Status:** Implementation reviewed; provisioning is verified in the release checklist.
|
||||
|
||||
## Product and actors
|
||||
|
||||
This public repository distributes engineering guidance, configurations, templates,
|
||||
and scripts. Consumers download a versioned bundle or call reusable workflows pinned
|
||||
to a commit. The bundle does not run an application or collect consumer data.
|
||||
Contributors submit changes; the maintainer reviews and merges PRs, creates protected
|
||||
version tags, and approves publication. GitHub-hosted Actions runners build artifacts;
|
||||
GitHub stores releases, environment secrets, and attestations. Upstream Actions are
|
||||
third-party code trusted only at reviewed, pinned versions.
|
||||
|
||||
## Data flow and boundaries
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
contributor[Contributor] --> pr[Pull request]
|
||||
pr --> gates[CI and maintainer review]
|
||||
gates --> main[Protected main]
|
||||
main --> proposal[Version and changelog PR]
|
||||
proposal --> gates
|
||||
main --> tag[Maintainer-created protected tag]
|
||||
tag --> build[Reusable gated build]
|
||||
build --> draft[Draft assets and attestations]
|
||||
draft --> approval[Release environment approval]
|
||||
approval --> release[Immutable GitHub Release]
|
||||
release --> consumer[Consumer verifies and extracts]
|
||||
```
|
||||
|
||||
Untrusted PR content cannot access version-bot or publication secrets. The
|
||||
`release-automation` environment admits only protected `main`; its App key is used
|
||||
after CI succeeds. The `release` environment admits only protected `v*` tags and
|
||||
requires maintainer approval. Tokens are scoped per job and revoked on completion.
|
||||
Publishing checks GitHub's immutability setting before creating the draft.
|
||||
|
||||
## External interfaces and assets
|
||||
|
||||
| Interface | Data and authentication | Validation |
|
||||
| --- | --- | --- |
|
||||
| Git checkout and archive | Public source, tagged commit | Main ancestry, committed version/manifest/changelog |
|
||||
| GitHub REST API | App installation token or job-scoped `GITHUB_TOKEN` over HTTPS | Release PR, maintainer-approved preflight checklist, immutability setting |
|
||||
| Actions secrets | App private key in named environments | Deployment policy, reviewed job definitions, no repository secret |
|
||||
| OIDC attestations | GitHub-signed build provenance | Consumer `gh attestation verify` |
|
||||
| Release downloads | Versioned archives, manifest, notes, checksums | HTTPS, checksum and provenance verification |
|
||||
|
||||
Assets are the App credential, integrity of standards and executable tooling,
|
||||
protected source history, release tags, artifact bytes, and distribution availability.
|
||||
|
||||
## Threats and controls
|
||||
|
||||
| Threat / STRIDE category | Control | Remaining condition |
|
||||
| --- | --- | --- |
|
||||
| Forged or malicious contribution / spoofing | Signed commits, DCO, PR gates and maintainer review | Review remains accountable to a human |
|
||||
| Modified source or artifact / tampering | Commit-based archives, checksums, attestations, immutable publication | Consumer must verify downloads |
|
||||
| Disputed release origin / repudiation | Protected tagged commit, manifest SHA, release checklist, attestations | Maintainer reviews checklist evidence |
|
||||
| Credential theft / information disclosure | Environment restrictions, scoped App installation, ephemeral tokens, secret scans | EX-0006 must remain open and unexpired |
|
||||
| Failed or duplicate publication / denial of service | Timeouts, per-tag concurrency, draft-first upload, no replacement | Failed unpublished drafts need maintainer recovery |
|
||||
| Workflow privilege escalation | SHA-pinned Actions, no cached release dependencies, no App ruleset bypass | Compromised trusted action remains a supply-chain risk |
|
||||
|
||||
## Exceptions and review
|
||||
|
||||
[EX-0006](../../exceptions/register.md#ex-0006) covers main-only access to the version
|
||||
bot's key. Existing single-maintainer review uses
|
||||
[EX-0001](../../exceptions/register.md#ex-0001). Review this model with the implementation
|
||||
PR and each MINOR/MAJOR checklist; update it when credentials, channels, or workflow
|
||||
trust boundaries change.
|
||||
|
||||
## Review log
|
||||
|
||||
- **2026-10-10, v0.1.0:** Reviewed source-only packaging, release-PR validation,
|
||||
preflight approval, App token scopes, environment restrictions, provenance, and
|
||||
immutable publication. Main and tag protections remain in place. Most likely
|
||||
failure is incomplete draft publication; recovery retains the tag and replaces
|
||||
no published assets. Highest-impact threat is a compromised credential or upstream
|
||||
Action; restricted environments, scoped installation, reviewed SHA pins, and
|
||||
required PR gates limit its reach. Provisioning and live checks are recorded in
|
||||
the first-release checklist. Existing Scorecard process findings are recorded in
|
||||
[their dispositions](scorecard-dispositions.md); EX-0001 retains the accepted
|
||||
single-maintainer risk.
|
||||
@@ -10,6 +10,7 @@ Open and closed exceptions to the KofTwentyTwo standards. The process is in
|
||||
| [EX-0003](#ex-0003) | Open | K22-DEP-02 (license allowlist) | gclo | Reviewed yearly, next on 2027-10-09 |
|
||||
| [EX-0004](#ex-0004) | Open | K22-CI-30, K22-REL-04 (shared release workflow) | gclo | When the shared reusable release workflow ships, at the latest 2027-04-09 |
|
||||
| [EX-0005](#ex-0005) | Open | K22-AI-10 (agents via PR only), K22-AI-11 (in part: unconfirmed pushes) | second-brain, nix, Praetor, Jellyfin | Reviewed yearly, next on 2027-10-10 |
|
||||
| [EX-0006](#ex-0006) | Open | K22-CI-16 (tag-only environment secrets) | standards | 2027-04-10; review 2027-01-10 |
|
||||
|
||||
## EX-0001
|
||||
|
||||
@@ -143,3 +144,28 @@ Open and closed exceptions to the KofTwentyTwo standards. The process is in
|
||||
requests per `K22-AI-10`.
|
||||
- **Expires:** reviewed yearly, next on 2027-10-10.
|
||||
- **Owner:** the maintainer.
|
||||
|
||||
## EX-0006
|
||||
|
||||
**Main-only release-version bot credential**
|
||||
|
||||
- **Status:** Open; approved with the reviewed release-bundle implementation PR.
|
||||
- **Requirements:** `K22-CI-16` restricts remaining secrets to environments whose
|
||||
deployment rules allow only protected `v*` tags.
|
||||
- **Scope:** `KofTwentyTwo/standards`, only `RELEASE_APP_PRIVATE_KEY` in the
|
||||
`release-automation` environment for the `ci.yml` release-PR job on protected `main`.
|
||||
- **Reason:** Release Please needs a GitHub App token to create version PRs that
|
||||
trigger CI without enabling PR creation by `GITHUB_TOKEN`. No native GitHub App
|
||||
OIDC exchange is configured; the private key is therefore needed before a tag exists.
|
||||
- **Risk:** compromised version-PR automation could obtain the App key and exercise
|
||||
the App's repository permissions.
|
||||
- **Compensating controls:** App installation limited to this repository; Contents and
|
||||
Pull requests write, Administration read only; no ruleset bypass; main-only
|
||||
environment deployment policy; job runs only after all main CI gates succeed;
|
||||
SHA-pinned actions; short-lived scoped installation tokens revoked after each job;
|
||||
all resulting PRs still require normal checks and maintainer review. The bot neither
|
||||
creates release tags nor signs or publishes packages. Publication still uses the
|
||||
protected tag-only `release` environment and GitHub's immutable-releases setting.
|
||||
- **Expires:** 2027-04-10, or earlier when version proposals use credential federation.
|
||||
- **Owner:** the maintainer.
|
||||
- **Review date:** 2027-01-10.
|
||||
@@ -0,0 +1,20 @@
|
||||
{
|
||||
"$schema": "https://raw.githubusercontent.com/googleapis/release-please/main/schemas/config.json",
|
||||
"release-type": "simple",
|
||||
"include-component-in-tag": false,
|
||||
"initial-version": "0.1.0",
|
||||
"changelog-sections": [
|
||||
{ "type": "security", "section": "Security", "hidden": false },
|
||||
{ "type": "feat", "section": "Features", "hidden": false },
|
||||
{ "type": "fix", "section": "Bug Fixes", "hidden": false },
|
||||
{ "type": "docs", "section": "Documentation", "hidden": false },
|
||||
{ "type": "perf", "section": "Performance", "hidden": false },
|
||||
{ "type": "refactor", "section": "Refactoring", "hidden": false },
|
||||
{ "type": "build", "section": "Build", "hidden": false },
|
||||
{ "type": "ci", "section": "Automation", "hidden": false },
|
||||
{ "type": "revert", "section": "Reverts", "hidden": false }
|
||||
],
|
||||
"packages": {
|
||||
".": { "version-file": "version.txt" }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,98 @@
|
||||
#Requires -Version 7.4
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Build versioned standards archives from a Git commit.
|
||||
.DESCRIPTION
|
||||
Uses git archive so untracked files and local edits cannot enter a release. Writes
|
||||
ZIP, tar.gz, source manifest, release notes, and SHA256SUMS without overwriting output.
|
||||
.PARAMETER Version
|
||||
Stable semantic version matching the committed version.txt and release manifest.
|
||||
.PARAMETER RepositoryPath
|
||||
Local source repository; defaults to the parent directory of this script.
|
||||
.PARAMETER Ref
|
||||
Commit to package; defaults to HEAD.
|
||||
.PARAMETER OutputPath
|
||||
New output directory; defaults to artifacts/standards in the source repository.
|
||||
.PARAMETER ChecklistUrl
|
||||
Completed release checklist to link in the notes, when applicable.
|
||||
#>
|
||||
[CmdletBinding()]
|
||||
param(
|
||||
[Parameter(Mandatory)]
|
||||
[ValidatePattern('^(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)$')]
|
||||
[string] $Version,
|
||||
[string] $RepositoryPath = (Split-Path -Parent $PSScriptRoot),
|
||||
[ValidateNotNullOrEmpty()]
|
||||
[string] $Ref = 'HEAD',
|
||||
[string] $OutputPath,
|
||||
[AllowEmptyString()]
|
||||
[string] $ChecklistUrl = ''
|
||||
)
|
||||
|
||||
Set-StrictMode -Version Latest
|
||||
$ErrorActionPreference = 'Stop'
|
||||
$PSNativeCommandUseErrorActionPreference = $true
|
||||
|
||||
$repository = (Resolve-Path -LiteralPath $RepositoryPath).Path
|
||||
$commit = (git -C $repository rev-parse --verify "$Ref^{commit}").Trim()
|
||||
$committedVersion = (git -C $repository show "${commit}:version.txt").Trim()
|
||||
$releaseManifest = (git -C $repository show "${commit}:.release-please-manifest.json") | ConvertFrom-Json -AsHashtable
|
||||
if($committedVersion -ne $Version -or $releaseManifest['.'] -ne $Version)
|
||||
{
|
||||
throw "Version $Version does not match version.txt and the release manifest at $commit."
|
||||
}
|
||||
|
||||
$changelog = (git -C $repository show "${commit}:CHANGELOG.md") -join "`n"
|
||||
$escapedVersion = [regex]::Escape($Version)
|
||||
$section = [regex]::Match($changelog, "(?ms)^## (?:\[$escapedVersion\](?:\([^\r\n]*\))?|$escapedVersion)(?=\s|$)[^\r\n]*\r?\n.*?(?=^## |\z)")
|
||||
if(-not $section.Success)
|
||||
{
|
||||
throw "CHANGELOG.md has no release notes for $Version at $commit."
|
||||
}
|
||||
$notes = $section.Value.Trim()
|
||||
if($ChecklistUrl)
|
||||
{
|
||||
if($ChecklistUrl -notmatch '^https://github\.com/[^/]+/[^/]+/issues/[1-9][0-9]*$')
|
||||
{
|
||||
throw 'ChecklistUrl must be a GitHub issue URL.'
|
||||
}
|
||||
$notes += "`n`nRelease checklist: $ChecklistUrl"
|
||||
}
|
||||
|
||||
if(-not $OutputPath)
|
||||
{
|
||||
$OutputPath = Join-Path $repository 'artifacts/standards'
|
||||
}
|
||||
$outputDirectory = [System.IO.Path]::GetFullPath($OutputPath)
|
||||
if(Test-Path -LiteralPath $outputDirectory)
|
||||
{
|
||||
throw "Output directory already exists: $outputDirectory. Choose a new directory."
|
||||
}
|
||||
New-Item -ItemType Directory -Path $outputDirectory | Out-Null
|
||||
|
||||
$name = "KofTwentyTwo-standards-$Version"
|
||||
foreach($format in @('zip', 'tar.gz'))
|
||||
{
|
||||
$archive = Join-Path $outputDirectory "$name.$format"
|
||||
git -C $repository archive "--format=$format" "--prefix=$name/" "--output=$archive" $commit
|
||||
}
|
||||
|
||||
$manifest = [ordered]@{
|
||||
name = 'KofTwentyTwo/standards'
|
||||
version = $Version
|
||||
tag = "v$Version"
|
||||
commit = $commit
|
||||
repository = 'https://github.com/KofTwentyTwo/standards'
|
||||
licenses = @('CC-BY-4.0 (documentation)', 'MIT (tooling)', 'Apache-2.0 (Kingsrook files; see NOTICE)')
|
||||
}
|
||||
$utf8 = [System.Text.UTF8Encoding]::new($false)
|
||||
$manifestText = ($manifest | ConvertTo-Json -Depth 4).Replace("`r`n", "`n") + "`n"
|
||||
[System.IO.File]::WriteAllText((Join-Path $outputDirectory "$name.manifest.json"), $manifestText, $utf8)
|
||||
[System.IO.File]::WriteAllText((Join-Path $outputDirectory "$name.notes.md"), $notes.Replace("`r`n", "`n") + "`n", $utf8)
|
||||
$checksums = foreach($file in (Get-ChildItem -LiteralPath $outputDirectory -File | Sort-Object -Property Name))
|
||||
{
|
||||
$hash = (Get-FileHash -LiteralPath $file.FullName -Algorithm SHA256).Hash.ToLowerInvariant()
|
||||
"$hash $($file.Name)"
|
||||
}
|
||||
[System.IO.File]::WriteAllText((Join-Path $outputDirectory 'SHA256SUMS'), ($checksums -join "`n") + "`n", $utf8)
|
||||
Write-Output "Built $name from $commit in $outputDirectory"
|
||||
@@ -0,0 +1,122 @@
|
||||
#Requires -Version 7.4
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Exercise standards packaging with an isolated local Git fixture.
|
||||
.DESCRIPTION
|
||||
Checks committed content, hidden files, both archive formats, determinism, hashes,
|
||||
version validation, release notes, and refusal to overwrite existing output.
|
||||
#>
|
||||
[CmdletBinding()]
|
||||
param()
|
||||
|
||||
Set-StrictMode -Version Latest
|
||||
$ErrorActionPreference = 'Stop'
|
||||
$PSNativeCommandUseErrorActionPreference = $true
|
||||
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Fail a behavioral assertion with a descriptive message.
|
||||
#>
|
||||
function Test-Condition
|
||||
{
|
||||
param([bool] $Condition, [string] $Message)
|
||||
if(-not $Condition) { throw "FAIL: $Message" }
|
||||
Write-Output "PASS: $Message"
|
||||
}
|
||||
|
||||
|
||||
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Verify an operation refuses invalid input for the expected reason.
|
||||
#>
|
||||
function Test-Exception
|
||||
{
|
||||
param([scriptblock] $Operation, [string] $Pattern)
|
||||
$failure = $null
|
||||
try { & $Operation | Out-Null }
|
||||
catch { $failure = $_.Exception.Message }
|
||||
Test-Condition ($null -ne $failure -and $failure -match $Pattern) "rejects $Pattern"
|
||||
}
|
||||
|
||||
$builder = Join-Path $PSScriptRoot 'Build-StandardsPackage.ps1'
|
||||
$fixtureRoot = Join-Path ([System.IO.Path]::GetTempPath()) "standards-package-$([guid]::NewGuid().ToString('N'))"
|
||||
$source = Join-Path $fixtureRoot 'source'
|
||||
New-Item -ItemType Directory -Path $source | Out-Null
|
||||
git init --quiet $source
|
||||
git -C $source config core.autocrlf false
|
||||
git -C $source config core.hooksPath /dev/null
|
||||
Set-Content -LiteralPath (Join-Path $source 'version.txt') -Value '1.2.3'
|
||||
Set-Content -LiteralPath (Join-Path $source '.release-please-manifest.json') -Value '{".":"1.2.3"}'
|
||||
Set-Content -LiteralPath (Join-Path $source '.editorconfig') -Value 'root = true'
|
||||
Set-Content -LiteralPath (Join-Path $source 'README.md') -Value 'committed content'
|
||||
Set-Content -LiteralPath (Join-Path $source 'CHANGELOG.md') -Value @'
|
||||
# Changelog
|
||||
|
||||
## [1.2.4](https://example.com/next) (2026-10-11)
|
||||
|
||||
Future notes.
|
||||
|
||||
## [1.2.3](https://example.com/current) (2026-10-10)
|
||||
|
||||
### Security
|
||||
|
||||
Current notes.
|
||||
|
||||
## 1.2.2
|
||||
|
||||
Older notes.
|
||||
'@
|
||||
git -C $source add -- .
|
||||
git -C $source -c user.name=Fixture -c user.email=fixture@example.invalid -c commit.gpgsign=false commit --quiet -m 'test: packaging fixture'
|
||||
|
||||
Set-Content -LiteralPath (Join-Path $source 'README.md') -Value 'uncommitted content'
|
||||
Set-Content -LiteralPath (Join-Path $source 'local-only.txt') -Value 'untracked content'
|
||||
$first = Join-Path $fixtureRoot 'first'
|
||||
$second = Join-Path $fixtureRoot 'second'
|
||||
$checklist = 'https://github.com/KofTwentyTwo/standards/issues/123'
|
||||
& $builder -Version '1.2.3' -RepositoryPath $source -OutputPath $first -ChecklistUrl $checklist
|
||||
& $builder -Version '1.2.3' -RepositoryPath $source -OutputPath $second -ChecklistUrl $checklist
|
||||
|
||||
$prefix = 'KofTwentyTwo-standards-1.2.3'
|
||||
$zip = [System.IO.Compression.ZipFile]::OpenRead((Join-Path $first "$prefix.zip"))
|
||||
try
|
||||
{
|
||||
$names = @($zip.Entries.FullName)
|
||||
Test-Condition ($names -contains "$prefix/.editorconfig") 'ZIP preserves hidden configuration files'
|
||||
Test-Condition (-not ($names -match '/(?:\.git/|local-only\.txt$)')) 'ZIP excludes Git metadata and untracked files'
|
||||
$reader = [System.IO.StreamReader]::new($zip.GetEntry("$prefix/README.md").Open())
|
||||
try { Test-Condition ($reader.ReadToEnd().Trim() -eq 'committed content') 'ZIP contains committed content despite local edits' }
|
||||
finally { $reader.Dispose() }
|
||||
}
|
||||
finally { $zip.Dispose() }
|
||||
|
||||
$extracted = Join-Path $fixtureRoot 'extracted'
|
||||
New-Item -ItemType Directory -Path $extracted | Out-Null
|
||||
tar -xzf (Join-Path $first "$prefix.tar.gz") -C $extracted
|
||||
Test-Condition ((Get-Content -LiteralPath (Join-Path $extracted "$prefix/README.md") -Raw).Trim() -eq 'committed content') 'tar.gz contains committed content'
|
||||
Test-Condition (Test-Path -LiteralPath (Join-Path $extracted "$prefix/.editorconfig")) 'tar.gz preserves hidden configuration files'
|
||||
$manifest = Get-Content -LiteralPath (Join-Path $first "$prefix.manifest.json") -Raw | ConvertFrom-Json
|
||||
Test-Condition ($manifest.version -eq '1.2.3' -and $manifest.commit -eq (git -C $source rev-parse HEAD)) 'manifest identifies version and exact source commit'
|
||||
$notes = Get-Content -LiteralPath (Join-Path $first "$prefix.notes.md") -Raw
|
||||
Test-Condition ($notes -match 'Current notes' -and $notes -notmatch 'Older notes|Future notes' -and $notes.Contains($checklist)) 'notes contain only this release and its checklist'
|
||||
foreach($file in (Get-ChildItem -LiteralPath $first -File))
|
||||
{
|
||||
Test-Condition ((Get-FileHash -LiteralPath $file.FullName).Hash -eq (Get-FileHash -LiteralPath (Join-Path $second $file.Name)).Hash) "$($file.Name) is reproducible"
|
||||
}
|
||||
$checksums = @(Get-Content -LiteralPath (Join-Path $first 'SHA256SUMS'))
|
||||
Test-Condition ($checksums.Count -eq 4) 'checksums cover every asset except the checksum file itself'
|
||||
foreach($line in $checksums)
|
||||
{
|
||||
$hash, $fileName = $line -split ' ', 2
|
||||
Test-Condition ($hash -eq (Get-FileHash -LiteralPath (Join-Path $first $fileName)).Hash.ToLowerInvariant()) "$fileName matches its checksum"
|
||||
}
|
||||
Test-Exception { & $builder -Version '1.2.4' -RepositoryPath $source -OutputPath (Join-Path $fixtureRoot 'mismatch') } 'does not match'
|
||||
Test-Exception { & $builder -Version '01.2.3' -RepositoryPath $source } 'ValidatePattern|validation|pattern'
|
||||
Test-Exception { & $builder -Version '1.2.3' -RepositoryPath $source -OutputPath $first } 'already exists'
|
||||
Test-Exception { & $builder -Version '1.2.3' -RepositoryPath $source -OutputPath (Join-Path $fixtureRoot 'invalid-url') -ChecklistUrl 'http://example.com/123' } 'GitHub issue URL'
|
||||
Set-Content -LiteralPath (Join-Path $source 'CHANGELOG.md') -Value '# Changelog without release notes'
|
||||
git -C $source add -- CHANGELOG.md
|
||||
git -C $source -c user.name=Fixture -c user.email=fixture@example.invalid -c commit.gpgsign=false commit --quiet -m 'test: missing release notes fixture'
|
||||
Test-Exception { & $builder -Version '1.2.3' -RepositoryPath $source -OutputPath (Join-Path $fixtureRoot 'missing-notes') } 'no release notes'
|
||||
Write-Output "All standards package tests passed. Fixture: $fixtureRoot"
|
||||
@@ -0,0 +1 @@
|
||||
0.0.0
|
||||
@@ -7,3 +7,5 @@ rules:
|
||||
# not yet understood by actionlint 1.7.12, which is also a required gate.
|
||||
ignore:
|
||||
- ci.yml
|
||||
- release.yml
|
||||
- release-standards.yml
|
||||
Reference in new issue
Block a user