Skip to content

feat: semantic release gate (iss-35 task 5, VSA, signed via A.1) - #16

Merged
REPPL merged 7 commits into
mainfrom
auto/iss35-semantic-gate
Jul 11, 2026
Merged

feat: semantic release gate (iss-35 task 5, VSA, signed via A.1)#16
REPPL merged 7 commits into
mainfrom
auto/iss35-semantic-gate

Conversation

@REPPL

@REPPL REPPL commented Jul 11, 2026

Copy link
Copy Markdown
Owner

The semantic (Direction-A) half of the iss-35 brief↔surface graduation — a VSA-shaped semantic release gate. Completes iss-35 alongside the deterministic surface_coverage rule (PR #14). Zero new dependencies; every phase security-reviewed PASS.

Phases

  1. Runbook + detector — a dev-tier release-gate runbook enumerating both enforcement planes (deterministic CI / host-run semantic), plus the sanitised host-run brief↔surface cross-check workflow.
  2. receipt_gate — a fail-closed, release-time record-lint rule: blocks a tag unless every required gate has a PROMOTE receipt whose subject digest matches the tagged commit and which pins a judge model. Disabled by default (release-time only).
  3. gate_lockstep — the runbook's deterministic-gate list must stay in lockstep with release.yml's verify steps; a non-empty min_gates floor is the fail-closed backstop. Enabled and enforced.
  4. Signed wiring (A.1)record-lint --release-gate <sha> --require-gate … arms the gate from the CLI (trust-rooted to the workflow, not the in-tree config); release.yml (release job, gated if: !private) runs the content gate, signs the receipts with actions/attest, and verifies with gh attestation verify — reusing the attest family + gh already used for binary provenance.

Security

Each trust-boundary phase was reviewed; the reviews caught (and this PR fixes) a real fail-open each time: empty required_gates passing vacuously; three hand-parser fail-opens; a severity-downgrade defang and a github.ref_name predicate injection.

Honest scope

Signing is auditable release provenance / tamper-evidence, not committer-forgery-proof (a receipt forged+committed pre-tag would be signed too; bounded by the iss-62 identity gate + branch protection). The whole release.yml gate is dormant on a private repo (attestation is public-only) and activates on the public flip. The attest predicate-type URI is a placeholder pending confirmation.

Verification

make preflight (build, gofmt, vet, test, race) + make record-lint green; release.yml validates as YAML; go.mod unchanged. New tests: TestReceiptGate (11), TestGateLockstep (9), TestArmReceiptGate.

Assisted-by: Claude:claude-opus-4-8

REPPL added 7 commits July 11, 2026 12:50
Codify the golden rule as a first-class abcd principle: prefer state-of-the-art
for design/tooling/practice decisions, but never adopt SOTA that violates the
repo's preferences — an adversary reviewer challenges SOTA for repo fit first,
then a sota-researcher verdict accounts for that challenge, then the pick.

A propagated default for all abcd-managed repos (the "opinionated" in
"opinionated configuration"); its managed-repo home is the AGENTS.md
conventions section abcd installs.

Assisted-by: Claude:claude-opus-4-8
The SOTA-verified design for the semantic (Direction-A) half of the iss-35
graduation, reached via the prefer-sota process (adversary fit-challenge then
sota-researcher verdict). The fit-surviving form is a Verification Summary
Attestation: a host-run judge emits a signed, sha-keyed PROMOTE/HOLD receipt;
a fail-closed record-lint rule verifies it before a tag. Phased build recorded;
phases touching signing/release run through security-reviewer first.

Assisted-by: Claude:claude-opus-4-8
… 5 ph1)

Phase 1 of the semantic release gate (design: plans/2026-07-11-iss35-semantic-
release-gate.md). Adds the dev-tier release-gate home:

- README.md: the pre-tag runbook. Enumerates both enforcement planes —
  deterministic (CI, release.yml is the source of truth) and semantic (host-run
  agent gates) — and owns the semantic half. The not-yet-armed receipt/lockstep
  machinery (phases 2-4) is marked loudly per loud-staging, never implied done.
- brief-surface-crosscheck.js: the iss-35 Direction-A semantic detector,
  promoted from ephemeral scratch and sanitised — the hardcoded absolute local
  path is gone (privacy) and the stale skills ground-truth is corrected (abcd
  ships zero skills; the surface is commands/abcd/). Host-run harness workflow,
  never CI.

Assisted-by: Claude:claude-opus-4-8
…5 ph2)

Phase 2 of the semantic release gate. Adds the receipt_gate record-lint rule:
the fail-closed, release-time verification of the semantic gates (the LLM passes
CI cannot run). Armed with a target commit sha, it asserts every required gate
has a receipt at .abcd/work/reviews/<sha>/<gate>.json whose subject digest is
that commit, whose verificationResult is PROMOTE, and which pins a judge model.
Missing / mismatched / malformed / HOLD / model-less all BLOCK — an un-run
semantic pass is never a silent pass.

Disabled by default in record-lint.json: it must never fire on ordinary
PRs/pushes, only at release time against the tagged commit (arming + signing is
phase 4). Adds a VSA-shaped receipt.example.json and updates the runbook's
staging note (partially armed) per loud-staging.

Security-reviewed (trust boundary): initial BLOCK on a fail-open (enabled +
empty required_gates passed vacuously) was fixed with a symmetric fail-closed
guard plus Commit/gate-name path-component validation; re-review PASS. The
review's Finding 2 (sign receipts AND trust-root the arming config, or signing
protects nothing) is recorded as a phase-4 obligation in the design doc.

TDD: TestReceiptGate, 11 cases.

Assisted-by: Claude:claude-opus-4-8
…sk 5 ph3)

Phase 3 of the semantic release gate. Adds the gate_lockstep record-lint rule:
the release-gate runbook's deterministic-gate list must stay in lockstep with
the CI workflow's verify-job steps (a gate in one but not the other BLOCKS), so
the runbook cannot silently drift from what CI enforces. Hand-parsed, no new
YAML dependency.

Security-reviewed (trust boundary): initial BLOCK found three fail-opens in the
hand-parse (the alternate `- uses:`/`name:` step form was invisible, a
double-empty parse passed vacuously, a 2-space comment closed a job early).
Fixed and re-reviewed PASS:
- a non-empty floor (min_gates) — any under-count fails closed, the backstop
  that makes hand-parsing safe;
- step-block parsing captures a step name wherever it appears;
- job scoping anchored to the jobs: block + a strict identifier-only header
  regex (a comment can't be a job boundary);
- a missing configured path fails loud, not nil;
- symmetric quote normalization.

Enabled in record-lint.json (min_gates:9, matching the live 9-gate verify job);
watched fire on injected drift. Runbook's staging note updated to present-tense.

Follow-up (fail-safe, not fail-open): indentation-anchored step scoping would
stop a name: inside a with:/run: block being read as a phantom gate (over-blocks
today, never hides drift).

TDD: TestGateLockstep, 9 cases.

Assisted-by: Claude:claude-opus-4-8
Phase 4 (final) of the semantic release gate — full signed path, no new Go
dependency, reusing the actions/attest family + gh attestation verify the repo
already uses for binary provenance (investigation confirmed A.1 viable).

- ArmReceiptGate (internal/core/lint) + cmd/record-lint --release-gate <sha>
  / --require-gate <name> flags: a CI workflow arms receipt_gate from the CLI,
  so the decision to gate, the target commit, and the required-gate list are
  trust-rooted to the workflow, not the in-tree committer-editable config. The
  rule stays disabled for ordinary runs.
- release.yml (release job, gated `if: !private` like the binary attestation):
  fail-closed content gate (record-lint --release-gate), signs the receipts with
  actions/attest (predicate .../semantic-release-gate/v1), verifies with
  gh attestation verify.

Security-reviewed (trust boundary): initial BLOCK found two holes — arming did
not force blocker severity (a committed severity:warning would defang the gate)
and github.ref_name (a tag name) was interpolated into the signed predicate JSON
(injection). Both fixed (force blocker on arming; predicate carries only the
immutable github.sha) + an empty-sha fail-closed guard; re-review PASS.

Honest scope: signing is auditable release provenance / tamper-evidence, not
committer-forgery-proof (a receipt forged+committed pre-tag is signed too) —
bounded by the iss-62 identity gate + branch protection; full forgery-prevention
would need host-side signing. Dormant on a private repo (attestation is a
public-repo feature); activates on the public flip. go.mod unchanged.

TDD: TestArmReceiptGate.

Assisted-by: Claude:claude-opus-4-8
# Conflicts:
#	.abcd/development/plans/2026-07-11-iss35-semantic-release-gate.md
@REPPL
REPPL merged commit d58c81c into main Jul 11, 2026
12 checks passed
@REPPL
REPPL deleted the auto/iss35-semantic-gate branch July 11, 2026 13:23
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant