diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index e5fd817..e10d50f 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -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
diff --git a/.github/workflows/release-standards.yml b/.github/workflows/release-standards.yml
new file mode 100644
index 0000000..8f4870f
--- /dev/null
+++ b/.github/workflows/release-standards.yml
@@ -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'
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
new file mode 100644
index 0000000..a333a5e
--- /dev/null
+++ b/.github/workflows/release.yml
@@ -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
diff --git a/.release-please-manifest.json b/.release-please-manifest.json
new file mode 100644
index 0000000..0967ef4
--- /dev/null
+++ b/.release-please-manifest.json
@@ -0,0 +1 @@
+{}
diff --git a/AGENTS.md b/AGENTS.md
new file mode 100644
index 0000000..7191894
--- /dev/null
+++ b/AGENTS.md
@@ -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--` 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.
diff --git a/CHANGELOG.md b/CHANGELOG.md
new file mode 100644
index 0000000..06f0180
--- /dev/null
+++ b/CHANGELOG.md
@@ -0,0 +1,3 @@
+# Changelog
+
+Release Please maintains this file from Conventional Commits.
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index a224624..53f6315 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -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
diff --git a/README.md b/README.md
index e233b39..f26244e 100644
--- a/README.md
+++ b/README.md
@@ -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.
diff --git a/SECURITY.md b/SECURITY.md
index 333a90d..68a8e6b 100644
--- a/SECURITY.md
+++ b/SECURITY.md
@@ -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
diff --git a/docs/adr/0001-release-standards-bundles.md b/docs/adr/0001-release-standards-bundles.md
new file mode 100644
index 0000000..b492bd7
--- /dev/null
+++ b/docs/adr/0001-release-standards-bundles.md
@@ -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.
diff --git a/docs/releases.md b/docs/releases.md
new file mode 100644
index 0000000..1c5a158
--- /dev/null
+++ b/docs/releases.md
@@ -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/`.
+
+ ```bash
+ git fetch origin main
+ git tag -s v0.1.0 \
+ -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-.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).
diff --git a/docs/security/scorecard-dispositions.md b/docs/security/scorecard-dispositions.md
new file mode 100644
index 0000000..97b3209
--- /dev/null
+++ b/docs/security/scorecard-dispositions.md
@@ -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.
diff --git a/docs/security/threat-model.md b/docs/security/threat-model.md
new file mode 100644
index 0000000..8a6d95d
--- /dev/null
+++ b/docs/security/threat-model.md
@@ -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.
diff --git a/exceptions/register.md b/exceptions/register.md
index 8015cb2..7136da6 100644
--- a/exceptions/register.md
+++ b/exceptions/register.md
@@ -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.
diff --git a/release-please-config.json b/release-please-config.json
new file mode 100644
index 0000000..64c098f
--- /dev/null
+++ b/release-please-config.json
@@ -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" }
+ }
+}
diff --git a/tools/Build-StandardsPackage.ps1 b/tools/Build-StandardsPackage.ps1
new file mode 100644
index 0000000..8660577
--- /dev/null
+++ b/tools/Build-StandardsPackage.ps1
@@ -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"
diff --git a/tools/Test-StandardsPackage.ps1 b/tools/Test-StandardsPackage.ps1
new file mode 100644
index 0000000..0b9334b
--- /dev/null
+++ b/tools/Test-StandardsPackage.ps1
@@ -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"
diff --git a/version.txt b/version.txt
new file mode 100644
index 0000000..77d6f4c
--- /dev/null
+++ b/version.txt
@@ -0,0 +1 @@
+0.0.0
diff --git a/zizmor.yml b/zizmor.yml
index c1ec7e5..f6de413 100644
--- a/zizmor.yml
+++ b/zizmor.yml
@@ -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