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
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,14 @@ Format: [Keep a Changelog](https://keepachangelog.com/en/1.0.0/)
files, alter portfolio scoring, or regenerate portfolio truth.

### Changed
- Converged the strict security gate, risk/attention derivation, portfolio
rollups, registry/report rendering, decision digest, and weekly command-center
posture on one
fail-closed `SecurityAdmissionV1` interpretation of fresh Dependabot, CodeQL,
and secret-scanning evidence. Missing or contradictory evidence now carries
explicit reason codes; non-Dependabot blocking findings can no longer vanish
from risk or decision surfaces. PortfolioTruth remains schema `0.11.0` and
retains its legacy compatibility fields for existing consumers.
- Kept PortfolioTruth `0.11.0` and
`GitHubSecurityCoverageReceiptV1` read-compatible while adding normalized
provider reason codes, explicit completed-zero semantics, producer-commit
Expand Down
17 changes: 12 additions & 5 deletions docs/portfolio-decision-contract.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,17 +11,24 @@ generation `decision_fingerprint`, the exact question and accountable owner,
the allowed `accept` / `defer` / `reject` outcomes, an operator approval
boundary, an immutable evidence reference, `evaluated_at`,
`evidence_observed_at`, `valid_until`, source generations, and a readback
contract. The current security policy expires 36 hours after the underlying
Dependabot observation, reflecting the verified daily producer cadence plus
one bounded grace window. Regenerating PortfolioTruth never moves that clock.
contract. The current security policy expires 36 hours after the oldest
admitted Dependabot, CodeQL, or secret-scanning observation, reflecting the
verified daily producer cadence plus one bounded grace window. Regenerating
PortfolioTruth never moves that clock.

Security decisions carry the shared `SecurityAdmissionV1` identity and the
provider-specific blocking counts. A CodeQL high/critical finding or an open
secret-scanning finding enters the same repository-scoped decision contract as
a Dependabot high/critical finding. Incomplete or contradictory admission is
retained under `withheld_decisions` with the canonical `SECURITY_*` reason code.

Incomplete owner decisions are emitted under `withheld_decisions` with stable
reason codes. They are not actionable and consumers must not invent a question
or options. A prior digest may be supplied with `--previous-digest`; the next
generation then records the superseding generation and fingerprint. A missing
decision closes only when a newer complete GitHub security receipt establishes
authoritative absence. Bridge `SHIPPED` is supporting evidence only and is
explicitly barred from satisfying the readback contract.
authoritative absence across all three providers. Bridge `SHIPPED` is supporting
evidence only and is explicitly barred from satisfying the readback contract.

The machine-readable shape is
`config/portfolio-decision-digest-v2.schema.json`. Generate JSON and Markdown
Expand Down
19 changes: 18 additions & 1 deletion docs/security-model.md
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ Output lands in `output/ghas-alerts-<user>-<date>.json`. Excel and control-cente
Run `audit security-gate --output-dir output` after generating portfolio truth with
`--portfolio-truth-include-security`. The gate reads
`output/portfolio-truth-latest.json` and exits nonzero when any scanned repo has open
high/critical Dependabot alerts.
high/critical Dependabot or CodeQL alerts, or an open secret-scanning finding.

Use `--max-age-hours N` when the caller needs freshness enforcement. For example,
`audit security-gate --output-dir output --max-age-hours 168` fails as `STALE` when
Expand All @@ -74,6 +74,23 @@ The gate is deliberately strict: a snapshot with no scanned security overlay is
reported as `UNKNOWN`, not clear. This prevents a missing GHAS overlay from looking
like a clean portfolio.

All PortfolioTruth security consumers now use the same internal
`SecurityAdmissionV1` derivation. It admits a repo as clear only when the receipt is
fresh, coverage is complete, all three provider observations are complete, clocks
are monotonic, compatibility counts match provider counts, and no blocking finding
is present. Partial, stale, missing, malformed, or contradictory evidence carries
explicit `SECURITY_*` reason codes and cannot render green. Known findings remain
visible even when another provider is unavailable, but the evidence stays
unadmitted and the decision queue withholds actionability until the complete
evidence contract is restored.

This is a consumer-semantics convergence, not a PortfolioTruth schema migration:
the producer remains at `0.11.0`, existing compatibility count fields remain, and
the legacy Dependabot-only `open_high_critical` project field is retained for old
readers. Risk/attention, the strict gate, portfolio rollups, registry/report
rendering, the decision digest, and the weekly command-center security posture use
the combined admission result.

## OSSF Scorecard

Pass `--ossf-scorecard` to enrich each repo with pre-computed OSSF Scorecard data.
Expand Down
2 changes: 1 addition & 1 deletion fixtures/contracts/operator-control-plane-v1/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@
"portfolio_truth_schema_version": "0.11.0",
"producer": {
"artifact_path": "fixtures/contracts/portable-consumers-v1/portfolio-truth.json",
"artifact_sha256": "8af46309e1b7a891edf7489dcbfa5971cc3453fe983b9a824098d87dc42105d3",
"artifact_sha256": "e79ca6b203ff58b8b6ffbb01e778305887dfba4e4caf66334defea6ccc1713df",
"generator": "src.portfolio_truth_contract_fixture:build_portable_contract_fixture",
"manifest_path": "fixtures/contracts/operator-control-plane-v1/manifest.json",
"repository": "saagpatel/GithubRepoAuditor"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1492,7 +1492,7 @@
"risk_factors": [
"active-high-severity-alerts"
],
"risk_summary": "1 risk factor(s): open high/critical security alerts.",
"risk_summary": "1 risk factor(s): blocking GitHub security findings.",
"risk_tier": "moderate",
"security_risk": true
},
Expand Down Expand Up @@ -1609,9 +1609,11 @@
"scanned_count": 1,
"secret_scanning_observed_count": 1,
"secret_scanning_zero_finding_count": 1,
"security_admission_schema_version": "SecurityAdmissionV1",
"stale_count": 1,
"total_open_critical": 0,
"total_open_high": 3,
"total_open_secrets": 0,
"unavailable_count": 3,
"unknown_count": 1
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
"portfolio_truth_schema_version": "0.11.0",
"producer": {
"artifact_path": "fixtures/contracts/portfolio-command-center-v1/portfolio-truth.json",
"artifact_sha256": "5b12c9851e43f82bcaf872f900539d5a5b67da9d405a7e73f7d3a20bfcdb9eae",
"artifact_sha256": "3838ee7bb814170691bebe5506a9f55fefa1315b9436f4f24c4c041409f0b258",
"generator": "src.portfolio_truth_contract_fixture:build_contract_fixture",
"manifest_path": "fixtures/contracts/portfolio-command-center-v1/manifest.json",
"repository": "saagpatel/GithubRepoAuditor"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1492,7 +1492,7 @@
"risk_factors": [
"active-high-severity-alerts"
],
"risk_summary": "1 risk factor(s): open high/critical security alerts.",
"risk_summary": "1 risk factor(s): blocking GitHub security findings.",
"risk_tier": "moderate",
"security_risk": true
},
Expand Down Expand Up @@ -1609,9 +1609,11 @@
"scanned_count": 1,
"secret_scanning_observed_count": 1,
"secret_scanning_zero_finding_count": 1,
"security_admission_schema_version": "SecurityAdmissionV1",
"stale_count": 1,
"total_open_critical": 0,
"total_open_high": 3,
"total_open_secrets": 0,
"unavailable_count": 3,
"unknown_count": 1
}
Expand Down
2 changes: 1 addition & 1 deletion fixtures/contracts/public-site-projection-v1/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@
"portfolio_truth_schema_version": "0.11.0",
"producer": {
"artifact_path": "fixtures/contracts/portable-consumers-v1/portfolio-truth.json",
"artifact_sha256": "8af46309e1b7a891edf7489dcbfa5971cc3453fe983b9a824098d87dc42105d3",
"artifact_sha256": "e79ca6b203ff58b8b6ffbb01e778305887dfba4e4caf66334defea6ccc1713df",
"generator": "src.portfolio_truth_contract_fixture:build_portable_contract_fixture",
"manifest_path": "fixtures/contracts/public-site-projection-v1/manifest.json",
"repository": "saagpatel/GithubRepoAuditor"
Expand Down
16 changes: 8 additions & 8 deletions output/demo/pending-proposals.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
"repo_full_name": "demo-org/solstice-cairn",
"description": "Open an auto-PR improving the managed context block for Solstice Cairn.",
"status": "pending",
"created_at": "2026-07-31T22:50:13.825998+00:00",
"created_at": "2026-08-03T20:46:39.243078+00:00",
"approved_at": "",
"approved_by": "",
"rejected_at": "",
Expand All @@ -22,8 +22,8 @@
"repo_full_name": "demo-org/tabard-anvil",
"description": "Apply catalog seed updates for Tabard Anvil.",
"status": "approved",
"created_at": "2026-07-31T22:50:13.825998+00:00",
"approved_at": "2026-08-01T19:50:13.825998+00:00",
"created_at": "2026-08-03T20:46:39.243078+00:00",
"approved_at": "2026-08-04T17:46:39.243078+00:00",
"approved_by": "demo-operator",
"rejected_at": "",
"executed_at": "",
Expand All @@ -36,10 +36,10 @@
"repo_full_name": "demo-org/umbra-trellis",
"description": "Open an auto-PR improving the managed context block for Umbra Trellis.",
"status": "rejected",
"created_at": "2026-07-31T22:50:13.825998+00:00",
"created_at": "2026-08-03T20:46:39.243078+00:00",
"approved_at": "",
"approved_by": "",
"rejected_at": "2026-08-01T19:50:13.825998+00:00",
"rejected_at": "2026-08-04T17:46:39.243078+00:00",
"executed_at": "",
"execution_ref": ""
},
Expand All @@ -50,11 +50,11 @@
"repo_full_name": "demo-org/vellum-compass",
"description": "Apply catalog seed updates for Vellum Compass.",
"status": "executed",
"created_at": "2026-07-31T22:50:13.825998+00:00",
"approved_at": "2026-08-01T19:50:13.825998+00:00",
"created_at": "2026-08-03T20:46:39.243078+00:00",
"approved_at": "2026-08-04T17:46:39.243078+00:00",
"approved_by": "demo-operator",
"rejected_at": "",
"executed_at": "2026-08-01T20:50:13.825998+00:00",
"executed_at": "2026-08-04T18:46:39.243078+00:00",
"execution_ref": "demo-run-0004"
}
]
Expand Down
38 changes: 20 additions & 18 deletions output/demo/portfolio-truth-history-01.json
Original file line number Diff line number Diff line change
Expand Up @@ -2607,7 +2607,7 @@
"risk_factors": [
"active-high-severity-alerts"
],
"risk_summary": "1 risk factor(s): open high/critical security alerts.",
"risk_summary": "1 risk factor(s): blocking GitHub security findings.",
"doctor_gap": false,
"context_risk": false,
"path_risk": false,
Expand Down Expand Up @@ -2971,7 +2971,7 @@
"risk_factors": [
"active-high-severity-alerts"
],
"risk_summary": "1 risk factor(s): open high/critical security alerts.",
"risk_summary": "1 risk factor(s): blocking GitHub security findings.",
"doctor_gap": false,
"context_risk": false,
"path_risk": false,
Expand Down Expand Up @@ -3334,7 +3334,7 @@
"risk_factors": [
"active-high-severity-alerts"
],
"risk_summary": "1 risk factor(s): open high/critical security alerts.",
"risk_summary": "1 risk factor(s): blocking GitHub security findings.",
"doctor_gap": false,
"context_risk": false,
"path_risk": false,
Expand Down Expand Up @@ -3699,7 +3699,7 @@
"risk_factors": [
"active-high-severity-alerts"
],
"risk_summary": "1 risk factor(s): open high/critical security alerts.",
"risk_summary": "1 risk factor(s): blocking GitHub security findings.",
"doctor_gap": false,
"context_risk": false,
"path_risk": false,
Expand Down Expand Up @@ -4914,7 +4914,7 @@
"risk_factors": [
"active-high-severity-alerts"
],
"risk_summary": "1 risk factor(s): open high/critical security alerts.",
"risk_summary": "1 risk factor(s): blocking GitHub security findings.",
"doctor_gap": false,
"context_risk": false,
"path_risk": false,
Expand Down Expand Up @@ -5272,7 +5272,7 @@
"no-run-instructions",
"active-high-severity-alerts"
],
"risk_summary": "4 risk factor(s): weak context quality, investigate override active, run instructions missing, open high/critical security alerts.",
"risk_summary": "4 risk factor(s): weak context quality, investigate override active, run instructions missing, blocking GitHub security findings.",
"doctor_gap": false,
"context_risk": true,
"path_risk": true,
Expand Down Expand Up @@ -7412,7 +7412,7 @@
"no-run-instructions",
"active-high-severity-alerts"
],
"risk_summary": "4 risk factor(s): weak context quality, investigate override active, run instructions missing, open high/critical security alerts.",
"risk_summary": "4 risk factor(s): weak context quality, investigate override active, run instructions missing, blocking GitHub security findings.",
"doctor_gap": false,
"context_risk": true,
"path_risk": true,
Expand Down Expand Up @@ -7776,7 +7776,7 @@
"risk_factors": [
"active-high-severity-alerts"
],
"risk_summary": "1 risk factor(s): open high/critical security alerts.",
"risk_summary": "1 risk factor(s): blocking GitHub security findings.",
"doctor_gap": false,
"context_risk": false,
"path_risk": false,
Expand Down Expand Up @@ -8140,7 +8140,7 @@
"risk_factors": [
"active-high-severity-alerts"
],
"risk_summary": "1 risk factor(s): open high/critical security alerts.",
"risk_summary": "1 risk factor(s): blocking GitHub security findings.",
"doctor_gap": false,
"context_risk": false,
"path_risk": false,
Expand Down Expand Up @@ -8504,7 +8504,7 @@
"risk_factors": [
"active-high-severity-alerts"
],
"risk_summary": "1 risk factor(s): open high/critical security alerts.",
"risk_summary": "1 risk factor(s): blocking GitHub security findings.",
"doctor_gap": false,
"context_risk": false,
"path_risk": false,
Expand Down Expand Up @@ -8869,7 +8869,7 @@
"no-run-instructions",
"active-high-severity-alerts"
],
"risk_summary": "4 risk factor(s): weak context quality, investigate override active, run instructions missing, open high/critical security alerts.",
"risk_summary": "4 risk factor(s): weak context quality, investigate override active, run instructions missing, blocking GitHub security findings.",
"doctor_gap": false,
"context_risk": true,
"path_risk": true,
Expand Down Expand Up @@ -10083,7 +10083,7 @@
"risk_factors": [
"active-high-severity-alerts"
],
"risk_summary": "1 risk factor(s): open high/critical security alerts.",
"risk_summary": "1 risk factor(s): blocking GitHub security findings.",
"doctor_gap": false,
"context_risk": false,
"path_risk": false,
Expand Down Expand Up @@ -10449,7 +10449,7 @@
"no-run-instructions",
"active-high-severity-alerts"
],
"risk_summary": "4 risk factor(s): weak context quality, investigate override active, run instructions missing, open high/critical security alerts.",
"risk_summary": "4 risk factor(s): weak context quality, investigate override active, run instructions missing, blocking GitHub security findings.",
"doctor_gap": false,
"context_risk": true,
"path_risk": true,
Expand Down Expand Up @@ -10812,7 +10812,7 @@
"risk_factors": [
"active-high-severity-alerts"
],
"risk_summary": "1 risk factor(s): open high/critical security alerts.",
"risk_summary": "1 risk factor(s): blocking GitHub security findings.",
"doctor_gap": false,
"context_risk": false,
"path_risk": false,
Expand Down Expand Up @@ -11174,7 +11174,7 @@
"risk_factors": [
"active-high-severity-alerts"
],
"risk_summary": "1 risk factor(s): open high/critical security alerts.",
"risk_summary": "1 risk factor(s): blocking GitHub security findings.",
"doctor_gap": false,
"context_risk": false,
"path_risk": false,
Expand Down Expand Up @@ -11530,7 +11530,7 @@
"risk_factors": [
"active-high-severity-alerts"
],
"risk_summary": "1 risk factor(s): open high/critical security alerts.",
"risk_summary": "1 risk factor(s): blocking GitHub security findings.",
"doctor_gap": false,
"context_risk": false,
"path_risk": false,
Expand Down Expand Up @@ -13057,8 +13057,10 @@
"remote_default_branch_unavailable_count": 22,
"coverage_state": "partial",
"repos_with_open_high_critical": 16,
"total_open_high": 133,
"total_open_critical": 5
"total_open_high": 137,
"total_open_critical": 6,
"total_open_secrets": 1,
"security_admission_schema_version": "SecurityAdmissionV1"
},
"decision": {
"decision_needed_count": 13,
Expand Down
Loading