Skip to content

Scorecard

After each run, Forgecroft produces a scorecard: one entry per active invariant that intersected with the change. Entries are sorted by severity and include the evidence that supported the verdict.

Each scorecard entry carries one of three customer-facing labels:

LabelMeaning
CompliantForgecroft found evidence that the property holds for this change. Evidence references are included in the entry.
Needs evidenceThe invariant intersects with this change, but the available evidence is insufficient to prove or disprove the claim. The entry includes what would resolve it.
ViolationForgecroft found evidence that the property is violated. The entry includes remediation guidance.

Invariants that do not intersect with the change are not present in the scorecard. Absence means “not applicable,” not “passed.”

Entries are always sorted from most to least severe:

  1. Violation
  2. Needs evidence
  3. Compliant

The rollup counts in the scorecard header reflect the same order.

When a run is triggered by a pull request, Forgecroft posts a scorecard summary as a PR comment. The header badge shows the rollup:

❌ 1 · ⚠ 2 · ✅ 5

Each invariant with a Violation or Needs evidence verdict is listed individually with its label, intent text, and remediation or gap description.

Terminal window
forgecroft runs scorecard <run-id>

Output:

Run: run-01j4k...
Rollup: ❌ 1 ⚠ 2 ✅ 5
STATE SOURCE INTENT REMEDIATION
Violation policy no public S3 buckets Remove public-read ACL from aws_s3_bucket.assets
Needs evidence policy all IAM roles require MFA Runtime MFA enforcement is not observable from plan output
Needs evidence policy deletion protection on all production databases --
Compliant policy all resources tagged with cost-center --
...

Add --json to get the full structured response.

card, err := client.GetRunScorecard(ctx, runID)
if err != nil {
return err
}
fmt.Printf("Rollup: ❌ %d%d%d\n",
card.Rollup.Violation,
card.Rollup.NeedsEvidence,
card.Rollup.Compliant,
)
for _, entry := range card.Entries {
fmt.Printf("[%s] %s\n", entry.StateLabel, entry.IntentText)
if entry.Remediation != "" {
fmt.Printf(" Remediation: %s\n", entry.Remediation)
}
}
GET /runs/{id}/scorecard

Response:

{
"run_id": "run-01j4k...",
"entries": [
{
"invariant_id": "inv-uuid",
"intent_text": "no public S3 buckets",
"source": "customer",
"state": "blocked",
"state_label": "Violation",
"remediation": "Remove public-read ACL from aws_s3_bucket.assets",
"evidence_refs": ["ev-uuid-1"],
"last_evaluated_at": "2026-05-11T10:05:00Z"
},
{
"invariant_id": "inv-uuid-2",
"intent_text": "all IAM roles require MFA",
"source": "customer",
"state": "unprovable",
"state_label": "Needs evidence",
"remediation": "Runtime MFA enforcement is not observable from plan output",
"evidence_refs": [],
"last_evaluated_at": "2026-05-11T10:05:00Z"
},
{
"invariant_id": "inv-uuid-3",
"intent_text": "all resources tagged with cost-center",
"source": "customer",
"state": "proven",
"state_label": "Compliant",
"evidence_refs": ["ev-uuid-2", "ev-uuid-3"],
"last_evaluated_at": "2026-05-11T10:05:00Z"
}
],
"rollup": {
"compliant": 5,
"needs_evidence": 2,
"violation": 1
}
}
FieldDescription
invariant_idID of the invariant this entry evaluates
intent_textThe invariant’s intent text at evaluation time
sourceWho created the invariant (customer or system)
stateInternal state value: proven, unprovable, or blocked
state_labelCustomer-facing label: Compliant, Needs evidence, or Violation
remediationGuidance for resolving a Violation or Needs evidence entry (omitted when Compliant)
evidence_refsIDs of the evidence records that support the verdict
last_evaluated_atTimestamp of the most recent evaluation
FieldDescription
compliantCount of entries with Compliant verdict
needs_evidenceCount of entries with Needs evidence verdict
violationCount of entries with Violation verdict

If a run touches no resources that any active invariant covers, the entries array is empty:

No scorecard entries — change touched no active invariants.

This is normal for changes that are entirely outside the scope of your authored invariants.