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
31 changes: 27 additions & 4 deletions .githooks/validate-k9.sh
Original file line number Diff line number Diff line change
Expand Up @@ -120,15 +120,30 @@
# per-file verdicts coming from a single process rather than N slightly
# different environments.
REPORT="$(mktemp)"
trap 'rm -f "$REPORT"' EXIT
STRICT_ARGS=()
case "$LAYER" in L2|L3|all) STRICT_ARGS=(--strict) ;; esac

Check failure on line 125 in .githooks/validate-k9.sh

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Add a default case (*) to handle unexpected values.

See more on https://sonarcloud.io/project/issues?id=hyperpolymath_standards&issues=AaFKjYUgiuOCX-cJ_jhJ&open=AaFKjYUgiuOCX-cJ_jhJ&pullRequest=1273
set +e
bash "$VALIDATOR" --layer "$LAYER" --json --quiet "${FILES[@]}" > "$REPORT" 2>/dev/null
bash "$VALIDATOR" "${STRICT_ARGS[@]}" --layer "$LAYER" --json --quiet "${FILES[@]}" > "$REPORT"
VALIDATOR_RC=$?
set -e
if [ -n "${K9_JSON_REPORT:-}" ]; then cp "$REPORT" "$K9_JSON_REPORT"; fi

Check failure on line 130 in .githooks/validate-k9.sh

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Use '[[' instead of '[' for conditional tests. The '[[' construct is safer and more feature-rich.

See more on https://sonarcloud.io/project/issues?id=hyperpolymath_standards&issues=AaFKjYUgiuOCX-cJ_jhK&open=AaFKjYUgiuOCX-cJ_jhK&pullRequest=1273
RESULTS=$(grep -cE '"verdict":"(pass|fail|error|routed)"' "$REPORT" || true)
if [ "$RESULTS" -ne "${#FILES[@]}" ]; then

Check failure on line 132 in .githooks/validate-k9.sh

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Use '[[' instead of '[' for conditional tests. The '[[' construct is safer and more feature-rich.

See more on https://sonarcloud.io/project/issues?id=hyperpolymath_standards&issues=AaFKjYUgiuOCX-cJ_jhL&open=AaFKjYUgiuOCX-cJ_jhL&pullRequest=1273
echo "[validate-k9] ERROR: expected ${#FILES[@]} file results, received $RESULTS (validator exit $VALIDATOR_RC)" >&2
exit 2
fi
case "$VALIDATOR_RC" in 0|1) ;; *)
echo "[validate-k9] ERROR: validator incomplete or unavailable (exit $VALIDATOR_RC)" >&2
exit "$VALIDATOR_RC" ;;
esac

CHANGED="$STAGED_FILES"
CHANGED="${K9_CHANGED_FILES:-$STAGED_FILES}"
HARD=0
ADVISORY=0
CONFORMING=0
STALE=0
ROUTED=0

while IFS= read -r line; do
[ -z "$line" ] && continue
Expand All @@ -140,7 +155,10 @@
rules="$(printf '%s' "$line" | grep -oE '"rule":"K9-[ESNC][0-9]+"' | sed 's/.*:"//; s/"//' | sort -u | tr '\n' ' ' | sed 's/ $//' || true)"

case "$verdict" in
pass|routed)
routed)
ROUTED=$((ROUTED + 1))
continue ;;
pass)
CONFORMING=$((CONFORMING + 1))
if in_ledger "$file"; then
# A conforming file must not keep its exemption: that is how a ledger
Expand All @@ -166,13 +184,18 @@
done < "$REPORT"
rm -f "$REPORT"

if [ "$VALIDATOR_RC" -ne 0 ] && [ "$HARD" -eq 0 ] && [ "$ADVISORY" -eq 0 ]; then

Check failure on line 187 in .githooks/validate-k9.sh

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Use '[[' instead of '[' for conditional tests. The '[[' construct is safer and more feature-rich.

See more on https://sonarcloud.io/project/issues?id=hyperpolymath_standards&issues=AaFKjYUgiuOCX-cJ_jhM&open=AaFKjYUgiuOCX-cJ_jhM&pullRequest=1273

Check failure on line 187 in .githooks/validate-k9.sh

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Use '[[' instead of '[' for conditional tests. The '[[' construct is safer and more feature-rich.

See more on https://sonarcloud.io/project/issues?id=hyperpolymath_standards&issues=AaFKjYUgiuOCX-cJ_jhN&open=AaFKjYUgiuOCX-cJ_jhN&pullRequest=1273

Check failure on line 187 in .githooks/validate-k9.sh

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Use '[[' instead of '[' for conditional tests. The '[[' construct is safer and more feature-rich.

See more on https://sonarcloud.io/project/issues?id=hyperpolymath_standards&issues=AaFKjYUgiuOCX-cJ_jhO&open=AaFKjYUgiuOCX-cJ_jhO&pullRequest=1273
echo "[validate-k9] ERROR: validator failed without attributed violations" >&2
exit 2
fi

if [ "$STALE" -gt 0 ] || [ "$HARD" -gt 0 ]; then
echo -e "${RED}[validate-k9] $HARD violation(s), $STALE stale ledger entr(ies)${NC}" >&2
echo -e "${RED}[validate-k9] spec: 1-formats/k9/spec/K9-CONTRACT-SPEC.adoc · plan: 1-formats/k9/spec/MIGRATION-1058.adoc${NC}" >&2
exit 1
fi

echo -e "${GRN}[validate-k9]${NC} $CONFORMING conforming, $ADVISORY grandfathered (layer $LAYER, contract v1.0.0)"
echo -e "${GRN}[validate-k9]${NC} $CONFORMING conforming, $ADVISORY grandfathered, $ROUTED routed out of scope (layer $LAYER, contract v1.0.0)"
if [ "$LAYER" = "L0" ] || [ "$LAYER" = "L1" ]; then
# Say out loud what was NOT checked. A lexical pass is not a conformance
# result, and the difference is the whole point of §12.
Expand Down
2 changes: 2 additions & 0 deletions .github/workflows/actions.lock
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,8 @@ workflows:
- 'oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6'
'.github/workflows/k9-contractile.yml':
- 'actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1'
- 'actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9'
- 'oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6'
'.github/workflows/label-triage.yml': []
'.github/workflows/labels.yml': []
'.github/workflows/launcher-standard-lockstep.yml':
Expand Down
180 changes: 49 additions & 131 deletions .github/workflows/k9-contractile.yml
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,10 @@ jobs:
steps:
- name: Checkout
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
fetch-depth: 0
ref: ${{ github.event.pull_request.head.sha || github.sha }}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Validate the merge result instead of only the PR head

When the target branch has advanced since the PR branch was created, checking out pull_request.head.sha makes every structural and conformance check run against the contributor's standalone head rather than GitHub's synthetic merge commit. A PR can therefore receive the required green check even though the tree that would be merged with the current base violates these checks; retain the merge checkout for validation and pass the head SHA separately for report identity and changed-file calculations.

Useful? React with 👍 / 👎.

persist-credentials: false

- name: K9-SVC Validation
run: |
Expand Down Expand Up @@ -119,147 +123,61 @@ jobs:
echo "${NICKEL_SHA256} ${RUNNER_TEMP}/nickel" | sha256sum -c -
chmod +x "$RUNNER_TEMP/nickel"
echo "$RUNNER_TEMP" >> "$GITHUB_PATH"
echo "K9_REPORT=$RUNNER_TEMP/k9-report.md" >> "$GITHUB_ENV"

# Each step mirrors its output into $GITHUB_STEP_SUMMARY as well as
# stdout. The PR then shows the verdict without anyone opening a raw log,
# and `gh api repos/{o}/{r}/check-runs/{job_id} --jq .output.summary`
# reads it back — which matters on a machine that cannot reach the log
# blob host. `set -uo pipefail` without `-e` so a failing validator still
# reaches the tee and its own status is what the step exits with.
- name: K9 normative contract typecheck
run: |
set -uo pipefail
# Its own step, ahead of the fixtures. `nickel typecheck` stops at the
# first error, and when the contract is broken every fixture verdict
# downstream is void — reported as five "non-conforming" positive
# controls rather than one broken contract. Twice now that cost a run
# to diagnose (`Record`, then `Any`: neither is a Nickel type).
set +e
out="$(nickel typecheck 1-formats/k9/spec/contract/k9_contract.ncl 2>&1)"; rc=$?
set -e
{
echo '## K9 normative contract typecheck'
echo '```'
[ $rc -eq 0 ] && echo 'k9_contract.ncl typechecks' || printf '%s\n' "$out"
echo '```'
} | tee -a "$GITHUB_STEP_SUMMARY" "$K9_REPORT"
if [ $rc -ne 0 ]; then
printf '%s\n' "$out" | head -20 | while IFS= read -r line; do
esc="$(printf '%s' "$line" | sed 's/%/%25/g' | tr -d '\r\n')"
[ -n "$esc" ] && printf '::error title=k9_contract.ncl does not typecheck::%s\n' "$esc"
done
fi
exit $rc
- name: Install reporting runtime
if: ${{ always() }}
uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2.2.0
Comment on lines +127 to +129

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Pin the Bun version used by the required gate

When setup-bun changes its default release, this required workflow silently begins running both the reporting regression tests and the DEED validator under a different runtime, so an upstream Bun release can break or alter conformance results without any repository change; it also changes the recorded profile and discards otherwise comparable baselines. The repository's other runtime lanes explicitly pin Bun 1.4.2, so this setup step should likewise specify a reviewed bun-version rather than following the action's moving default.

Useful? React with 👍 / 👎.


- name: K9 contract self-test
run: |
set -uo pipefail
# Asserts the validator's bash mirrors still agree with the normative
# contract, the capability arithmetic, the structural extractor, and
# the envelope strip. Runs before the fixtures so a broken validator
# is reported as a broken validator rather than as a broken corpus.
{
echo '## K9 contract self-test'
echo '```'
bash 1-formats/k9/tools/k9-validate.sh --self-test 2>&1
echo '```'
} | tee -a "$GITHUB_STEP_SUMMARY" "$K9_REPORT"
exit "${PIPESTATUS[0]}"
- name: Reporting regression tests
run: bun test 1-formats/k9/tools/conformance-report.test.mjs

- name: K9 conformance fixtures (positive AND negative controls)
- name: Read previous compact report
if: ${{ always() && github.event_name == 'pull_request' }}
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
PR_NUMBER: ${{ github.event.pull_request.number }}
run: |
set -uo pipefail
# --strict: with Nickel on PATH every format layer (L0-L2) must
# actually run, so a missing toolchain cannot report a pass. The 21
# negative controls are the load-bearing half — a validator that
# accepts everything satisfies the positive half trivially.
set +e
out="$(bash 1-formats/k9/tools/k9-validate.sh --strict \
--fixtures 1-formats/k9/tools/fixtures 2>&1)"; rc=$?
set -e
{
echo '## K9 conformance fixtures'
echo '```'
printf '%s\n' "$out"
echo '```'
} | tee -a "$GITHUB_STEP_SUMMARY" "$K9_REPORT"
# A failing gate also raises a check-run annotation. Annotations are
# readable through `gh api repos/{o}/{r}/check-runs/{job}/annotations`
# from machines that cannot reach the log blob host, which is where
# the first two runs of this workflow had to be diagnosed from.
# Workflow-command escaping: % first, then CR and LF.
if [ $rc -ne 0 ]; then
# One annotation per failure, not one multi-kilobyte annotation: a
# whole-report message never reached the check-run API, and a
# per-failure annotation is what a reader wants anyway.
printf '%s\n' "$out" | grep -E '^(FAIL|ERROR)' | head -20 | while IFS= read -r line; do
esc="$(printf '%s' "$line" | sed 's/%/%25/g' | tr -d '\r\n')"
printf '::error title=K9 conformance fixture::%s\n' "$esc"
done
fi
exit $rc
set -euo pipefail
gh api --paginate --slurp "repos/$GITHUB_REPOSITORY/issues/$PR_NUMBER/comments" \
| jq -r '[.[][] | select(.user.login == "github-actions[bot]" and (.body | contains("<!-- k9-contractile-report:")))] | last | .body // ""' \
> "$RUNNER_TEMP/k9-previous.md"
Comment on lines +141 to +143

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Keep an optional baseline lookup from failing the gate

If the comments API is temporarily unavailable, rate-limited, or denied, set -euo pipefail makes this newly added lookup step fail the required job even though the previous report is optional and the renderer already supports a missing baseline as no comparable baseline. The evidence collector still runs because of always(), so its report can be green while the required check remains red solely because comparison history could not be fetched; handle this lookup failure by writing an empty baseline and continuing.

Useful? React with 👍 / 👎.


- name: K9 corpus conformance (ratcheted, L2)
- name: Collect DEED and K9 evidence
if: ${{ always() }}
env:
# MIGRATION-1058 M7's first step. The hook's own default stays L1: a
# pre-commit hook that fails because a laptop lacks a toolchain
# teaches people to pass --no-verify, which is how a gate gets
# deleted. CI has the pinned binary, so here the corpus is checked at
# L2 as well. The six contractiles and the two axel config files that
# M1/M4 fixed had never been evaluated against K9.Component, and the
# closed record contracts in k9_contract.ncl rejected exactly the
# extension fields §5.5 permits. Ledger entries stay advisory, so the
# four svc templates and the rsr checklist are reported, not failed,
# until their own items land.
K9_VALIDATE_LAYER: all
PR_NUMBER: ${{ github.event.pull_request.number || 'local' }}
K9_BASE_SHA: ${{ github.event.pull_request.base.sha }}
K9_HEAD_SHA: ${{ github.event.pull_request.head.sha || github.sha }}
K9_PREVIOUS_REPORT: ${{ runner.temp }}/k9-previous.md
run: |
set -uo pipefail
# The whole tracked corpus, through the same gate the pre-commit hook
# uses, so CI and the hook cannot disagree about what a K9 file is.
# .machine_readable/k9-contract-debt.txt grandfathers the files
# that predate the contract; it is shrink-only (a conforming file
# left in it fails), and it does not protect a file this PR touches.
set +e
out="$(bash .githooks/validate-k9.sh 2>&1)"; rc=$?
set -e
{
echo '## K9 corpus conformance (L2)'
echo '```'
printf '%s\n' "$out"
echo '```'
} | tee -a "$GITHUB_STEP_SUMMARY" "$K9_REPORT"
if [ $rc -ne 0 ]; then
printf '%s\n' "$out" | grep -E '^(FAIL|ERROR)' | head -20 | while IFS= read -r line; do
esc="$(printf '%s' "$line" | sed 's/%/%25/g' | tr -d '\r\n')"
printf '::error title=K9 corpus conformance::%s\n' "$esc"
done
set -euo pipefail
if command -v bun >/dev/null; then
bun 1-formats/k9/tools/conformance-report.mjs
else
node 1-formats/k9/tools/conformance-report.mjs
fi
exit $rc

- name: Publish the K9 verdict to the pull request
# always(): a failing gate is exactly when the detail is needed, and a
# skipped step would publish nothing.
if: ${{ always() && github.event_name == 'pull_request' }}
- name: Archive dated conformance evidence
if: ${{ always() && env.K9_REPORT_DIR != '' }}
uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
with:
name: conformance-evidence-${{ github.run_id }}-${{ github.run_attempt }}
path: ${{ env.K9_REPORT_DIR }}
if-no-files-found: error
retention-days: 90

- name: Update the compact PR verdict
if: ${{ always() && github.event_name == 'pull_request' && env.K9_REPORT_DIR != '' }}
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
PR_NUMBER: ${{ github.event.pull_request.number }}
run: |
set -uo pipefail
[ -s "$K9_REPORT" ] || { echo "no K9 report produced"; exit 0; }
{
echo '<!-- k9-contractile-report: regenerated on every run -->'
echo '# K9 contract conformance'
echo
echo "_run_ $GITHUB_SERVER_URL/$GITHUB_REPOSITORY/actions/runs/$GITHUB_RUN_ID"
cat "$K9_REPORT"
} > "$RUNNER_TEMP/body.md"
# Always post; never PATCH. Creating a comment is the one path this
# step has been observed to complete, and two runs were lost
# diagnosing an update that could not be read back. Stacked reports
# cost a little noise on the PR and buy a result that actually
# arrives, which is the right trade while the log host is
# unreachable. Newest comment wins; each one names its run.
gh pr comment "$PR_NUMBER" --repo "$GITHUB_REPOSITORY" \
--body-file "$RUNNER_TEMP/body.md" >/dev/null
echo "published the K9 verdict to #$PR_NUMBER"
set -euo pipefail
comment_id="$(gh api --paginate --slurp "repos/$GITHUB_REPOSITORY/issues/$PR_NUMBER/comments" \
| jq -r '[.[][] | select(.user.login == "github-actions[bot]" and (.body | contains("<!-- k9-contractile-report:")))] | last | .id // ""')"
jq -n --rawfile body "$K9_REPORT_DIR/report.md" '{body: $body}' > "$RUNNER_TEMP/k9-comment.json"
if [ -n "$comment_id" ]; then
gh api -X PATCH "repos/$GITHUB_REPOSITORY/issues/comments/$comment_id" --input "$RUNNER_TEMP/k9-comment.json" >/dev/null
else
gh api -X POST "repos/$GITHUB_REPOSITORY/issues/$PR_NUMBER/comments" --input "$RUNNER_TEMP/k9-comment.json" >/dev/null
fi
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -105,3 +105,6 @@ hooks/playbook-to-recipe/Cargo.lock
# Backup/scratch files (never commit)
*.backup
*.bak

# Generated conformance evidence is uploaded by run, never mixed into source.
/tests/conformance/????-??-??/
83 changes: 83 additions & 0 deletions 1-formats/k9/tools/CONFORMANCE-REPORTING.adoc
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
// SPDX-License-Identifier: CC-BY-SA-4.0
= DEED–K9 evidence and compact reporting

`conformance-report.mjs` calls the existing validators, retains their logs and
produces a structured result plus one compact Markdown report. This is local
reporting policy, not a new normative contract. It adds no K9 conformance rules.

== What each category establishes

[cols="1,3,3",options="header"]
|===
|Category |Evidence |Limit
|DEED syntax |The current Bun linter accepts each tracked live deed; a root repo deed exists. |No assertion that metadata is true, complete or authorised.
|Contract typecheck |Pinned Nickel typechecks the normative contract. |Does not establish that every predicate or validator path is correct.
|Validator self-test |Mirror, extractor, capability and attribution assertions execute. |Finite implementation tests, not a proof of validator completeness.
|Positive/negative controls |Valid examples pass; invalid examples fail by their named rule and layer. |Expected rejection is a successful test. Coverage is finite.
|Live corpus L0–L2 |Tracked contracts are checked by the canonical hook with Nickel; changed debt entries lose their exemptions. |Coordination dialects are routed out of scope. L3 signature verification is outside this report.
|===

== Verdicts and counts

* Red: at least one blocking conformance failure is known.
* Orange: required evidence is incomplete, unavailable, interrupted or malformed.
* Yellow: checks completed with permitted advisory debt or warnings.
* Green: all applicable checks in the declared scope completed successfully.
* Grey: explicitly not applicable, with a reason; never a synonym for not run.

Red takes precedence over orange, so missing evidence cannot conceal a known
failure. Indeterminate counts remain visible. Counts are result assertions or
files, not lines of log output. Failed counts include advisory failures, labelled
as such; advisory warnings on a passing file can also produce yellow. A routed
coordination file is N/A, never a validated K9 contract. The combined badge covers
DEED syntax and K9 L0–L2 only; it is no security certification or L3 claim.

== Comparison and evidence

The previous report is read from the latest bot-owned marked PR comment. A
small baseline is embedded in that comment. Compare different revisions of the
same PR only when schema, validator/profile hashes, tool versions, fixture definitions and corpus identities
match and the category has no indeterminate evidence. Arrows mean fewer/more
failed results; equal failures and advisories mean unchanged. Changes to scope or validators
produce `no comparable baseline`, rather than an apparent improvement.

Evidence lives in `tests/conformance/YYYY-MM-DD/pr-N/run-ID-ATTEMPT/` and is
uploaded as a dated CI artifact. The current PR comment is updated in place.
Failures and scope exclusions are expandable; at most 20 details per category
are shown, with full evidence in the artifact. Run and commit identity accompany
the badge. Reports are evidence, not an authority to weaken a gate.

== Reproduce

[source,bash]
----
# Install the workflow's pinned, SHA-256-verified Nickel, and use Bun.
bun test 1-formats/k9/tools/conformance-report.test.mjs
bun 1-formats/k9/tools/conformance-report.mjs
----

Exit 0 means green, yellow or explicit N/A; exit 1 means a blocking failure;
exit 2 means evidence could not establish the verdict. CI retains its required
job name. Existing structural/Mustfile checks still run and can fail the job
independently of this scoped conformance report. No fabricated check status is
posted. Fork PRs use `pull_request`, never privileged `pull_request_target`;
if GitHub denies comment writes, the artifact and job summary remain available.

== Audit changes

The previous self-test workflow used a brace group ending in `echo`, so its
pipeline status could hide the validator's failure. Collection now captures the
actual subprocess exit. The corpus hook previously discarded that exit and
could pass an empty result stream; it now checks both status and result count.
Routed files no longer inflate the conformance count. A separate changed-file
list applies debt policy to the full corpus without narrowing the scan.

The structured reader additionally rejects missing, duplicate, unexpected or
malformed per-file results. Missing tools and unresolvable PR ranges are orange.
Report text escapes input from file paths and diagnostic messages.

The lock membership helper also completes direct workflow refs from existing
resolved dependencies. Its regression verifies idempotence; offline
`gh-actions-lock --verify-local` checks that no new unresolved pin was invented.
The full authenticated relock is unavailable in this environment; no dependency
digest was changed.
7 changes: 7 additions & 0 deletions 1-formats/k9/tools/README.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,13 @@ The hook's default layer is L1 (it must run on a machine with no toolchain);
the CI corpus step raises it with `K9_VALIDATE_LAYER=all`, because CI installs
the pinned `nickel` binary. Ledger entries stay advisory at either layer.

== Compact CI reports

link:CONFORMANCE-REPORTING.adoc[Conformance reporting] defines the evidence
schema, five-state verdicts, comparison rules and dated artifact layout.
`conformance-report.mjs` renders one updatable PR table with expandable findings;
`conformance-report.test.mjs` checks failure and missing-evidence behavior.

== Running it

[source,bash]
Expand Down
Loading
Loading