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
6 changes: 1 addition & 5 deletions .github/workflows/pr-orchestrator.yml
Original file line number Diff line number Diff line change
Expand Up @@ -89,12 +89,8 @@ jobs:
if [ "${{ github.event_name }}" = "pull_request" ]; then
BASE_REF="origin/${{ github.event.pull_request.base.ref }}"
TARGET_BRANCH="${{ github.event.pull_request.base.ref }}"
HEAD_REPO="${{ github.event.pull_request.head.repo.full_name }}"
THIS_REPO="${{ github.repository }}"
VERIFY_CMD+=(--version-check-base "$BASE_REF")
if [ "$TARGET_BRANCH" = "dev" ]; then
VERIFY_CMD+=(--metadata-only)
elif [ "$TARGET_BRANCH" = "main" ] && [ "$HEAD_REPO" = "$THIS_REPO" ]; then
if [ "$TARGET_BRANCH" = "main" ]; then
VERIFY_CMD+=(--require-signature)
fi
else
Expand Down
1 change: 0 additions & 1 deletion .github/workflows/publish-modules.yml
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,6 @@ concurrency:

jobs:
publish:
if: github.actor != 'github-actions[bot]'
runs-on: ubuntu-latest
env:
SPECFACT_MODULE_PRIVATE_SIGN_KEY: ${{ secrets.SPECFACT_MODULE_PRIVATE_SIGN_KEY }}
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/sign-modules-on-approval.yml
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,7 @@ jobs:
exit 0
fi
git add -u -- packages/
git commit -m "chore(modules): ci sign changed modules [skip ci]"
git commit -m "chore(modules): ci sign changed modules"
echo "changed=true" >> "$GITHUB_OUTPUT"
if ! git push origin "HEAD:${PR_HEAD_REF}"; then
echo "::error::Push to ${PR_HEAD_REF} failed (branch may have advanced after the approved commit). Update the PR branch and re-approve if signing is still required."
Expand Down
13 changes: 9 additions & 4 deletions .github/workflows/sign-modules.yml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# yaml-language-server: $schema=https://json.schemastore.org/github-workflow.json
# Auto-sign changed module manifests on push to dev/main, then strict-verify. PRs use checksum-only
# verification so feature branches are not blocked before CI can reconcile signatures on the branch.
# Auto-sign changed module manifests on push to dev/main, then strict-verify. PRs use full payload
# checksum + version bump without `--require-signature` until `main`.
name: Module Signature Hardening

on:
Expand Down Expand Up @@ -52,6 +52,11 @@ jobs:
runs-on: ubuntu-latest
permissions:
contents: write
# Same public-key env as pr-orchestrator so strict verify can check signatures against the
# configured release key (not only resources/keys/module-signing-public.pem in the checkout).
env:
SPECFACT_MODULE_PUBLIC_SIGN_KEY: ${{ secrets.SPECFACT_MODULE_PUBLIC_SIGN_KEY }}
SPECFACT_MODULE_SIGNING_PUBLIC_KEY_PEM: ${{ secrets.SPECFACT_MODULE_SIGNING_PUBLIC_KEY_PEM }}
steps:
- name: Checkout repository
uses: actions/checkout@v4
Expand Down Expand Up @@ -147,7 +152,7 @@ jobs:
echo "No manifest signing changes to commit."
exit 0
fi
git commit -m "chore(modules): auto-sign module manifests [skip ci]"
git commit -m "chore(modules): auto-sign module manifests"
git push origin "HEAD:${GITHUB_REF_NAME}"

reproducibility:
Expand Down Expand Up @@ -277,7 +282,7 @@ jobs:
echo "No staged module manifest updates."
exit 0
fi
git commit -m "chore(modules): manual workflow_dispatch sign changed modules [skip ci]"
git commit -m "chore(modules): manual workflow_dispatch sign changed modules"
git push origin "HEAD:${GITHUB_REF_NAME}"
echo "## Signed manifests pushed" >> "${GITHUB_STEP_SUMMARY}"
echo "Branch: \`${GITHUB_REF_NAME}\` (base: \`origin/${{ github.event.inputs.base_branch }}\`, bump: \`${{ github.event.inputs.version_bump }}\`, resign_all: \`${{ github.event.inputs.resign_all_manifests }}\`)." >> "${GITHUB_STEP_SUMMARY}"
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ hatch run test
hatch run specfact code review run --json --out .specfact/code-review.json
```

**Module signatures:** For pull request verification, `pr-orchestrator` runs `verify-modules-signature` with **`--payload-from-filesystem --enforce-version-bump`** and does **not** pass **`--require-signature` by default** (checksum + version bump only). **Strict `--require-signature`** applies when the integration target is **`main`** (pushes to `main` and PRs whose base is `main`). Add `--require-signature` locally when you want the same bar as **`main`** before promotion. Approval-time **`sign-modules-on-approval`** signs with `scripts/sign-modules.py` using **`--payload-from-filesystem`** among other flags; if verification fails after merge, re-sign affected **`module-package.yaml`** files and bump versions as needed. Pre-commit runs `scripts/pre-commit-verify-modules-signature.sh`, mirroring CI (**`--require-signature`** on branch **`main`** or when **`GITHUB_BASE_REF=main`** in Actions).
**Module signatures:** Split **PR-time** checks from **post-merge branch** checks. **`pr-orchestrator`** (on PRs and related events) runs `verify-modules-signature` with **`--payload-from-filesystem --enforce-version-bump`**, and for pull requests adds **`--version-check-base`**. PRs whose base is **`dev`** use payload checksum + version bump **without** **`--require-signature`**. PRs whose base is **`main`** append **`--require-signature`**; **`push`** paths in that workflow that target **`main`** also append **`--require-signature`**. Separately, **`.github/workflows/sign-modules.yml`** (**Module Signature Hardening**) runs its own verifier: **pushes to `dev` or `main`** execute the **Strict verify** step with **`--require-signature`** (plus **`--payload-from-filesystem --enforce-version-bump`** and **`--version-check-base`** against the push parent); **pull requests** and **`workflow_dispatch`** in that same workflow use **`--payload-from-filesystem --enforce-version-bump`** and **`--version-check-base`** **without** **`--require-signature`** on the head. After merge to **`dev`** or **`main`**, **`sign-modules`** auto-signs (non-bot pushes), strict-verifies on those pushes, and commits without **`[skip ci]`** so follow-up workflows (including **`publish-modules`**) run on the signed tip. Approval-time **`sign-modules-on-approval`** signs with `scripts/sign-modules.py` using **`--payload-from-filesystem`** among other flags; if verification fails after merge, re-sign affected **`module-package.yaml`** files and bump versions as needed. Pre-commit runs `scripts/pre-commit-verify-modules-signature.sh`: **`--require-signature`** only on branch **`main`** or when **`GITHUB_BASE_REF=main`** in Actions; otherwise the same baseline formal verify as PRs to **`dev`**. Refresh checksums locally without a private key via **`python scripts/sign-modules.py --allow-unsigned --payload-from-filesystem`** on changed manifests. On non-`main` branches, the pre-commit hook **auto-runs** that flow (`--changed-only` vs `HEAD`, then vs `HEAD~1` when needed), re-stages updated **`module-package.yaml`** files, and re-verifies. **`registry/index.json`** and published tarballs are **not** updated locally: a manifest may temporarily be **ahead** of `latest_version` until **`publish-modules`** runs on **`dev`**/**`main`** (see **`hatch run yaml-lint`** / `tools/validate_repo_manifests.py`). For rare manual registry repair only, use **`hatch run sync-registry-from-package --bundle`** with a bundle name (for example **`specfact-code-review`**); it is **not** wired into pre-commit so CI publish stays authoritative.

**CI signing:** Approved PRs to `dev` or `main` from **this repository** (not forks) run `.github/workflows/sign-modules-on-approval.yml`, which can commit signed manifests using repository secrets. See [Module signing](./docs/authoring/module-signing.md).

Expand Down
2 changes: 1 addition & 1 deletion docs/agent-rules/50-quality-gates-and-review.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ depends_on:

## Pre-commit order

1. Module signature verification via `scripts/pre-commit-verify-modules-signature.sh` (`.pre-commit-config.yaml`; `fail_fast: true` so a failing earlier hook never runs later stages). The hook adds `--require-signature` on branch `main`, or when `GITHUB_BASE_REF` is `main` (PR target in Actions).
1. Module signature verification via `scripts/pre-commit-verify-modules-signature.sh` (`.pre-commit-config.yaml`; `fail_fast: true` so a failing earlier hook never runs later stages). The hook adds `--require-signature` on branch `main`, or when `GITHUB_BASE_REF` is `main` (PR target in Actions); otherwise it runs the baseline `--payload-from-filesystem --enforce-version-bump` verifier (same formal policy as PRs targeting `dev`).
2. **Block 1** — four separate hooks (each flushes pre-commit output when it exits, so you see progress between stages): `pre-commit-quality-checks.sh block1-format` (always), `block1-yaml` when staged `*.yaml` / `*.yml`, `block1-bundle` (always), `block1-lint` when staged `*.py` / `*.pyi`.
3. **Block 2** — `pre-commit-quality-checks.sh block2` (skipped for “safe-only” staged paths): `hatch run python scripts/pre_commit_code_review.py …` on **staged paths under `packages/`, `registry/`, `scripts/`, `tools/`, `tests/`, and `openspec/changes/`** (excluding `TDD_EVIDENCE.md`), then `contract-test-status` / `hatch run contract-test`.

Expand Down
4 changes: 2 additions & 2 deletions docs/authoring/module-signing.md
Original file line number Diff line number Diff line change
Expand Up @@ -143,11 +143,11 @@ python scripts/verify-modules-signature.py --require-signature --payload-from-fi

### Signing on approval (same-repo PRs)

Workflow **`sign-modules-on-approval.yml`** runs when a review is **submitted** and **approved** on a PR whose base is **`dev`** or **`main`**, and only when the PR head is in **this** repository (`head.repo` equals the base repo). It checks out **`github.event.pull_request.head.sha`** (the commit that was approved, not the moving branch tip), uses `SPECFACT_MODULE_PRIVATE_SIGN_KEY` and `SPECFACT_MODULE_PRIVATE_SIGN_KEY_PASSPHRASE` (each validated with a named error if missing), discovers changes against the **merge-base** with the base branch (not the moving base tip alone), runs `scripts/sign-modules.py --changed-only --bump-version patch --payload-from-filesystem`, and commits results with `[skip ci]`. If `git push` is rejected because the PR branch advanced after approval, the job fails with guidance to update the branch and re-approve. **Fork PRs** are skipped (the default `GITHUB_TOKEN` cannot push to a contributor fork).
Workflow **`sign-modules-on-approval.yml`** runs when a review is **submitted** and **approved** on a PR whose base is **`dev`** or **`main`**, and only when the PR head is in **this** repository (`head.repo` equals the base repo). It checks out **`github.event.pull_request.head.sha`** (the commit that was approved, not the moving branch tip), uses `SPECFACT_MODULE_PRIVATE_SIGN_KEY` and `SPECFACT_MODULE_PRIVATE_SIGN_KEY_PASSPHRASE` (each validated with a named error if missing), discovers changes against the **merge-base** with the base branch (not the moving base tip alone), runs `scripts/sign-modules.py --changed-only --bump-version patch --payload-from-filesystem`, and commits results **without** `[skip ci]` so PR checks and downstream workflows run on the signed head. If `git push` is rejected because the PR branch advanced after approval, the job fails with guidance to update the branch and re-approve. **Fork PRs** are skipped (the default `GITHUB_TOKEN` cannot push to a contributor fork).

### Pre-commit

The first pre-commit hook runs **`scripts/pre-commit-verify-modules-signature.sh`**, which mirrors CI: **`--require-signature` on branch `main`**, or when **`GITHUB_BASE_REF=main`** in Actions pull-request contexts; otherwise checksum + version enforcement only.
The first pre-commit hook runs **`scripts/pre-commit-verify-modules-signature.sh`**, which mirrors CI: **`--require-signature` on branch `main`**, or when **`GITHUB_BASE_REF=main`** in Actions pull-request contexts; otherwise the same baseline formal verify as PRs to **`dev`** (`--payload-from-filesystem --enforce-version-bump`, no **`--require-signature`**). On failure it runs **`sign-modules.py --allow-unsigned --payload-from-filesystem`** (`--changed-only` vs **`HEAD`**, then vs **`HEAD~1`** for manifests still failing), **`git add`** those `module-package.yaml` paths, and re-verifies. It does **not** rewrite **`registry/`** (publish workflows own signed artifacts and index updates). **`yaml-lint`** allows a semver **ahead** manifest vs **`registry/index.json`** until **`publish-modules`** reconciles.

## Rotation Procedure

Expand Down
2 changes: 1 addition & 1 deletion docs/guides/ci-cd-pipeline.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ hatch run smart-test
hatch run test
```

Add `--require-signature` to the verify step when checking the same policy as **`main`** (for example before promoting work to `main`). On feature branches and for PRs targeting **`dev`**, CI does not require signatures yet; pre-commit matches that via `scripts/pre-commit-verify-modules-signature.sh`.
Add `--require-signature` to the verify step when checking the same policy as **`main`** (for example before promoting work to `main`). On feature branches and for PRs targeting **`dev`**, CI still enforces payload checksums and version bumps but does not require `integrity.signature` yet; pre-commit matches that via `scripts/pre-commit-verify-modules-signature.sh`.

Use the same order locally before pushing changes that affect docs, bundles, or registry metadata.

Expand Down
8 changes: 4 additions & 4 deletions docs/reference/module-security.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,11 +47,11 @@ Module packages carry **publisher** and **integrity** metadata so installation,
- `SPECFACT_MODULE_PRIVATE_SIGN_KEY_PASSPHRASE`
- **Verification command** (`scripts/verify-modules-signature.py`):
- **Baseline (CI)**: `--payload-from-filesystem --enforce-version-bump` — full payload checksum verification plus version-bump enforcement.
- **Dev-target PR mode**: `.github/workflows/pr-orchestrator.yml` appends `--metadata-only` for pull requests targeting `dev` so branch work is not blocked before approval-time signing refreshes manifests.
- **Strict mode**: add `--require-signature` so every manifest must include a verifiable `integrity.signature`. In `.github/workflows/pr-orchestrator.yml` this is appended for **same-repository pull requests whose base is `main`** (fork heads skip strict signature enforcement here) and for **pushes to `main`**, in addition to the baseline flags. Locally, `scripts/pre-commit-verify-modules-signature.sh` adds `--require-signature` only when the checkout (or `GITHUB_BASE_REF` in Actions) is **`main`**.
- **Local non-main hook mode**: `scripts/pre-commit-verify-modules-signature.sh` otherwise keeps the baseline command shape but adds `--metadata-only`, avoiding local checksum/signature enforcement on branches that are expected to be signed by CI.
- **Dev-target PR mode**: `.github/workflows/pr-orchestrator.yml` uses the baseline verifier for pull requests targeting `dev` (full payload checksum + version bump, **no** `--require-signature`). Cryptographic signing is applied after merge via `sign-modules` / approval workflows, not required on the PR head.
- **Strict mode**: add `--require-signature` so every manifest must include a verifiable `integrity.signature`. In `.github/workflows/pr-orchestrator.yml` this is appended for **pull requests whose base is `main`** and for **pushes to `main`**, in addition to the baseline flags. Locally, `scripts/pre-commit-verify-modules-signature.sh` adds `--require-signature` only when the checkout (or `GITHUB_BASE_REF` in Actions) is **`main`**.
- **Local non-main hook mode**: `scripts/pre-commit-verify-modules-signature.sh` otherwise runs the same baseline flags as dev-target PR CI (no `--require-signature`). Refresh `integrity.checksum` without a private key using `scripts/sign-modules.py --allow-unsigned --payload-from-filesystem`.
- **Pull request CI** also passes `--version-check-base <git-ref>` (typically `origin/<base branch>`) so version rules compare against the PR base.
- **CI uses the full verifier** for `main` and push checks (payload digest + rules above), while PRs targeting `dev` intentionally pass `--metadata-only`. The script still supports `--metadata-only` for optional tooling that only needs manifest shape and checksum format checks.
- **`--metadata-only`** remains available for optional tooling that only needs manifest shape and checksum **format** checks without hashing module trees.
- **CI signing**: Approved same-repo PRs to `dev` or `main` from trusted reviewer associations may receive automated signing commits via `sign-modules-on-approval.yml` (repository secrets; merge-base scoped `--changed-only`).

## Public key and key rotation
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,5 +53,5 @@

- [x] 7.1 Run `hatch run test` — all new and existing tests pass
- [x] 7.2 Run `hatch run format && hatch run type-check && hatch run lint` — clean
- [x] 7.3 Run `specfact code review run --json --out .specfact/code-review.json` — resolve any findings
- [x] 7.3 Run `hatch run specfact code review run --json --out .specfact/code-review.json` — resolve any findings
Comment thread
djm81 marked this conversation as resolved.
- [x] 7.4 Record passing test output in `TDD_EVIDENCE.md`
2 changes: 1 addition & 1 deletion openspec/config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ context: |
Quality & CI (typical order): `format` → `type-check` → `lint` → `yaml-lint` → module **signature verification**
(`verify-modules-signature`, enforce version bump when manifests change) → `contract-test` → `smart-test` →
`test`. Pre-commit: `pre-commit-verify-modules-signature.sh` (branch-aware `--require-signature` when target is
`main`: local branch `main` or `GITHUB_BASE_REF=main` on PR events),
`main`: local branch `main` or `GITHUB_BASE_REF=main` on PR events; otherwise baseline payload checksum + version bump),
then split `pre-commit-quality-checks.sh` hooks (format, yaml-if-staged, bundle, lint-if-staged, then block2:
`pre_commit_code_review.py` + contract tests; JSON report under `.specfact/`).
Hatch default env sets `SPECFACT_MODULES_REPO={root}`; `apply_specfact_workspace_env` also sets
Expand Down
2 changes: 1 addition & 1 deletion openspec/specs/modules-pre-commit-quality-parity/spec.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ The modules repo pre-commit configuration SHALL fail a commit when module payloa
- **THEN** the hook set includes an always-run signature verification command
- **AND** that command always enforces filesystem payload checksums and version-bump policy (`--payload-from-filesystem --enforce-version-bump`)
- **AND** when the active Git branch is `main`, or GitHub Actions sets `GITHUB_BASE_REF` to `main` (PR target branch), that command also enforces `--require-signature`
- **AND** on any other branch (for example `dev` or a feature branch), that command SHALL NOT pass `--require-signature`, matching `pr-orchestrator` behavior for non-`main` targets
- **AND** on any other branch (for example `dev` or a feature branch), that command SHALL NOT pass `--require-signature` and SHALL NOT pass `--metadata-only`, matching `pr-orchestrator` behavior for PRs whose base is not `main` (full payload checksum + version bump without cryptographic signature on the branch head)

### Requirement: Modules Repo Pre-Commit Must Catch Formatting And Quality Drift Early

Expand Down
4 changes: 2 additions & 2 deletions packages/specfact-code-review/module-package.yaml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
name: nold-ai/specfact-code-review
version: 0.47.6
version: 0.47.8
commands:
- code
tier: official
Expand All @@ -23,4 +23,4 @@ description: Official SpecFact code review bundle package.
category: codebase
bundle_group_command: code
integrity:
checksum: sha256:d48dfce318a75ea66d9a2bb2b69c7ed0c29e1228732b138719555a470e9fc47b
checksum: sha256:c612a0ca21b285e0b0b1e02480b27751de347ad23e30eb644cc5c13f1162f347
Loading
Loading