Skip to content

Commit e15a3a1

Browse files
ci: repair invalid instant-sync workflow + Scorecard enforcer (#18)
## Why Two workflow files were failing at GitHub Actions **validation** (whole-run failure, zero jobs), so they show red on every push to `main`. ## `instant-sync.yml` — invalid YAML structure The `K9-SVC Validation` block was authored as a YAML **sequence item** (`- name:` at 2-space indent) placed directly under the `jobs:` mapping. A mapping key (`dispatch:`) and a sequence entry cannot coexist at the same level, so the file does not parse and the whole workflow is invalid. **Fix:** converged onto the estate-canonical `instant-sync.yml`: - `K9-SVC Validation` is now a proper step inside the `dispatch` job. - the propagation dispatch is presence-gated via a job-level `env:` var (`env.FARM_DISPATCH_TOKEN != ''`). The `secrets` context is **not** available in `if:`, so gating on it directly would itself invalidate the workflow. ## `scorecard-enforcer.yml` — OSSF publish + run-step (HTTP 400) and an unreadable score - The `scorecard` job ran `ossf/scorecard-action` with `publish_results: true` **and** a `run:` step in the same job; OSSF attestation rejects that with HTTP 400 ("scorecard job must only have steps with `uses`"). - The gate parsed SARIF at `.runs[0].tool.driver.properties.score`, which SARIF does **not** carry → it always read `0` and failed the `< 5` check. **Fix:** split into an uses-only publish job + an unprivileged `score-gate` job that re-derives the score in **JSON** mode (top-level `.score`). Also: least-privilege `contents: read`, bounded timeouts, canonical SHA pins. (Both failure modes verified live on `nextgen-languages` scorecard-enforcer run 27468148000.) ## Verification No Julia toolchain locally; both files validated as parseable YAML (`yaml.safe_load`). These workflows trigger on push-to-`main` (not `pull_request`), so the green proof lands after merge. https://claude.ai/code/session_01VwbFNQJw23tW8tqM7utWku --- _Generated by [Claude Code](https://claude.ai/code/session_01VwbFNQJw23tW8tqM7utWku)_
2 parents 9ef3c4e + 8468350 commit e15a3a1

9 files changed

Lines changed: 853 additions & 3 deletions

File tree

.github/workflows/instant-sync.yml

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,13 +11,19 @@ on:
1111
permissions:
1212
contents: read
1313

14+
# NOTE: the `secrets` context is not available in `if:` conditionals, so the
15+
# token-presence gate routes through a job-level `env:` var and the step
16+
# tests `env.FARM_DISPATCH_TOKEN`. Referencing `secrets.*` directly in `if:`
17+
# invalidates the workflow (the run fails at validation with zero jobs).
1418
jobs:
1519
dispatch:
1620
runs-on: ubuntu-latest
1721
timeout-minutes: 15
18-
if: ${{ secrets.FARM_DISPATCH_TOKEN != '' }}
22+
env:
23+
FARM_DISPATCH_TOKEN: ${{ secrets.FARM_DISPATCH_TOKEN }}
1924
steps:
2025
- name: Trigger Propagation
26+
if: env.FARM_DISPATCH_TOKEN != ''
2127
uses: peter-evans/repository-dispatch@28959ce8df70de7be546dd1250a005dd32156697 # v3
2228
with:
2329
token: ${{ secrets.FARM_DISPATCH_TOKEN }}
@@ -33,3 +39,8 @@ jobs:
3339
3440
- name: Confirm
3541
run: echo "::notice::Propagation triggered for ${{ github.event.repository.name }}"
42+
43+
- name: K9-SVC Validation
44+
run: |
45+
echo "K9-SVC validation"
46+
[ -d .machine_readable/contractiles ] && echo "Contractiles present" || echo "No contractiles"

.github/workflows/scorecard-enforcer.yml

Lines changed: 38 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
# SPDX-License-Identifier: MPL-2.0
22
# Prevention workflow - runs OpenSSF Scorecard and fails on low scores
33
name: OpenSSF Scorecard Enforcer
4+
45
on:
56
push:
67
branches: [main]
@@ -14,9 +15,17 @@ on:
1415
concurrency:
1516
group: ${{ github.workflow }}-${{ github.ref }}
1617
cancel-in-progress: true
18+
1719
permissions:
1820
contents: read
21+
1922
jobs:
23+
# Publish job. The OSSF attestation flow requires that a job invoking
24+
# ossf/scorecard-action with publish_results: true contain ONLY `uses:`
25+
# steps (no `run:`). A `run:` step in this job makes the OSSF publish
26+
# endpoint reject the upload with HTTP 400 ("scorecard job must only have
27+
# steps with `uses`"), failing the whole workflow. Score enforcement is
28+
# therefore split into the separate unprivileged `score-gate` job below.
2029
scorecard:
2130
runs-on: ubuntu-latest
2231
timeout-minutes: 15
@@ -37,10 +46,36 @@ jobs:
3746
uses: github/codeql-action/upload-sarif@c6f931105cb2c34c8f901cc885ba1e2e259cf745 # v4
3847
with:
3948
sarif_file: results.sarif
49+
50+
# Unprivileged enforcement gate. Re-derives the score read-only (no
51+
# publish, no id-token), so it may legally contain a `run:` step.
52+
# NOTE: uses JSON output deliberately. The SARIF format does NOT carry the
53+
# aggregate score at .runs[0].tool.driver.properties.score (it is absent
54+
# there, so a SARIF-based gate reads the `// 0` fallback and ALWAYS fails
55+
# the < MIN_SCORE check); the JSON format exposes the aggregate at the
56+
# top-level `.score`. publish_results is false here and does not affect the
57+
# computed score, so enforcement behaviour is unchanged.
58+
score-gate:
59+
runs-on: ubuntu-latest
60+
timeout-minutes: 15
61+
permissions:
62+
contents: read
63+
steps:
64+
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
65+
with:
66+
persist-credentials: false
67+
68+
- name: Run Scorecard (analysis only)
69+
uses: ossf/scorecard-action@4eaacf0543bb3f2c246792bd56e8cdeffafb205a # v2.4.3
70+
with:
71+
results_file: results.json
72+
results_format: json
73+
publish_results: false
74+
4075
- name: Check minimum score
4176
run: |
42-
# Parse score from results
43-
SCORE=$(jq -r '.runs[0].tool.driver.properties.score // 0' results.sarif 2>/dev/null || echo "0")
77+
# JSON output carries the aggregate score at the top-level `.score`.
78+
SCORE=$(jq -r '.score // 0' results.json 2>/dev/null || echo "0")
4479
4580
echo "OpenSSF Scorecard Score: $SCORE"
4681
@@ -57,6 +92,7 @@ jobs:
5792
timeout-minutes: 15
5893
steps:
5994
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
95+
6096
- name: Check SECURITY.md exists
6197
run: |
6298
if [ ! -f "SECURITY.md" ]; then
Lines changed: 179 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,179 @@
1+
// SPDX-License-Identifier: MPL-2.0
2+
// Copyright (c) Jonathan D.A. Jewell <j.d.a.jewell@open.ac.uk>
3+
= K9 Contractiles
4+
:toc: left
5+
:icons: font
6+
7+
== What Are K9 Contractiles?
8+
9+
K9 contractiles are self-validating components that combine configuration, validation, and deployment logic in a single file format. They implement the RSR principle of "self-describing artifacts" by embedding contracts and orchestration directly in the component.
10+
11+
== The Three Security Levels
12+
13+
K9 components declare their trust requirements using "The Leash" security model:
14+
15+
[horizontal]
16+
`'Kennel`:: Pure data, no execution (safest)
17+
`'Yard`:: Nickel evaluation with contracts (medium trust)
18+
`'Hunt`:: Full execution with Just recipes (requires signature)
19+
20+
== Example Components
21+
22+
This directory contains example K9 contractiles for common repository tasks:
23+
24+
=== Kennel Level (Pure Data)
25+
26+
**File:** `examples/project-metadata.k9.ncl`
27+
28+
Pure configuration data with no execution. Safe to include in any repository.
29+
30+
**Use cases:**
31+
- Project metadata (name, version, description)
32+
- Build configuration
33+
- Tool settings
34+
- Data schemas
35+
36+
**Security:** No signature required, data-only.
37+
38+
=== Yard Level (Validated Config)
39+
40+
**File:** `examples/ci-config.k9.ncl`
41+
42+
Configuration with Nickel contracts for runtime validation. Evaluated safely without I/O.
43+
44+
**Use cases:**
45+
- CI/CD configuration with validation
46+
- Deployment parameters
47+
- Database schemas with constraints
48+
- API specifications
49+
50+
**Security:** Signature recommended, Nickel evaluation only.
51+
52+
=== Hunt Level (Full Execution)
53+
54+
**File:** `examples/setup-repo.k9.ncl`
55+
56+
Full execution with Just recipes. Can run shell commands and modify filesystem.
57+
58+
**Use cases:**
59+
- Repository setup scripts
60+
- Deployment automation
61+
- System configuration
62+
- Package installation
63+
64+
**Security:** **Signature required**, full system access.
65+
66+
== Usage in Your Repository
67+
68+
=== 1. Create K9 Components
69+
70+
Choose the appropriate security level for your use case:
71+
72+
[source,bash]
73+
----
74+
# Kennel: Pure configuration
75+
cp contractiles/k9/examples/project-metadata.k9.ncl config/metadata.k9.ncl
76+
77+
# Yard: Validated configuration
78+
cp contractiles/k9/examples/ci-config.k9.ncl .github/ci.k9.ncl
79+
80+
# Hunt: Full automation
81+
cp contractiles/k9/examples/setup-repo.k9.ncl scripts/setup.k9.ncl
82+
----
83+
84+
=== 2. Validate Components
85+
86+
[source,bash]
87+
----
88+
# Validate Nickel syntax and contracts
89+
nickel typecheck config/metadata.k9.ncl
90+
91+
# Verify Hunt-level signature (if signed)
92+
./must verify scripts/setup.k9.ncl
93+
----
94+
95+
=== 3. Execute Components
96+
97+
[source,bash]
98+
----
99+
# Kennel: Export as JSON
100+
nickel export config/metadata.k9.ncl > metadata.json
101+
102+
# Yard: Evaluate with validation
103+
nickel eval .github/ci.k9.ncl
104+
105+
# Hunt: Run with Just (dry-run first!)
106+
./must --dry-run run scripts/setup.k9.ncl
107+
./must run scripts/setup.k9.ncl
108+
----
109+
110+
== Integration with RSR
111+
112+
K9 contractiles integrate with other RSR standards:
113+
114+
**STATE.scm**:: K9 components can generate or validate STATE.scm
115+
**ECOSYSTEM.scm**:: K9 can automate cross-repo operations
116+
**META.scm**:: K9 can enforce architectural decisions
117+
118+
== Security Best Practices
119+
120+
=== For Kennel/Yard Components
121+
122+
✅ **Safe to use without signatures** +
123+
✅ **Review Nickel code before use** +
124+
✅ **Validate contracts match expectations**
125+
126+
=== For Hunt Components
127+
128+
⚠️ **ALWAYS verify signatures** +
129+
⚠️ **Review Just recipes carefully** +
130+
⚠️ **Run dry-run mode first** +
131+
⚠️ **Never run as root unless required** +
132+
⚠️ **Sandbox external components**
133+
134+
**See:** https://github.com/hyperpolymath/standards/blob/main/k9-svc/docs/SECURITY-BEST-PRACTICES.adoc
135+
136+
== Template Files
137+
138+
Use these as starting points for your own K9 components:
139+
140+
- `template-kennel.k9.ncl` - Pure data template
141+
- `template-yard.k9.ncl` - Validated config template
142+
- `template-hunt.k9.ncl` - Full execution template
143+
144+
== Dependencies
145+
146+
To use K9 contractiles in your repository:
147+
148+
[source,bash]
149+
----
150+
# Install Nickel (configuration language)
151+
curl -L https://github.com/tweag/nickel/releases/latest/download/nickel-linux-x86_64 -o nickel
152+
chmod +x nickel && sudo mv nickel /usr/local/bin/
153+
154+
# Install Just (task runner, for Hunt level)
155+
cargo install just
156+
157+
# Clone K9-SVC (for must shim and tooling)
158+
git clone https://github.com/hyperpolymath/standards.git
159+
# Note: K9-SVC is located in standards/k9-svc
160+
----
161+
162+
== Learn More
163+
164+
- **K9-SVC Specification:** https://github.com/hyperpolymath/standards/blob/main/k9-svc/SPEC.adoc
165+
- **K9 User Guide:** https://github.com/hyperpolymath/standards/blob/main/k9-svc/GUIDE.adoc
166+
- **Security Documentation:** https://github.com/hyperpolymath/standards/blob/main/k9-svc/docs/SECURITY-FAQ.adoc
167+
- **IANA Media Type:** `application/vnd.k9+nickel`
168+
169+
== Contributing
170+
171+
When adding K9 contractiles to your repository:
172+
173+
1. Use appropriate security level (Kennel > Yard > Hunt)
174+
2. Document what each component does
175+
3. Include validation contracts in Yard/Hunt components
176+
4. Sign Hunt-level components before committing
177+
5. Add K9 validation to CI/CD pipeline
178+
179+
**Questions?** Open an issue on https://github.com/hyperpolymath/standards/tree/main/k9-svc

0 commit comments

Comments
 (0)