Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
3171095
Add change for code-review
djm81 Apr 8, 2026
9601c2a
Modify openspec change for code review improvements
djm81 Apr 14, 2026
c6c2d05
Add source tracking
djm81 Apr 14, 2026
6db629a
feat: code review bug-hunt, sidecar venv skip, registry 0.41.5/0.47.0
djm81 Apr 14, 2026
5dcf4b8
Add sign fixes
djm81 Apr 14, 2026
a2590cb
fix tests and logic
djm81 Apr 14, 2026
cc56394
Merge pull request #191 from nold-ai/feature/code-review-bug-finding-…
djm81 Apr 14, 2026
cd14825
chore(registry): publish changed modules [skip ci]
github-actions[bot] Apr 14, 2026
8ca7c17
Merge pull request #192 from nold-ai/auto/publish-dev-24427260705
djm81 Apr 14, 2026
6765811
Fix review findings and signature
djm81 Apr 14, 2026
37ac44b
chore(registry): publish changed modules [skip ci]
github-actions[bot] Apr 14, 2026
4628a65
Merge pull request #194 from nold-ai/auto/publish-dev-24427914086
djm81 Apr 15, 2026
9458eeb
fix PR 193 review findings
djm81 Apr 15, 2026
e971c8d
fix: address PR 193 review follow-ups (icontract gate, CI hook, manif…
djm81 Apr 15, 2026
d06048a
fix(code-review): repo-relative package roots and stub icontract scans
djm81 Apr 15, 2026
71cabbe
fix(code-review): strict repo-relative packages/ roots and .pyi ancho…
djm81 Apr 15, 2026
e7174c9
Merge pull request #195 from nold-ai/bugfix/pr-193-review-comments
djm81 Apr 15, 2026
e469301
chore(registry): publish changed modules [skip ci]
github-actions[bot] Apr 15, 2026
97bf40d
Merge pull request #196 from nold-ai/auto/publish-dev-24445439408
djm81 Apr 15, 2026
b2a39f4
ci(modules): auto-sign on dev/main pushes and sign before registry pu…
djm81 Apr 15, 2026
c516516
Merge branch 'dev' of https://github.com/nold-ai/specfact-cli-modules…
djm81 Apr 15, 2026
df8135a
fix: address review follow-ups (focus flags, PR signing, registry che…
djm81 Apr 15, 2026
36f79f9
Fix sign flow dependencies
djm81 Apr 15, 2026
80f3e05
Fix module sign check and remediation
djm81 Apr 15, 2026
509baa3
Fix module sign check and remediation
djm81 Apr 15, 2026
3e09007
Merge pull request #197 from nold-ai/bugfix/review-findings-followup
djm81 Apr 15, 2026
9aacfcc
add missing dependencies
djm81 Apr 15, 2026
3f497c5
chore(registry): publish changed modules [skip ci]
github-actions[bot] Apr 15, 2026
3c927fc
Merge pull request #198 from nold-ai/auto/publish-dev-24451958492
djm81 Apr 15, 2026
f7b3297
Fix sign process and publish logic
djm81 Apr 15, 2026
83e4476
Merge branch 'dev' of https://github.com/nold-ai/specfact-cli-modules…
djm81 Apr 15, 2026
cc0911a
Fix module sign logic
djm81 Apr 15, 2026
f1c5db9
Fix module sign process
djm81 Apr 15, 2026
626c138
chore(modules): auto-sign module manifests
github-actions[bot] Apr 15, 2026
944ebba
Merge pull request #199 from nold-ai/auto/sign-dev-24453227593
djm81 Apr 15, 2026
67696e5
chore(modules): auto-sign module manifests
github-actions[bot] Apr 15, 2026
c64bfb3
Merge pull request #200 from nold-ai/auto/sign-dev-24453275014
djm81 Apr 15, 2026
5c3e0a6
chore(registry): publish changed modules [skip ci]
github-actions[bot] Apr 15, 2026
5f48bb7
Merge pull request #201 from nold-ai/auto/publish-dev-24453598377
djm81 Apr 15, 2026
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
28 changes: 12 additions & 16 deletions .github/workflows/pr-orchestrator.yml
Original file line number Diff line number Diff line change
Expand Up @@ -80,29 +80,25 @@ jobs:
- name: Verify bundled module signatures and version bumps
run: |
set -euo pipefail
TARGET_BRANCH=""
if [ "${{ github.event_name }}" = "pull_request" ]; then
TARGET_BRANCH="${{ github.event.pull_request.base.ref }}"
else
TARGET_BRANCH="${GITHUB_REF#refs/heads/}"
fi

BASE_REF=""
if [ "${{ github.event_name }}" = "pull_request" ]; then
BASE_REF="origin/${{ github.event.pull_request.base.ref }}"
fi

if [ -z "${SPECFACT_MODULE_PUBLIC_SIGN_KEY:-}" ] && [ -z "${SPECFACT_MODULE_SIGNING_PUBLIC_KEY_PEM:-}" ]; then
echo "warning: no public signing key secret set; verifier must resolve key from repo/default path"
fi

VERIFY_CMD=(python scripts/verify-modules-signature.py --payload-from-filesystem --enforce-version-bump)
if [ "$TARGET_BRANCH" = "main" ]; then
VERIFY_CMD+=(--require-signature)
fi
if [ -n "$BASE_REF" ]; then

if [ "${{ github.event_name }}" = "pull_request" ]; then
BASE_REF="origin/${{ github.event.pull_request.base.ref }}"
TARGET_BRANCH="${{ github.event.pull_request.base.ref }}"
VERIFY_CMD+=(--version-check-base "$BASE_REF")
if [ "$TARGET_BRANCH" = "main" ]; then
VERIFY_CMD+=(--require-signature)
Comment thread
djm81 marked this conversation as resolved.
fi
else
if [ "${{ github.ref_name }}" = "main" ]; then
VERIFY_CMD+=(--require-signature)
fi
fi

"${VERIFY_CMD[@]}"

quality:
Expand Down
77 changes: 72 additions & 5 deletions .github/workflows/publish-modules.yml
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,10 @@ 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 }}
SPECFACT_MODULE_PRIVATE_SIGN_KEY_PASSPHRASE: ${{ secrets.SPECFACT_MODULE_PRIVATE_SIGN_KEY_PASSPHRASE }}
steps:
- name: Checkout
uses: actions/checkout@v4
Expand All @@ -42,7 +44,9 @@ jobs:
- name: Install publish dependencies
run: |
python -m pip install --upgrade pip
python -m pip install pyyaml packaging
# cryptography/cffi: required when the publish step invokes scripts/sign-modules.py
# to add integrity.signature before packaging (same stack as sign-modules.yml).
python -m pip install pyyaml packaging cryptography cffi

- name: Resolve publish bundle set
id: bundles
Expand Down Expand Up @@ -197,6 +201,37 @@ jobs:
ignored_dir_names = {".git", "tests", "__pycache__", ".pytest_cache", ".mypy_cache", ".ruff_cache", "logs"}
ignored_suffixes = {".pyc", ".pyo"}

def manifest_has_signature(data: dict) -> bool:
integrity_obj = data.get("integrity")
if not isinstance(integrity_obj, dict):
return False
return bool(str(integrity_obj.get("signature") or "").strip())

signing_key = os.environ.get("SPECFACT_MODULE_PRIVATE_SIGN_KEY", "").strip()

def sign_manifest_if_unsigned(manifest_path: Path, *, reason: str) -> None:
if not signing_key:
return
if not manifest_path.is_file():
return
raw = yaml.safe_load(manifest_path.read_text(encoding="utf-8"))
if not isinstance(raw, dict):
return
if manifest_has_signature(raw):
return
print(f"Signing {manifest_path} ({reason}).", flush=True)
subprocess.run(
[
"python",
"scripts/sign-modules.py",
"--payload-from-filesystem",
"--allow-same-version",
str(manifest_path),
],
cwd=str(repo_root),
check=True,
)

skipped_bundles: list[str] = []
current_branch = os.environ.get("GITHUB_REF_NAME", "").strip()
baseline_ref = determine_registry_baseline_ref(
Expand Down Expand Up @@ -260,7 +295,24 @@ jobs:

manifest = yaml.safe_load(manifest_path.read_text(encoding="utf-8"))
if not isinstance(manifest, dict):
raise ValueError(f"Invalid manifest content: {manifest_path}")
raise ValueError(f"Invalid manifest content: {manifest_path}")

if not manifest_has_signature(manifest):
sign_manifest_if_unsigned(
manifest_path,
reason="missing integrity.signature before registry packaging",
)
manifest = yaml.safe_load(manifest_path.read_text(encoding="utf-8"))
if not isinstance(manifest, dict):
raise ValueError(f"Invalid manifest content after signing: {manifest_path}")
if signing_key and not manifest_has_signature(manifest):
raise ValueError(f"Signing did not produce integrity.signature: {manifest_path}")
if not signing_key:
print(
f"::warning::Publishing {bundle} with checksum-only tree manifest "
"(SPECFACT_MODULE_PRIVATE_SIGN_KEY unset).",
flush=True,
)

module_id = str(manifest.get("name") or f"nold-ai/{bundle}")
version = str(manifest.get("version") or "").strip()
Expand Down Expand Up @@ -320,6 +372,18 @@ jobs:
if skipped_bundles:
print(f"Skipped already-published bundles: {skipped_bundles}")

for bundle in skipped_bundles:
sign_manifest_if_unsigned(
repo_root / "packages" / bundle / "module-package.yaml",
reason="registry version already published; still align git manifest signature",
)

for manifest_path in sorted((repo_root / "packages").glob("*/module-package.yaml")):
sign_manifest_if_unsigned(
manifest_path,
reason="final pass: ensure no unsigned module-package.yaml remains before commit",
)

registry_index_path.write_text(json.dumps(registry, indent=2) + "\n", encoding="utf-8")
PY

Expand All @@ -330,8 +394,8 @@ jobs:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
BUNDLE_REASONS_JSON: ${{ steps.bundles.outputs.bundle_reasons_json }}
run: |
if git diff --quiet -- registry/index.json registry/modules registry/signatures; then
echo "No registry changes to commit."
if git diff --quiet -- registry/index.json registry/modules registry/signatures && git diff --quiet -- packages/; then
echo "No registry or signed package manifest changes to commit."
exit 0
fi

Expand All @@ -342,7 +406,10 @@ jobs:
PUBLISH_BRANCH="auto/publish-${TARGET_BRANCH}-${GITHUB_RUN_ID}"
git checkout -b "${PUBLISH_BRANCH}"

# Registry artifacts plus any module-package.yaml updates from in-workflow signing
# (otherwise dev→main PRs fail verify-modules-signature --require-signature).
git add registry/index.json registry/modules registry/signatures
git add -u packages/
git commit -m "chore(registry): publish changed modules [skip ci]"

if [ "${DRY_RUN:-false}" = "true" ]; then
Expand Down
65 changes: 54 additions & 11 deletions .github/workflows/sign-modules-on-approval.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,18 +14,50 @@ permissions:

jobs:
sign-modules:
if: >-
github.event.review.state == 'approved' &&
(github.event.pull_request.base.ref == 'dev' || github.event.pull_request.base.ref == 'main') &&
github.event.pull_request.head.repo.full_name == github.repository
runs-on: ubuntu-latest
env:
SPECFACT_MODULE_PRIVATE_SIGN_KEY: ${{ secrets.SPECFACT_MODULE_PRIVATE_SIGN_KEY }}
SPECFACT_MODULE_PRIVATE_SIGN_KEY_PASSPHRASE: ${{ secrets.SPECFACT_MODULE_PRIVATE_SIGN_KEY_PASSPHRASE }}
PR_BASE_REF: ${{ github.event.pull_request.base.ref }}
PR_HEAD_REF: ${{ github.event.pull_request.head.ref }}
steps:
- name: Eligibility gate (required status check)
id: gate
run: |
set -euo pipefail
if [ "${{ github.event.review.state }}" != "approved" ]; then
echo "sign=false" >> "$GITHUB_OUTPUT"
echo "::notice::Skipping module signing: review state is not approved."
exit 0
fi
author_association="${{ github.event.review.user.author_association }}"
case "$author_association" in
COLLABORATOR|MEMBER|OWNER)
;;
*)
echo "sign=false" >> "$GITHUB_OUTPUT"
echo "::notice::Skipping module signing: reviewer association '${author_association}' is not trusted for signing."
exit 0
;;
esac
base_ref="${{ github.event.pull_request.base.ref }}"
if [ "$base_ref" != "dev" ] && [ "$base_ref" != "main" ]; then
echo "sign=false" >> "$GITHUB_OUTPUT"
echo "::notice::Skipping module signing: base branch is not dev or main."
exit 0
fi
head_repo="${{ github.event.pull_request.head.repo.full_name }}"
this_repo="${{ github.repository }}"
if [ "$head_repo" != "$this_repo" ]; then
echo "sign=false" >> "$GITHUB_OUTPUT"
echo "::notice::Skipping module signing: fork PR (head repo differs from target repo)."
exit 0
fi
echo "sign=true" >> "$GITHUB_OUTPUT"
echo "Eligible for module signing (approved by trusted reviewer, same-repo PR to dev or main)."

- name: Guard signing secrets
if: steps.gate.outputs.sign == 'true'
run: |
set -euo pipefail
if [ -z "${SPECFACT_MODULE_PRIVATE_SIGN_KEY:-}" ]; then
Expand All @@ -38,21 +70,25 @@ jobs:
fi

- uses: actions/checkout@v4
if: steps.gate.outputs.sign == 'true'
with:
ref: ${{ github.event.pull_request.head.sha }}
fetch-depth: 0

- name: Set up Python 3.12
if: steps.gate.outputs.sign == 'true'
uses: actions/setup-python@v5
with:
python-version: "3.12"

- name: Install signing dependencies
if: steps.gate.outputs.sign == 'true'
run: |
python -m pip install --upgrade pip
python -m pip install pyyaml beartype icontract cryptography cffi

- name: Discover module manifests
if: steps.gate.outputs.sign == 'true'
id: discover
run: |
set -euo pipefail
Expand All @@ -61,6 +97,7 @@ jobs:
echo "Discovered ${#MANIFESTS[@]} module-package.yaml file(s) under packages/"

- name: Sign changed module manifests
if: steps.gate.outputs.sign == 'true'
id: sign
run: |
set -euo pipefail
Expand All @@ -73,6 +110,7 @@ jobs:
--payload-from-filesystem

- name: Commit and push signed manifests
if: steps.gate.outputs.sign == 'true'
id: commit
run: |
set -euo pipefail
Expand All @@ -84,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 All @@ -94,15 +132,20 @@ jobs:
- name: Write job summary
if: always()
env:
COMMIT_CHANGED: ${{ steps.commit.outputs.changed }}
MANIFESTS_COUNT: ${{ steps.discover.outputs.manifests_count }}
GATE_SIGN: ${{ steps.gate.outputs.sign }}
COMMIT_CHANGED: ${{ steps.commit.outputs.changed || '' }}
MANIFESTS_COUNT: ${{ steps.discover.outputs.manifests_count || '' }}
run: |
{
echo "### Module signing (CI approval)"
echo "Manifests discovered under \`packages/\`: ${MANIFESTS_COUNT:-unknown}"
if [ "${COMMIT_CHANGED}" = "true" ]; then
echo "Committed signed manifest updates to ${PR_HEAD_REF}."
if [ "${GATE_SIGN}" != "true" ]; then
echo "Signing skipped (eligibility gate: not approved, wrong base branch, or fork PR)."
else
echo "No changes detected (manifests already signed or no module changes on this PR vs merge-base)."
echo "Manifests discovered under \`packages/\`: ${MANIFESTS_COUNT:-unknown}"
if [ "${COMMIT_CHANGED}" = "true" ]; then
echo "Committed signed manifest updates to ${PR_HEAD_REF}."
else
echo "No changes detected (manifests already signed or no module changes on this PR vs merge-base)."
fi
fi
} >> "$GITHUB_STEP_SUMMARY"
Loading
Loading