Skip to content
Open
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
23 changes: 11 additions & 12 deletions .github/workflows/verify.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -57,16 +57,17 @@ jobs:
go test -count=2 ./internal/forge/conformance/... -run 'TestConformanceTargetAdvancedRejected|TestConformanceRerunIdempotence|TestConformanceDuplicateRepair'
go test -count=2 ./internal/forge/... -run TestMergeFailsClosedDoubleRun
go test -count=2 ./internal/render/... -run 'TestRenderGoldens|TestRenderSummaryGolden'
- name: coverage gate (>=90% on internal/, D-010)
run: |
if ls internal/*/*.go >/dev/null 2>&1; then
go test -coverprofile=cov.out ./internal/...
pct=$(go tool cover -func=cov.out | awk '/^total:/ {gsub("%","",$3); print $3}')
echo "coverage: ${pct}% (required: 90%)"
awk -v p="$pct" 'BEGIN { exit (p+0 >= 90 ? 0 : 1) }'
else
echo "no internal packages yet — gate armed, not applicable"
fi
- name: install Task (coverage + exit gate scripts)
run: go install github.com/go-task/task/v3/cmd/task@"${TASK_VERSION}"
# AUD-S13 / D-128 — the coverage floor is SINGLE-SOURCED in Taskfile.yml's
# `coverage:` task (var COVERAGE_MIN). This step used to re-implement the gate
# inline with its own copy of the threshold, so raising the local floor left CI
# silently enforcing the previous one — the skew that actually shipped.
# Delegating to `task coverage` means the threshold exists in exactly ONE place
# and the two halves cannot drift again. Do not re-inline the shell here;
# change COVERAGE_MIN in Taskfile.yml instead.
- name: coverage gate (D-010 — threshold single-sourced from Taskfile.yml)
run: task coverage
- run: CGO_ENABLED=0 go build ./...
- name: dogfood examples (P5-E6-S08 — every non-locked pack gates itself green under the real `assent test` CLI)
run: |
Expand All @@ -77,8 +78,6 @@ jobs:
done
- name: comparison corpus dogfood (PCS-S08 — examples/comparison validates and runs green)
run: go test ./examples/comparison/...
- name: install Task (exit gate scripts)
run: go install github.com/go-task/task/v3/cmd/task@"${TASK_VERSION}"
- name: PCS compare exit gate (RELSE-03 — REQ-PCS-S09)
run: bash hack/compare/exitgate_test.sh
# AUD-S02 (REQ-AUD-S02-02) — CHANGELOG.md drift gate, main + schedule only.
Expand Down
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,8 @@ D-113 immutability holds — only the algorithm computing the pin moved, version

### Chores
- :wrench: chore(changelog): render version headings in Keep-a-Changelog bracket form
- :wrench: chore(gate): raise the D-010 coverage floor 90% -> 91% (D-128)
- :wrench: chore(gate): single-source the coverage floor so CI and local cannot skew (D-128)

### Documentation
- :memo: docs(decisions): close D-111 E9 exit gate after v0.1.0
Expand Down Expand Up @@ -85,6 +87,8 @@ D-113 immutability holds — only the algorithm computing the pin moved, version
- :memo: docs(adr): record the AUD-S12 malformed-marker behaviour change in ADR-0019 (review F3)
- :memo: docs(adr): correct the convergence mechanism for a skipped bot marker (review F8)
- :memo: docs(planning): mark E10 design-note steps 1-2 shipped by AUD-S15
- :memo: docs(decisions): correct D-128's margin arithmetic and its stale ownership line
- :memo: docs(open-questions): widen OQ-27 to the full string-operand class, deepen OQ-28 (review F4/F5)

### Features
- :sparkles: feat(cli): dispatch-table help listing the real subcommands (REQ-AUD-S05-01)
Expand Down Expand Up @@ -157,6 +161,8 @@ D-113 immutability holds — only the algorithm computing the pin moved, version
- :white_check_mark: test(cmd): pin policySha to raw policy bytes (D-121 byte-vs-document split)
- :white_check_mark: test(lint): close the aliased-import evasion in the ARCH-02 gate
- :white_check_mark: fix(test): make the exec-timeout tests deterministic under load
- :white_check_mark: test(aud-s13): TEST-02/05/06 — toCEL overflow, reconcileClearSlot branches, repo_file containment + expiry
- :white_check_mark: test(aud-s13): drop an unreachable assertion in the containment table (review F6)
## [0.1.0] - 2026-08-05

### Chores
Expand Down
4 changes: 3 additions & 1 deletion GUIDELINES.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,9 @@ holds the *how*. Conflicts: ADRs win; update this file via PR when they do.
## Testing (D-010)

- TDD: failing test first, always. One logical change per commit; `task check` green before
every commit (includes the ≥90% coverage gate on `internal/…`).
every commit (includes the D-010 coverage gate on `internal/…`; the floor is the
`COVERAGE_MIN` var in `Taskfile.yml`'s `coverage:` task — currently 91%, raised by D-128 —
and CI runs that same task, so there is only ever one number to change).
- Test at the level that gives the proof: golden decision tests (L0) for engine semantics;
the adopter harness (L1) for policy behavior; cassettes (L2) for adapters; real GitLab
(L3, `//go:build e2e`) for forge semantics — never mocks for thread/approval/merge flows.
Expand Down
15 changes: 12 additions & 3 deletions Taskfile.yml
Original file line number Diff line number Diff line change
Expand Up @@ -32,16 +32,25 @@ tasks:
- go test -race ./...

coverage:
desc: "Coverage gate: >=90% on internal/ (D-010) — arms itself once internal/ has code"
# D-010 coverage gate. COVERAGE_MIN below is the SINGLE SOURCE of the floor:
# .github/workflows/verify.yaml runs this very task rather than re-implementing
# the threshold, so the local and CI halves cannot skew (D-128). Raise the floor
# by editing COVERAGE_MIN only — no other GATE may carry its own copy of the
# number (GUIDELINES.md quotes it as prose and names this var as the source).
# NOTE: the comparison is against the ONE-DECIMAL total `go tool cover -func`
# prints, so a floor of 91 really admits anything rounding to 91.0, i.e. ~90.95%.
desc: "Coverage gate on internal/ (D-010; floor = COVERAGE_MIN, raised to 91 by D-128) — arms itself once internal/ has code"
vars:
COVERAGE_MIN: 91
cmds:
- |
if ! ls internal/*/*.go >/dev/null 2>&1; then
echo "no internal packages yet — gate armed, not applicable"; exit 0
fi
go test -coverprofile=cov.out ./internal/...
pct=$(go tool cover -func=cov.out | awk '/^total:/ {gsub("%","",$3); print $3}')
echo "coverage: ${pct}% (required: 90%)"
awk -v p="$pct" 'BEGIN { exit (p+0 >= 90 ? 0 : 1) }' || { echo "FAIL: coverage below 90% (D-010)"; exit 1; }
echo "coverage: ${pct}% (required: {{.COVERAGE_MIN}}%)"
awk -v p="$pct" -v min="{{.COVERAGE_MIN}}" 'BEGIN { exit (p+0 >= min+0 ? 0 : 1) }' || { echo "FAIL: coverage below {{.COVERAGE_MIN}}% (D-010, floor raised by D-128)"; exit 1; }

build:
desc: Build the CLI
Expand Down
1 change: 1 addition & 0 deletions docs/decisions/decisions.md
Original file line number Diff line number Diff line change
Expand Up @@ -130,3 +130,4 @@ project/process decisions.
| D-123 | 2026-08-06 | **ARCH-01 — boundary enforcement automated (depguard + extended purity walk); ADR-0011 Amendment 3 truths the "arch-lint enforced" claim.** Two layers: (1) golangci `depguard` deny-rules — `internal/core/**`, `internal/change/**`, `internal/glob`, `internal/lint`, `internal/catalogue`, `internal/evaldecode`, `internal/compare`, `schemas/**` may import none of `internal/forge/**`, `internal/render/**`, `cmd/**`, `net/**`; (2) `TestCorePurity` walk extends to `../evaldecode`, `../compare`, and `../../schemas` (call-level: `time.Now`/`os.Getenv`/`os.Environ`/rand/net, adversarial self-test retained). Scope note: this EXTENDS the AGENTS.md rule-7 pure tree — `internal/evaldecode` (engine input decode) and `internal/compare` (D-116/D-117 gate determinism) join the determinism guard; `schemas` is embedded compile-time authority. Acceptance: a synthetic violating import/call fails CI both ways. Revert: drop depguard rules + walk dirs and re-amend ADR-0011. |
| D-124 | 2026-08-07 | **AUD-S06 residual — the two docs truth-lag gates exist but are UNWIRED; wiring is assigned to Lane B.** `hack/docs/readme_smoke_test.sh` (REQ-AUD-S06-01, executes every README quick-start command) and `hack/docs/truthlag_pins_test.sh` (REQ-AUD-S06-02, 18 grep/diff pins over DOC-05/06/09/10/11 + the ADR status index) are green and mutation-proven, but nothing invokes them: `Taskfile.yml` and `.github/workflows/**` belong to **Lane B** (AUD-S02/S03/S09/S14), so AUD-S06 could not add them. **Consequence, stated plainly: until they are wired, a future README or docs edit reopens DOC-07 (a quick-start that exits 2) or any pinned claim with NOTHING going red** — the mechanism is a manual gate, which is the same class of defect AUD-S06 exists to close. Assignment: Lane B adds a `docs-gates` task (`bash hack/docs/readme_smoke_test.sh && bash hack/docs/truthlag_pins_test.sh`) as a `check:` dependency, following the `hack/compare/exitgate_test.sh` precedent (D-118), alongside its AUD-S09/S14 workflow work. **Extend when wiring** (known pin gaps, both accepted for now): (a) the DOC-09 walkthrough check asserts banner PRESENCE, not polarity — flipping a step's `Planned` to `Shipped` stays green; (b) the DOC-05 link-resolution loop is scoped to `README.md`, so the relative links in `examples/README.md` are unpinned; (c) AUD-S05's `TestNoStaleProductClaims` (`cmd/assent/main_help_test.go`, Lane A5's file) walks only `cmd/`, `internal/` and `docs/` — markdown under `hack/`, `.github/` and `test/` is grepped by no pin at all. **Known unfixed truth-lag, deliberately not corrected in AUD-S06 to keep the reviewed diff narrow — fix when next editing these files:** (i) `docs/planning/meta-plan.md` closes the Phase-5 epic table with "Ordering constraint: E7 starts early (alongside E1)", directly under the new heading asserting the table is the numbering that actually executed — E7 in fact landed after E6 (E6 tip `ec91226` is an ancestor of E7-S08 `f27457d`, both 2026-08-04); (ii) `docs/usage/install.md` credits a stamped version to "the Homebrew **bottle**", but `.goreleaser.yaml`'s `brews:` block publishes a **Formula** whose `url_template` points at the release archive — no bottle is built or hosted; the version claim is true, only the term is wrong. Revert: delete the two scripts and reopen DOC-05/06/07/09/10/11 as live findings. |
| D-125 | 2026-08-07 | **AUD-S02 judgment call (b) — the CHANGELOG drift gate runs in `task check` on every local commit and in CI on push-to-main + schedule, NOT on `pull_request`.** The spec asked for both placements; the PR placement is not merely noisy, it is red by construction. Evidence (reproduced locally, not reasoned): `hack/release/verify-changelog.sh` diffs the WHOLE generated changelog against the committed file, and `cliff.toml`'s parser list ends in a catch-all `{ message = ".*", group = "Other" }`, so merge commits are rendered — merging a probe branch put `- Merge 1234567 into 89abcde` in the generated output. On `pull_request`, `actions/checkout` checks out `refs/pull/N/merge`: a merge commit minted at CI time whose subject is exactly that shape. No committed `CHANGELOG.md` can contain a line naming a SHA that did not exist when it was written, so a PR-scoped step fails on every PR with no author fix (and would push commit SHAs into the changelog, contra **D-101**). Walk-back taken under the spec's decide-and-log sanction, using the `release-exitgate` precedent already in the file: `if: github.event_name != 'pull_request'`. **Consequence, stated plainly:** PR CI does not catch changelog drift. The `check:` entry does — one commit later, by construction: `task check` is green at HEAD, the next commit makes `CHANGELOG.md` stale, and the following `task check` is red until `task changelog-write` is committed. Push-to-main is the backstop, so a lane that lands without regenerating reds main until a regeneration commit follows. **Working rule:** a regeneration commit must be subject-prefixed `:memo: chore(release):` or `:wrench: chore(release):` — the two forms `cliff.toml` skips — or it creates fresh drift itself; and a lane must regenerate AFTER its last content commit and after any `git merge origin/main`. Two companion changes ship with it: version headings render Keep-a-Changelog style (`## [0.1.0] - 2026-08-05`, matching the hand-written `[0.0.0]` stub), and the **D-120** `pins.toolDigest` record-consumer warning lives in `cliff.toml`'s `[changelog] header` — CHANGELOG.md is regenerated in full, so a hand-edit there would be wiped by the next `changelog-write` with the drift gate still green. **Closes the D-124 assignment** in the same `check:` list: `docs-gates` (`hack/docs/readme_smoke_test.sh` + `truthlag_pins_test.sh`) and `lint-depguard-test` (`hack/lint/depguard_test.sh`) are now sequential `check:` commands — sequential, not `deps:`, because go-task runs deps in parallel and the smoke test builds a binary while `fmt` rewrites the tree. The depguard proof is local-only: CI lints via `golangci-lint-action`, which leaves no binary on PATH for a later step, and that gate refuses to skip when `golangci-lint` is absent. D-124's known pin gaps (a)/(b)/(c) and truth-lag items (i)/(ii) are NOT addressed here and remain open. Revert: drop the `check:` entries and the verify.yaml step — reopens RELSE-01 and D-124. |
| D-128 | 2026-08-08 | **AUD-S13 — the D-010 coverage floor is raised 90% → 91%, local gate only; the CI half is NOT raised here.** Operator ruling, taken in the same change AUD-S13 lands. Rationale: the aggregate `./internal/...` total sat at **90.28%** against a 90% floor, so the gate had stopped MEASURING and started STEERING — any lane that added a branch without a test reddened `task check` for a reason unrelated to that lane, and the cheapest green was a filler test. **Supersedes the AUD-S13 spec line "Not in scope: raising the D-010 floor"** (`openspec/specs/p5-aud-audit-remediation/spec.md`, story AUD-S13) and its goal-(4) parenthetical "gate floor stays 90% per judgment call (e) unless the operator raises it" — the operator raised it; read the two together, not as a contradiction. Measured: **90.28% → 91.010%** (4479→4515 of 4961 statements) from the three TEST-02/05/06 behavior families, no filler. **The real margin, stated precisely — "91%" is nominal, the effective floor is ~90.95%, and current slack is TWO STATEMENTS.** The gate does not compare the raw ratio: it compares the **one-decimal total** `go tool cover -func` prints (`pct=$(… awk '/^total:/ …')` yields the string `91.0`, not `91.010`). So anything that rounds to `91.0` passes, i.e. **4513 of 4961 statements** is the true minimum (4513 → 90.9696% → prints `91.0` → PASS; 4512 → 90.9494% → prints `90.9` → FAIL). AUD-S13 leaves 4515, so the slack is **2 statements**, not the 0.01pp the raw ratio suggests. The rounding behaviour is inherited from the pre-existing gate design (D-010), not introduced here, and is left as-is. Net: the gate is now genuinely load-bearing — the next lane that adds a couple of untested branches reds `task check`, which is the intent. If that proves disruptive the answer is more behavior tests, not a lower floor. **Split-gate CLOSED, and closed by SINGLE-SOURCING rather than by editing two numbers.** The floor briefly existed twice: `Taskfile.yml` said 91% while `.github/workflows/verify.yaml` re-implemented the whole gate inline and still said 90% — AUD-S13 was fenced out of `.github/workflows/**` while PR #25 (AUD-S09/S14) owned it. #25 merged 2026-08-08, the fence lifted, and rather than raise a second literal (which would only reset the drift clock) the CI step now **delegates to `task coverage`**: the `install Task` step moves above it, and the step body is just `task coverage`. The threshold therefore lives in exactly ONE place — `Taskfile.yml`'s `coverage:` task var **`COVERAGE_MIN: 91`**, interpolated into the echo and the awk comparison, with the `desc` naming the var instead of the number. `verify.yaml` now carries no coverage threshold at all, so the two halves are structurally incapable of skewing. Proven both polarities: `COVERAGE_MIN: 99` reds the gate with the right message (so the templated var is really load-bearing — an unrendered `{{.COVERAGE_MIN}}` would make `min+0 == 0` and pass everything), and `hack/lint/workflow_pins_test.sh` stays green across all 39 controls, including "both Task installs interpolate a single workflow-level TASK_VERSION" after the step move. **Consequence:** raising or lowering the floor is now a one-line edit and CI follows automatically; re-inlining the shell into `verify.yaml` would silently reopen the skew, so the step carries a comment saying not to. Revert: restore the inline coverage shell in `verify.yaml` and the literal thresholds in `Taskfile.yml` / `GUIDELINES.md`. |
Loading