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
67 changes: 67 additions & 0 deletions .fusa-reqs.json
Original file line number Diff line number Diff line change
Expand Up @@ -2993,6 +2993,73 @@
"title": "SCI artifacts[] projection (file/hash/version) per x-FuSa spec 9.3",
"priority": "MUST",
"standard": "DO-178C \u00a711.16"
},
{
"id": "REQ-FO-CORE009",
"title": "Attestation struct + AttestationValid + ContentHash helper",
"description": "FuSaOps shall provide an Attestation type (status/implementationAuthor/independentReviewer/reviewedAt/contentHash) and an AttestationValid function checking non-staleness and independent-reviewer identity, per x-FuSa spec section 1.6.2.",
"priority": "MUST",
"rationale": "A DCO-style attestation mechanism lets an artifact-quality WARNING (FUSA-STUB002) stay advisory by default without opening a stub-content loophole -- a named independent reviewer can vouch for content a similarity heuristic is unsure about."
},
{
"id": "REQ-QB001",
"title": "qualitybar.DetectPlaceholder (FUSA-STUB001, x-FuSa spec section 1.6.1 Rule A)",
"priority": "MUST",
"standard": "x-FuSa spec 1.6.1"
},
{
"id": "REQ-QB002",
"title": "qualitybar.DetectBlankFallback (FUSA-STUB002, x-FuSa spec section 1.6.1 Rule B)",
"priority": "MUST",
"standard": "x-FuSa spec 1.6.1"
},
{
"id": "REQ-FO-FMEA007",
"title": "fmea.AttestationContentHash excludes Hash/Attestation/GeneratedAt per x-FuSa spec 1.6.2",
"priority": "MUST",
"standard": "IEC 61508:2010 / ISO 26262:2018 Part 8-7"
},
{
"id": "REQ-FO-FMEA008",
"title": "FMEA Summary coverage metrics: componentsAnalyzed/componentsInProject/coveragePct/componentInventoryMethod per x-FuSa spec 9.2",
"priority": "MUST",
"standard": "IEC 61508:2010 / ISO 26262:2018 Part 8-7"
},
{
"id": "REQ-FO-TARA007",
"title": "tara.AttestationContentHash excludes Hash/Attestation/GeneratedAt per x-FuSa spec 1.6.2",
"priority": "MUST",
"standard": "ISO 21434:2021 Clause 15"
},
{
"id": "REQ-FO-TARA008",
"title": "TARA Summary coverage metrics: assetsAnalyzed/assetsInProject/coveragePct/assetInventoryMethod per x-FuSa spec 9.2",
"priority": "MUST",
"standard": "ISO 21434:2021 Clause 15"
},
{
"id": "REQ-FO-SC007",
"title": "safetycase.AttestationContentHash excludes Hash/Attestation/GeneratedAt per x-FuSa spec 1.6.2",
"priority": "MUST",
"standard": "ISO 26262, DO-178C, IEC 61508, ISO 21434"
},
{
"id": "REQ-FO-SAS007",
"title": "sas.AttestationContentHash excludes Hash/Attestation/GeneratedAt per x-FuSa spec 1.6.2",
"priority": "MUST",
"standard": "DO-178C \u00a711.20"
},
{
"id": "REQ-FO-HARA006",
"title": "hara.AttestationContentHash excludes only Attestation itself (CreatedAt is stable input content, not a regeneration timestamp)",
"priority": "MUST",
"standard": "ISO 26262-3 Clause 6"
},
{
"id": "REQ-FO-CLI084",
"title": "Quality-gate wiring: runQualityGate + attestationValid + per-command QualFields extraction and attestation carry-forward across fmea/hara/tara/safety-case/sas",
"priority": "MUST",
"standard": "x-FuSa spec 1.6.1/1.6.2"
}
]
}
41 changes: 41 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,47 @@ and the project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.

## [Unreleased]

## [1.141.0] — 2026-07-28

### feat: attestation, FUSA-STUB001/002 detection heuristics, and coverage-gate wiring (x-FuSa spec §1.6.1/§1.6.2)

Closes out the enforcement mechanism from spec v1.14.0 against FuSaOps' own
artifacts — the schema conformance work landed in v1.140.0; this is the
detection/attestation/gating layer on top of it.

- New root-package `Attestation` type + `AttestationValid`/`ContentHash`
helpers (`attestation.go`) — a DCO-style provenance record (independent
reviewer distinct from implementation author, hash-pinned to the exact
content reviewed) reusing the same independence concept as the `vv`
package.
- New `qualitybar` package implementing both §1.6.1 detection rules:
`DetectPlaceholder` (`FUSA-STUB001`, always `ERROR`, disposition-
suppressible only) and `DetectBlankFallback` (`FUSA-STUB002`, a
distinct-value-ratio `WARNING` across ≥10 entries, advisory by default,
suppressible only by a valid attestation). Both reuse `fusaops.Finding`
and `fusaops.ComputeFingerprint` — no new finding shape.
- `fmea`/`tara`/`safetycase`/`sas`/`hara` each gain an `Attestation` field
(nil by default — FuSaOps never fabricates a "reviewed" status for its
own auto-generated content) and an `AttestationContentHash` function.
- `cmd_{fmea,tara,safety_case,sas,hara}.go` now: run both detection rules
after building/loading, gate the exit code (`FUSA-STUB001` always;
`FUSA-STUB002` only under the new `--require-attestation`/`--strict`
flags), and — since `Build` always assembles a fresh document — carry a
prior run's attestation forward across regeneration rather than silently
discarding it. Staleness is automatic: a carried-forward attestation
whose hash no longer matches the regenerated content is correctly
treated as invalid.
- `fmea`/`tara` `Summary` gains `coveragePct` + a stated
`component`/`assetInventoryMethod` denominator methodology, plus
`--min-coverage N` on `cmd_fmea.go`/`cmd_tara.go`. FMEA's own coverage is
an honest 19.5% (8 of 41 tracked packages); TARA's is 80% (8 of its own
10 known evidence artefacts) — surfaced plainly, not inflated.
- Regenerated `fmea.json`/`tara.json`/`safety-case.json` against the new
code.
- 11 new requirements registered (`REQ-FO-CORE009`, `REQ-QB001`,
`REQ-QB002`, `REQ-FO-FMEA007/008`, `REQ-FO-TARA007/008`, `REQ-FO-SC007`,
`REQ-FO-SAS007`, `REQ-FO-HARA006`, `REQ-FO-CLI084`).

## [1.140.0] — 2026-07-28

### feat: bring FuSaOps' own hara/fmea/tara/safetycase/sas/sci into x-FuSa spec v1.14.0 conformance
Expand Down
74 changes: 74 additions & 0 deletions attestation.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
package fusaops

import (
"crypto/sha256"
"fmt"
)

// AttestationStatus records whether an evidence artifact's qualitative
// content has been reviewed by an independent human, per x-FuSa spec §1.6.2.
//
//fusa:req REQ-FO-CORE009
type AttestationStatus string

const (
// AttestationHeuristic is the fail-safe default: no human has vouched for
// this content. Absent status MUST be treated as this value.
AttestationHeuristic AttestationStatus = "heuristic"
// AttestationReviewed asserts an independent reviewer examined the
// content at ContentHash and found it genuine, not templated/placeholder.
AttestationReviewed AttestationStatus = "reviewed"
)

// Attestation is a DCO-style, per-artifact provenance record. It lets a
// consumer distinguish auto-scaffolded content from human-reviewed content
// without requiring a detection heuristic to be perfectly precise: rather
// than proving the content is good, it requires a named, independent human
// to assert that they checked, with an accountability trail attached. The
// independence model (ImplementationAuthor vs IndependentReviewer) mirrors
// the one already used by the vv package (ISO 26262-2:2018 §6.4).
//
//fusa:req REQ-FO-CORE009
type Attestation struct {
Status AttestationStatus `json:"status"`
ImplementationAuthor string `json:"implementationAuthor,omitempty"`
IndependentReviewer string `json:"independentReviewer,omitempty"`
ReviewedAt string `json:"reviewedAt,omitempty"` // RFC 3339
ContentHash string `json:"contentHash,omitempty"`
}

// AttestationValid reports whether a is a non-stale, genuinely independent
// "reviewed" attestation matching currentContentHash. Every failure mode —
// absent attestation, status "heuristic", a self-attestation (reviewer ==
// author), or a stale hash — returns false (fail-safe): the caller MUST then
// treat the content as unreviewed.
//
//fusa:req REQ-FO-CORE009
func AttestationValid(a Attestation, currentContentHash string) bool {
if a.Status != AttestationReviewed {
return false
}
if a.IndependentReviewer == "" || a.IndependentReviewer == a.ImplementationAuthor {
return false
}
if a.ContentHash == "" || currentContentHash == "" {
return false
}
return a.ContentHash == currentContentHash
}

// ContentHash computes the sha256:-prefixed RFC 8785 canonical hash of data,
// suitable for populating Attestation.ContentHash or for comparison in
// AttestationValid. Callers pass the JSON-marshaled artifact content with
// the Attestation field itself (and any generatedAt-style timestamp) already
// excluded, so a later edit to that content invalidates a prior attestation.
//
//fusa:req REQ-FO-CORE009
func ContentHash(data []byte) string {
canon, err := Canonicalize(data)
if err != nil {
return ""
}
sum := sha256.Sum256(canon)
return fmt.Sprintf("sha256:%x", sum)
}
100 changes: 100 additions & 0 deletions attestation_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
package fusaops

import "testing"

//fusa:test REQ-FO-CORE009
func TestAttestationValidTrue(t *testing.T) {
hash := ContentHash([]byte(`{"a":1}`))
a := Attestation{
Status: AttestationReviewed,
ImplementationAuthor: "auto",
IndependentReviewer: "Jane Doe <jane@example.com>",
ReviewedAt: "2026-07-28T00:00:00Z",
ContentHash: hash,
}
if !AttestationValid(a, hash) {
t.Error("expected valid attestation")
}
}

//fusa:test REQ-FO-CORE009
func TestAttestationValidHeuristicStatus(t *testing.T) {
hash := ContentHash([]byte(`{"a":1}`))
a := Attestation{Status: AttestationHeuristic, ContentHash: hash}
if AttestationValid(a, hash) {
t.Error("heuristic status must never be valid")
}
}

//fusa:test REQ-FO-CORE009
func TestAttestationValidAbsentStatus(t *testing.T) {
hash := ContentHash([]byte(`{"a":1}`))
a := Attestation{ContentHash: hash}
if AttestationValid(a, hash) {
t.Error("absent status must default to invalid (fail-safe)")
}
}

//fusa:test REQ-FO-CORE009
func TestAttestationValidSelfAttestation(t *testing.T) {
hash := ContentHash([]byte(`{"a":1}`))
a := Attestation{
Status: AttestationReviewed,
ImplementationAuthor: "Jane Doe <jane@example.com>",
IndependentReviewer: "Jane Doe <jane@example.com>",
ContentHash: hash,
}
if AttestationValid(a, hash) {
t.Error("a reviewer identical to the author must not satisfy independence")
}
}

//fusa:test REQ-FO-CORE009
func TestAttestationValidEmptyReviewer(t *testing.T) {
hash := ContentHash([]byte(`{"a":1}`))
a := Attestation{Status: AttestationReviewed, ContentHash: hash}
if AttestationValid(a, hash) {
t.Error("an empty IndependentReviewer must not satisfy independence")
}
}

//fusa:test REQ-FO-CORE009
func TestAttestationValidStaleHash(t *testing.T) {
oldHash := ContentHash([]byte(`{"a":1}`))
newHash := ContentHash([]byte(`{"a":2}`))
a := Attestation{
Status: AttestationReviewed,
IndependentReviewer: "Jane Doe <jane@example.com>",
ContentHash: oldHash,
}
if AttestationValid(a, newHash) {
t.Error("a stale content hash must invalidate the attestation")
}
}

//fusa:test REQ-FO-CORE009
func TestAttestationValidEmptyHashes(t *testing.T) {
a := Attestation{Status: AttestationReviewed, IndependentReviewer: "Jane Doe"}
if AttestationValid(a, "") {
t.Error("empty content hashes must not be considered valid")
}
}

//fusa:test REQ-FO-CORE009
func TestContentHashDeterministic(t *testing.T) {
h1 := ContentHash([]byte(`{"a":1,"b":2}`))
h2 := ContentHash([]byte(`{"b":2,"a":1}`))
if h1 != h2 {
t.Errorf("ContentHash should be key-order independent: %q != %q", h1, h2)
}
if h1 == "" {
t.Error("ContentHash must not be empty for valid JSON")
}
}

//fusa:test REQ-FO-CORE009
func TestContentHashInvalidJSON(t *testing.T) {
if got := ContentHash([]byte("not json")); got != "" {
t.Errorf("ContentHash of invalid JSON should be empty, got %q", got)
}
}
Loading
Loading