Skip to content

refactor(cli): reporting, feeds, drift and rescan worklist on traust-core (XWING-2440) - #285

Merged
smith-xyz merged 2 commits into
traust-security:developmentfrom
smith-xyz:refactor/traust-reporting-sweep-on-core
Oct 27, 2026
Merged

smith-xyz merged 2 commits into
traust-security:developmentfrom
smith-xyz:refactor/traust-reporting-sweep-on-core

Conversation

@smith-xyz

@smith-xyz smith-xyz commented Oct 27, 2026 •

Copy link
Copy Markdown
Contributor

Plan: plans/traust-standardization/0-continuous-ops/in-progress/17-shaun-traust-core-only.md (part)

Wave 2, lane (d). Stacked on traust#279 (lane a, refactor/traust-core-loader @ 9189e08): until #279 merges this diff includes its commit; review only the top commit 446caa1. Pins traust-core to traust-core#222 @ 15db382 (stacked on core development beb96f1, the pin lane (a) uses).

Chain: traust#279 (lane a) → traust-core#222 → this PR.

What

  • traust-core#222 (small core PR, needed here): RenderFormat moves to models/artifacts.py (the consumer ratchet allows only context/models/errors), and a new Context.reporting cached property builds the ReportingService without the record store. services().reporting hands out that same instance. Without it, the four file-only commands below would have created or opened the record store on every run.
  • cli/groups/reporting.py → ctx.reporting. No engine import left.
    • validate → validate_reports(path, strict=, schema=). --schema keeps its file-name form: triage.schema.json, contracts/triage.schema.json and triage all map to the contract schema triage. Output (PASS/FAIL lines, totals, exit codes, the "No .json files found" / "Path not found" lines) is unchanged.
    • lint → lint_threat_models(paths, strict=). traust.context.load() already registers the pinned ATT&CK catalog, so ids are checked against the table, as the engine's linter did when it could import attack_refs.
    • render → render(ThreatModelArtifact | ReportArtifact, RenderFormat.MARKDOWN), chosen by the -threat-model.json name as before. The section-loss guard uses core's lost_sections, and traust's local copy (_section_bodies, lost_sections) is deleted.
    • sarif → sarif_reports(root) for the sweep, and sarif(CloudConfigAuditArtifact | ReportArtifact) per report. The cloud-config arm is still picked by content (metadata.assessment_mode == "declared" or facts_ref), as the engine exporter did, so a cloud-config -findings-current.json still exports through the declared arm.
  • cli/fetch_feeds.py → core Locations: locations(load(home)).product_definitions replaces traust_engine.locations.product_definitions_url(configured_locations()).
  • cli/check_drift.py:
    • The engine-bundled compliance data (traust_engine/data/compliance/: NIST 800-53 rev5 spine, 800-53B profiles, PCI/SOC2/GDPR id catalogs, compliance-mapping.yaml, provenance.json, README) moves into packs/security/compliance/ as pack content, read by path. The files are byte-identical to traust-engine 7c6aa58. The engine keeps its copy until wave 3 deletes it.
    • The yara/argus pin rows read core's provider modules (traust_core.v1.providers.tools.{yara,opengrep}), since core runs those tools now. The pins are identical to the engine's. The refresh text names packs/security/compliance/README.md.
  • cli/build_rescan_worklist.py: core's strict HttpsRepoUrl would break shadow-run parity (it's https-only, the whole string must be the URL, and the host must be in forge_hosts). So the engine's normalize_repo_url moves in verbatim as the private _normalize_repo_url, marked # retires with this script at cutover (plan 18, Live runs). This is a deliberate, scoped exception to "no engine code in traust" (supervisor-approved): the script is legacy, kept only so the shadow run can diff against it. It has a new table-driven test.
  • Left on the engine, re-marked (supervisor: moves only; it's Shaun's call to retire or port them):
    • sweep collect/draft/sweep/emit/benchmark/rule-lane → PENDING(decision: sweep stages — retire or port). Core has no door for them; the engine's sweep package is file-store based.
    • portfolio build/freshness/interfaces/symbols/artifacts/stats/query → PENDING(decision: portfolio graph layers — retire or port). These are the engine's SQLite graph layers L0–L4; the portfolio-graph port replaced only blast radius (dependents/dependencies).
    • sweep verify-draft → NEED(core: an opengrep door that scans with one given rule file). Core's registered opengrep tool scans with the registered pack only.
    • DEFAULT_SWEEP_LIMIT / SWEEPS_REL stay with the engine ops that use them: separating them would only copy engine constants.
  • Ratchet baseline shrinks: engine-internal build_rescan_worklist.py 1→0, check_drift.py 1→0, fetch_feeds.py 1→0, groups/reporting.py 4→0. tests/test_engine_imports.py shrinks the same four entries. Nothing grew.

Behaviour changes

  1. reporting render refuses a document its contract schema refuses (exit 2, render: ERROR: <schema>: N issue(s); first: …, nothing written). Before, the engine rendered any JSON.
  2. reporting sarif refuses a schema-invalid report (ERROR: <path>: …, rc 1, the run continues). Exported SARIF uses core's naming (core refactor(ops): emit registered reason codes and retain diagnostic logs #213): driver traust (was traust-engine), run properties traust/* (were harness/*), rule text "Finding category" (was "Harness finding category").
  3. reporting validate --schema names contract schemas only. A schema file outside traust-contracts can no longer be loaded by path: its file name maps to the contract schema of that name, or the run exits 2 with core's ConfigError (before: a traceback for an unknown name).
  4. reporting validate|lint|render|sarif read the deployment config through core (traust.context.load). An invalid core.yaml exits 2. sarif now resolves locations.sarif_tool_uri from --config-home (before: $TRAUST_CONFIG_HOME only).
  5. feeds fetch-products reads locations.product_definitions from --config-home (before: $TRAUST_CONFIG_HOME only). It always loads the config, even with --cache-dir.
  6. check drift pin rows read core's provider modules. Their refresh hint names the core module to bump.

Ops still on the engine (exact scope for the pending decisions)

Op Code Documented in
sweep collect src/traust/cli/groups/sweep.py:100 harnessing/mine-ledger/SKILL.md:160
sweep draft sweep.py:128 mine-ledger/SKILL.md:165
sweep sweep sweep.py:168 mine-ledger/SKILL.md:169
sweep emit sweep.py:200 mine-ledger/SKILL.md:171
sweep benchmark sweep.py:250 harnessing/5-validate/validate-findings/SKILL.md:139; docs/tooling-and-structure-files.md:67; docs/continuous-operations.md:145
sweep rule-lane sweep.py:294 mine-ledger/SKILL.md:13,73,194,236,260,286; docs/error-model.md:158-159; docs/tooling-and-structure-files.md:79; docs/continuous-operations.md:112,139; harnessing/drift-watch/SKILL.md:63
sweep verify-draft (NEED) sweep.py:508 —
portfolio build src/traust/cli/groups/portfolio.py:110 harnessing/portfolio-graph/SKILL.md:124,164; .claude/commands/portfolio-graph.md:5; docs/continuous-operations.md:151
portfolio freshness portfolio.py:203 portfolio-graph/SKILL.md:103
portfolio interfaces portfolio.py:229 portfolio-graph/SKILL.md:154
portfolio symbols portfolio.py:257 —
portfolio artifacts portfolio.py:298 —
portfolio stats portfolio.py:326 portfolio-graph/SKILL.md:169
portfolio query portfolio.py:360 portfolio-graph/SKILL.md:194,197,201

Reviewer checks

  • Each moved piece lands on the core owner the api-spec names (ReportingService via ctx.reporting, core Locations, core provider pins); no engine code copied into traust except the approved _normalize_repo_url (retires with the script)
  • Security content (compliance data) is pack content read by path

Quality checklist

  • 1. Comments only for the non-trivial
  • 2. Docstrings don't bloat
  • 3. Don't reinvent: core's lost_sections, sarif_reports, validate_reports, lint_threat_models; traust's copies deleted
  • 4. Shared logic in one place
  • 5. Tests cover the edges: schema-name mapping (file name, path, bare), unknown schema exit 2, empty dir / missing path, schema-invalid render refused with nothing written, schema-invalid SARIF refused, cloud-config SARIF arm, results-root sweep, URL normalisation edge cases (autolinks, prose, .git, trailing dot, http, no URL, ext::)
  • 6. Integration tests: full suite on SQLite + Postgres (traust_test_w2_d)
  • 7. Minimal, clean code
  • 8. Queries: none added
  • 9. Tests change only on purpose: listed below

Changed tests

Test Old → new Why
tests/test_render_section_guard.py monkeypatched reporting.load_engine with a fake facade rendering through traust_engine.reporting.render → calls call_render for real (core render); test_lost_sections_names_what_would_go uses load().reporting.lost_sections and core's render instead of traust's deleted lost_sections and the engine renderer. Assertions unchanged render and lost_sections are core's now; traust's copies were deleted
tests/test_fetch_feeds.py autouse fixture patched ff.locations.configured_locations (engine) with a contracts Locations → writes a locations.yaml with product_definitions into a tmp config home (TRAUST_CONFIG_HOME); the "absent" test deletes that file instead of patching. Assertions unchanged the URL comes from core Locations via load()
tests/routing_reference.py (helper, not an assertion) exec namespace stub normalize_repo_url → _normalize_repo_url the function is now private in build_rescan_worklist.py
tests/test_engine_imports.py baseline drops build_rescan_worklist.py, check_drift.py, fetch_feeds.py, groups/reporting.py the imports are gone (baseline only shrinks)

New: tests/test_reporting_group.py (13 tests); test_normalize_repo_url_takes_the_first_url_out_of_report_prose (10 cases) in tests/test_build_rescan_worklist.py.

  • A person reviewed each changed test and the behaviour change is intended

Validation

  • make lint-fix && make test: 2718 passed, 2 skipped (Postgres traust_test_w2_d)
  • uv run traust check docs-consistency: consistent
  • Consumer ratchet exactly as CI (e0b2ddd, reference upstream/development): clean
  • Pre-commit and pre-push hooks passed

Left for later lanes

  • reporting rate-threats still imports traust_contracts.v1.risk_rating (contracts 1 in .ratchet.json). No lane names it, and core has contracts.risk_rating but no consumer-reachable door.
  • Skill and doc text naming engine modules, for lane (e)/(f) housekeeping: harnessing/threat-register/SKILL.md:18, 3-audit/cloud-config-audit/SKILL.md:19, 3-audit/secure-container-audit/SKILL.md:31 (allowed-tools naming traust_engine.reporting.*); 3-audit/compliance-check/SKILL.md:31 (compliance data "shipped with traust-engine"); docs/risk-rating-methodology.md:112; config/README.md:40,50.
  • tests/test_check_drift.py::test_argus_pack_is_not_the_default_rules_source still reads the engine adapter's source, and test_rule_pack_pins_parse_from_the_engine_adapters keeps its name though it now parses core's modules (unchanged tests).

@smith-xyz
smith-xyz requested a review from a team as a code owner October 27, 2026 16:40
@openshift-ci

openshift-ci Bot commented Oct 27, 2026

Copy link
Copy Markdown

[APPROVALNOTIFIER] This PR is APPROVED

This pull-request has been approved by: smith-xyz

The full list of commands accepted by this bot can be found here.

The pull request process is described here

Details Needs approval from an approver in each of these files:

Approvers can indicate their approval by writing /approve in a comment
Approvers can cancel approval by writing /approve cancel in a comment

@openshift-ci openshift-ci Bot added approved Indicates a PR has been approved by an approver from all required OWNERS files. needs-rebase Indicates a PR cannot be merged because it has merge conflicts with HEAD. labels Oct 27, 2026
@openshift-ci openshift-ci Bot removed the needs-rebase Indicates a PR cannot be merged because it has merge conflicts with HEAD. label Oct 27, 2026
@smith-xyz
smith-xyz merged commit 9d8bf8d into traust-security:development Oct 27, 2026
4 of 5 checks passed
smith-xyz added a commit to smith-xyz/traust that referenced this pull request Oct 27, 2026
smith-xyz added a commit to smith-xyz/traust that referenced this pull request Oct 27, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

approved Indicates a PR has been approved by an approver from all required OWNERS files.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant