Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .gds/bundle.lock.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ bundle:
attestation_identity_digest: "sha256:e1b787c63de916f750e2cbcbf664967669024e1062964515f612b2c826c9e10d"

projection:
input_digest: "sha256:2b4778d4f725f958775501f9ea0a01e005ff452f426b088c3b78b9164d61deeb"
input_digest: "sha256:4aff802086959c163a94f212e2bc166d7f515577bfce4fc84343f6b07fe6130f"
output_digest: "sha256:8e8a9aec59f3cb0c89cba52e37ab0015501f94aaa72d8e9dc3e03304c7244852"
files:
- path: ".gds/compiled-policy.json"
Expand Down
2 changes: 1 addition & 1 deletion .gds/repository.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ verification:
lint: ["actionlint"]
test: ["scripts/validate_module.sh"]
required: ["lint", "test"]
required_contexts: ["ci-gate"]
required_contexts: []

agent:
context_profile: "project-default"
Expand Down
13 changes: 1 addition & 12 deletions .github/rulesets/branch-main.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,22 +29,11 @@
"require_code_owner_review": false,
"require_last_push_approval": false,
"required_review_thread_resolution": true,
"require_extra_approval_for_unattributed_changes": true,
"allowed_merge_methods": [
"merge"
]
}
},
{
"type": "required_status_checks",
"parameters": {
"strict_required_status_checks_policy": true,
"do_not_enforce_on_create": false,
"required_status_checks": [
{
"context": "ci-gate"
}
]
}
}
]
}
16 changes: 8 additions & 8 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,10 @@ on:
branches: [main]
pull_request:
branches: [main]
# ci-gate is this repository's required context. A required check is never
# reported to a merge queue unless its workflow subscribes to merge_group,
# so without this an enabled queue would wait forever for a status that can
# never arrive.
# ci-gate is truthful advisory evidence here, not a required merge context.
# A status check is never reported to a merge queue unless its workflow
# subscribes to merge_group, so this stays for consumers who require
# ci-gate and for a later queue enablement.
merge_group:
types: [checks_requested]
workflow_dispatch:
Expand Down Expand Up @@ -249,7 +249,7 @@ jobs:
# notice one silently becoming a no-op.
#
# These live in ci.yml rather than in a workflow of their own because a
# required context must be caller-native: ci-gate's `needs` is this run's real
# aggregate must be caller-native: ci-gate's `needs` is this run's real
# dependency graph, and nothing may cross a workflow boundary. They cannot be
# a called reusable either — a self workflow must not be `on: workflow_call`,
# since everything that is becomes part of the product consumers pin.
Expand Down Expand Up @@ -282,7 +282,7 @@ jobs:
go-version: '1.24'
# setup-go caches the module and build cache with no input at all.
# This job is in `ci-gate.needs`, so that entry would be an unreviewed
# input to a required check. Found by deriving the required surface
# input to the self-CI aggregate. Found by deriving that surface
# from the gate's own graph rather than from a hand-written list.
cache: false

Expand Down Expand Up @@ -433,8 +433,8 @@ jobs:
contents: read
steps:
# Caller-native: `needs` is this run's real dependency graph, so nothing
# crosses a workflow boundary and nothing can be substituted. This is the
# shape a required check must have — see docs/08 and gate.yml's header.
# crosses a workflow boundary and nothing can be substituted. Consumers
# who require `ci-gate` need this shape — see docs/08 and gate.yml.
- name: Aggregate required results
env:
RESULTS: ${{ toJSON(needs) }}
Expand Down
11 changes: 8 additions & 3 deletions .github/workflows/dependabot-catalog-convergence.yml
Original file line number Diff line number Diff line change
Expand Up @@ -80,20 +80,25 @@ jobs:
persist-credentials: true
path: candidate
- name: Synchronize derived action declarations
run: python3 -I trusted/scripts/sync_action_catalog.py --root candidate
run: python3 -I trusted/scripts/sync_action_catalog.py --root candidate --catalog-only
- name: Commit converged declarations
id: commit
working-directory: candidate
env:
HEAD_BRANCH: ${{ github.event.workflow_run.head_branch }}
run: |
set -euo pipefail
if git diff --quiet -- .github/workflows catalog docs/generated; then
# GITHUB_TOKEN cannot push workflow-file updates without the
# `workflows` permission. Dependabot already owns those files; this
# job only commits catalog and generated docs derived from them.
# sync_action_catalog.py --catalog-only requires a unique pin per
# action across workflow files and fails closed on mixed identities.
if git diff --quiet -- catalog docs/generated; then
echo "catalog already converged"
else
git config user.name github-actions[bot]
git config user.email 41898282+github-actions[bot]@users.noreply.github.com
git add .github/workflows catalog docs/generated
git add catalog docs/generated
git commit -m "chore(deps): synchronize action catalog"
fi
git push origin "HEAD:${HEAD_BRANCH}"
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/zizmor-sarif.yml
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ jobs:
with:
version: 0.11.30
# This workflow backs the `zizmor` job, which is in `ci-gate.needs`.
# A required gate must not take an unreviewed input.
# The self-CI aggregate must not take an unreviewed input.
enable-cache: false

- name: Run zizmor (SARIF)
Expand Down
31 changes: 22 additions & 9 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -61,12 +61,13 @@ Touch this → also do this:
Failed, cancelled, skipped and missing results prove nothing. Preserve the
scope of actual fixture execution; static validation is not runtime evidence.
- **a gate's behaviour** → the `shell-gates` and `dockerfile-gate` jobs in
`ci.yml` are inside `ci-gate`'s `needs`, so they **block the merge**. They lift
each gate's real step out of its workflow with
`scripts/negative_gate_probe.py` and run it twice: the broken fixture under
`tests/fixtures/negative/` must be rejected and the clean one accepted, for
every covered gate, `zizmor-no-sarif` among them. A gate that never fails is
not a gate, and a probe that never passes is not a test.
`ci.yml` are inside `ci-gate`'s `needs`. They remain focused self-CI evidence
(ordinary merge does not wait on remote `ci-gate`). They lift each gate's
real step out of its workflow with `scripts/negative_gate_probe.py` and run
it twice: the broken fixture under `tests/fixtures/negative/` must be
rejected and the clean one accepted, for every covered gate,
`zizmor-no-sarif` among them. A gate that never fails is not a gate, and a
probe that never passes is not a test.
- **a catalog file** → run `generate_docs.py` through the isolated launcher above.
- **a skill** → run `sync_skills.py` through the isolated launcher above.
- **a product fact** → re-read its `source_urls` and correct it. Bumping the
Expand Down Expand Up @@ -101,6 +102,7 @@ the named script — its fixtures say what the contract is.
| `capability.schema.yaml` is executed against `capabilities.yml`, not just shipped | `validate_catalog.py`, `_json_schema.py` |
| Branch and tag rulesets are `active`, and release tags carry the immutability rules | `check_rulesets.py` |
| A documented `zizmor`/`actionlint` command is the one `ci-gate` runs | `check_documented_commands.py` |
| This repository's default-branch ruleset has no required general CI context | `check_rulesets.py` |
| Every `CHANGELOG.md` release heading is a real tag, and dates run newest first | `check_release_ledger.py` |

Two rules no validator can catch for you:
Expand All @@ -113,6 +115,8 @@ Two rules no validator can catch for you:
Scorecard is the trap: it cannot run on a pull-request head, so it protects
nothing while blocking every merge. The requirement can live in *classic*
branch protection, where a ruleset-shaped investigation will not find it.
This repository does not require a general CI status check for ordinary
merge; consumers may still select `ci-gate` as theirs.

## Tier truth

Expand All @@ -128,9 +132,18 @@ a declared lane or green source check is not proof it ran on every platform.
## Git

Conventional Commits under 100 chars, `git commit -s -S`, no `Co-Authored-By`.
`main` is PR-only and takes **merge commits** (squash and rebase are disabled
live), behind the required `ci-gate`. Fill the PR template; workflow changes owe
a permissions diff and a threat-model note.
`main` is PR-only and takes **merge commits**. Ordinary merge does not wait on
a required general CI status check. `ci-gate` still runs (core validators,
changed-path validators, actionlint, zizmor, and the negative shell/Dockerfile
gates) and is truthful advisory evidence. Runtime fixtures, Scorecard, CodeQL,
language packs and other self-workflows stay truthful background evidence: a
failure remains a failure and opens an issue here. Prove a change with the
local Commands above. Fill the PR template; workflow changes owe a permissions
diff and a threat-model note.

Source ruleset `.github/rulesets/branch-main.json` is merge-only, signed, and
has no required general CI context. Live GitHub settings are not applied from
this tree; a drift report belongs with the coordinator's plan→approve→apply.

Releases are tag-driven and immutable: `VERSION` must equal the tag on one
LF-terminated line, `CHANGELOG.md` must hold exactly one matching `## [X.Y.Z]`
Expand Down
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,13 @@ The project follows Semantic Versioning.
Consumer adoption resolves the programme from the immutable release being
pinned, not from `main`. Private attestations stay an Enterprise Cloud plan
gate, independent of the three add-ons.
- Dependabot catalog convergence commits only catalog and generated docs, so
the default `GITHUB_TOKEN` can push without `workflows` permission.
Catalog-only follows the unique workflow pin per action and fails closed
when identities are mixed, so the catalog cannot describe a pin the tree
does not share. Ordinary merge in this repository does not require a
general CI status check; `ci-gate` stays truthful advisory evidence.
Authored skill `metadata:` mappings stay mappings.

- Re-verify four vendor allowance records with staggered review dates, correct
Ubicloud's monthly credit and Harness's conditional CI credit semantics, and
Expand Down
20 changes: 11 additions & 9 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,9 @@ license your contribution under [AGPL-3.0-or-later](LICENSE).

## Non-negotiables for every workflow PR

These are enforced by review, the self-CI `ci-gate`, `actionlint`, and `zizmor`.
A PR that misses any of them will not be merged.
These are enforced by review plus the focused local checks below (`validate_all`,
`actionlint`, and `zizmor`). `ci-gate` still runs them remotely as truthful
advisory evidence; ordinary merge does not wait on that remote context.

1. **Full-SHA action pins with version comments.** Every `uses:` of a
third-party action pins a 40-character commit SHA followed by a version
Expand Down Expand Up @@ -88,7 +89,7 @@ A PR that misses any of them will not be merged.

## Local checks

Run these before opening a PR (they mirror the self-CI `ci-gate`):
Run these before opening a PR (they are the focused checks `ci-gate` also runs):

```bash
# Lint all workflow YAML
Expand Down Expand Up @@ -154,12 +155,13 @@ one command ended up disagreeing.

## Branch protection and CI

`main` is protected: signed commits, required review plus code-owner review,
linear history, no force-push or deletion, and the required `ci-gate` status
check. All workflow files are owned by [@rldyourmnd](https://github.com/rldyourmnd)
via [CODEOWNERS](.github/CODEOWNERS), so a maintainer review is always required.
Open PRs against `main` from a topic branch; the `ci-gate` check (contract +
actionlint + zizmor) must be green before merge.
`main` is protected: signed commits, no force-push or deletion, merge commits
only in the source ruleset, and no required general CI status check. `ci-gate`
(core + touched validators, actionlint, zizmor, negative gates) stays truthful
advisory evidence. Background workflows must stay truthful; they are not merge
blockers. All workflow files are owned
by [@rldyourmnd](https://github.com/rldyourmnd) via
[CODEOWNERS](.github/CODEOWNERS). Open PRs against `main` from a topic branch.

## Releases

Expand Down
12 changes: 8 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -338,10 +338,14 @@ has `pre` and `post` entry points that GitHub can execute even when a step-level

`main` and release tags are protected by **rulesets** in
[`.github/rulesets/`](.github/rulesets/) (pull-request-only merge commits,
resolved review threads, signed commits, linear history, the strict `ci-gate`
status check, and tag protection). The repository's solo-maintainer rule does
not require an impossible self-approval; reusable projects with independent
reviewers should require approvals and CODEOWNERS review. See
resolved review threads, signed commits, no force-push or deletion, and tag
protection). Ordinary merge in this repository does not wait on a required
general CI status check. `ci-gate` still runs as truthful advisory evidence;
Scorecard, CodeQL and runtime fixtures stay background evidence. Consumers
may require `ci-gate` if they want a merge gate. The repository's
solo-maintainer rule does not require an impossible self-approval; reusable
projects with independent reviewers should require approvals and CODEOWNERS
review. See
[`docs/08-governance-rulesets.md`](docs/08-governance-rulesets.md) for the
rulesets-first model and a migration guide from classic branch protection.

Expand Down
11 changes: 6 additions & 5 deletions SECURITY.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,14 +26,15 @@ response as soon as reasonably possible.
`timeout-minutes`.
- `ci.yml` runs static validators (`scripts/validate_all.py`), `actionlint`, and
`zizmor` (regular persona, SARIF) against this repository's own workflows on
every push and pull request; `ci-gate` aggregates them.
every push and pull request; `ci-gate` aggregates them as truthful advisory
evidence, not a required merge context.
- `check_harden_runner_contract.py` rejects conditional Harden-Runner steps and
any paid runtime-hardening reference in cross-tier/private-free workflows.
- `main` is protected by a repository ruleset (`.github/rulesets/branch-main.json`):
pull-request-only merge commits, resolved review threads, signed commits,
linear history, no force-push or deletion, and the strict `ci-gate` status
check. The solo-maintainer repository does not require self-approval. Release
tags are
pull-request-only merge commits, resolved review threads, signed commits, and
no force-push or deletion. Ordinary merge does not wait on a required general
CI status check. The solo-maintainer repository does not require
self-approval. Release tags are
protected by `.github/rulesets/tag-semver.json`.
- Releases are tag-driven and immutable, and ship an SPDX SBOM, SHA256SUMS, a
build-provenance attestation, and an SBOM attestation (SLSA v1 Build L3,
Expand Down
16 changes: 8 additions & 8 deletions catalog/cache-contract.yml
Original file line number Diff line number Diff line change
Expand Up @@ -190,34 +190,34 @@ refusals:
action: astral-sh/setup-uv
input: enable-cache
value: false
reason: The blocking gate must not depend on an unreviewed input.
reason: The self-CI aggregate must not depend on an unreviewed input.
- workflow: .github/workflows/ci.yml
job: validate-touched
action: astral-sh/setup-uv
input: enable-cache
value: false
reason: The blocking gate must not depend on an unreviewed input.
reason: The self-CI aggregate must not depend on an unreviewed input.
- workflow: .github/workflows/ci.yml
job: shell-gates
action: astral-sh/setup-uv
input: enable-cache
value: false
reason: The blocking gate must not depend on an unreviewed input.
reason: The self-CI aggregate must not depend on an unreviewed input.
- workflow: .github/workflows/ci.yml
job: dockerfile-gate
action: astral-sh/setup-uv
input: enable-cache
value: false
reason: The blocking gate must not depend on an unreviewed input.
reason: The self-CI aggregate must not depend on an unreviewed input.
- workflow: .github/workflows/ci.yml
job: shell-gates
action: actions/setup-go
input: cache
value: false
reason: >-
setup-go caches the module and build cache with no input at all, and this
job is in `ci-gate.needs`. Found by deriving the required surface from the
gate's own graph; the hand-written list had not named it.
job is in `ci-gate.needs`. Found by deriving the aggregate surface from
the gate's own graph; the hand-written list had not named it.
- workflow: .github/workflows/zizmor-sarif.yml
job: zizmor
action: astral-sh/setup-uv
Expand All @@ -226,7 +226,7 @@ refusals:
reason: >-
This workflow backs the `zizmor` job, which is in `ci-gate.needs`. The
contract's own closing paragraph used to assert that the undeclared
remainder could not reach a required check; this is the counter-example.
remainder could not reach the aggregate; this is the counter-example.

# Everything else is left to the producer's default on purpose, and there is now
# exactly one such place: `python-ci.yml`, which sets `enable-cache: true`
Expand All @@ -237,6 +237,6 @@ refusals:
# where a poisoned entry cannot reach a release or a required check". That was
# false: `zizmor-sarif.yml` is called by the `zizmor` job, which is in
# `ci-gate.needs`, and it took setup-uv's default. The refusals below are no
# longer a hand-written list -- `check_cache_contract.py` derives the required
# longer a hand-written list -- `check_cache_contract.py` derives the aggregate
# jobs from `ci-gate`'s own `needs` graph, follows local `uses:` into the called
# workflow, and demands a refusal for every cache-capable step it finds there.
3 changes: 3 additions & 0 deletions catalog/python-execution.yml
Original file line number Diff line number Diff line change
Expand Up @@ -472,6 +472,9 @@
"_workflow_yaml",
"check_python_execution_contract"
],
"check_rulesets.py": [
"_strict_yaml"
],
"check_runner_routing.py": [
"_strict_yaml",
"_workflow_yaml",
Expand Down
Loading
Loading