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
6 changes: 4 additions & 2 deletions internal/receipt/receipt.go
Original file line number Diff line number Diff line change
Expand Up @@ -193,8 +193,10 @@ func sanitizePaths(detail, repoRoot string) string {
// by five tests where four pass and one fails does NOT hold, and letting
// passing probes outvote a failing one would launder a defect into a
// verified row.
// - verified: no refutation, and some probe ran and passed; effective tier is
// the highest a passing probe conferred.
// - verified: no refutation, and some probe ran, passed, and conferred a
// tier above T0 (Evidence.Verified enforces the tier floor — a T0 pass
// raises nothing); effective tier is the highest a passing probe
// conferred.
// - unverified: nothing ran that could raise the claim. Remainder.
func weigh(evs []schema.Evidence) (schema.Status, schema.Tier) {
best := schema.T0Unverified
Expand Down
23 changes: 23 additions & 0 deletions internal/receipt/receipt_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,29 @@ func TestINV007_RefutationDominatesVerification(t *testing.T) {
}
}

// TestT0PassConfersNoVerification: a probe result that ran and passed but
// confers only T0 raises nothing — T0 IS the unverified tier, so accepting
// such a pass would mint a verified row whose effective tier means "nothing
// checked this". The claim stays in the remainder, and the pass does not
// dilute an honest refutation either.
func TestT0PassConfersNoVerification(t *testing.T) {
claims := []schema.Claim{{ID: "Z1", Shape: schema.ShapeInvariant,
ProbeIDs: []string{"noop:x"}}}
evidence := [][]schema.Evidence{{
{ClaimID: "Z1", ProbeID: "noop:x", Tier: schema.T0Unverified, Ran: true, Passed: true},
}}
r := Assemble(gitdiff.Change{}, claims, evidence, schema.Coverage{})
if r.Results[0].Status != schema.StatusUnverified {
t.Fatalf("status = %q, want unverified (a T0 pass confers nothing)", r.Results[0].Status)
}
if len(r.Remainder) != 1 || r.Remainder[0].Claim.ID != "Z1" {
t.Fatalf("claim with only a T0 pass must land in the remainder")
}
if r.Summary.Verified != 0 {
t.Fatalf("summary.Verified = %d, want 0", r.Summary.Verified)
}
}

// TestRemainderSectionAlwaysRenders: the text receipt states the remainder even
// when it is empty, so its absence is a declared result, not an omission.
func TestRemainderSectionAlwaysRenders(t *testing.T) {
Expand Down
16 changes: 10 additions & 6 deletions schema/schema.go
Original file line number Diff line number Diff line change
Expand Up @@ -192,10 +192,13 @@ type Evidence struct {
Binding string `json:"binding,omitempty"`
}

// Verified reports whether this evidence raises its claim: the probe ran and
// passed. A probe that ran and FAILED refutes the claim; a probe that did not
// run says nothing.
func (e Evidence) Verified() bool { return e.Ran && e.Passed }
// Verified reports whether this evidence raises its claim: the probe ran,
// passed, AND confers a tier above T0. A pass at T0 confers nothing — T0 IS
// the unverified tier, so counting such a pass as verification would mint
// StatusVerified with an effective tier that means "nothing checked this".
// A probe that ran and FAILED refutes the claim; a probe that did not run
// says nothing.
func (e Evidence) Verified() bool { return e.Ran && e.Passed && e.Tier > T0Unverified }

// Refuted reports whether this evidence refutes its claim: the probe ran and
// the claim did not hold.
Expand All @@ -207,8 +210,9 @@ type Status string
const (
// StatusVerified: at least one probe ran and passed. EffectiveTier > T0.
StatusVerified Status = "verified"
// StatusRefuted: at least one probe ran and failed, and none passed at a
// higher tier. The merge gate should block on refuted claims.
// StatusRefuted: at least one probe ran and failed. Refutation dominates
// unconditionally — no volume or tier of passing probes outweighs a
// failure. The merge gate should block on refuted claims.
StatusRefuted Status = "refuted"
// StatusUnverified: nothing ran that could raise the claim. Remainder.
StatusUnverified Status = "unverified"
Expand Down
Loading