The shared Python base for traust: one clear place to read and write each kind of data, so scripts stop opening their own database connections, building their own paths, writing their own JSON and running their own subprocesses. Python callers (traust, traust-engine) use it in-process; other languages (the Go SDK, SCI) will reach the same services through the REST API served from here (resource/ID routes, request IDs, OpenAPI generated from the core models).
All modules live under traust_core.v1 (see Versioning).
| API | Module | Use it for | Instead of |
|---|---|---|---|
| Contracts | contracts |
the pinned traust-contracts dependency, read as data: validate(name, doc) (exact JSON Schema gate), schema_errors(name, doc) (every violation as path and message), risk_rating (the contracts' OWASP Risk Rating arithmetic: rate, problems), validate_config(name, doc) (the config/v1 gate: packs, policies), schema(), schema_dir() / schema_path(name) (the artifact schema files), enum_values(), normalize_enum(name, value) (registry read view), database_ddl(dialect) (every namespace, bootstrap order) |
skills remembering to run reporting validate |
| Errors | errors |
the exception hierarchy every layer raises (ValidationError, NotFoundError, …) |
ad hoc exceptions |
| Models | models |
one model per contract table, grouped by section — inventory, dependencies, assessment, findings, operations, ledger (fields = columns) — next to that section's service types; base (Model strict, Dto tolerant, Entity with a database-generated UUIDv4 id, AppendOnly for tables that are never updated), values (HttpsRepoUrl, GitSha, CveId, to_rfc3339 / TimestampError: the contracts' one RFC 3339 rule), integrity |
dicts passed between scripts |
| Validation rules | models.validation |
ValidationScope.permits(action, target, as_of=…), ValidationPlan.requires_approval(), ValidationAttempt.soundness() — pure scope, approval and refutation rules |
duplicated harness scope and soundness rules |
| Validation recording | services.validation, models.validation |
record_order(doc, provenance, bindings, run_order_id, principal) binds machine reports to a live order lease; trusted-local record(doc, provenance, bindings) and evidence(artifact_id) retain their existing semantics |
re-running probes, interpreting source labels as database IDs |
| Artifacts | models.artifacts |
the ArtifactDocument base + SchemaView (fields from the installed schema, doc.findings[0].verdict, unknown names raise) and the named documents (TriageArtifact, SecurityAuditArtifact, …, Verdict, Severity) |
json.load + .get("key") |
| Artifact publishing | services.artifact_publishing |
ctx.artifact_publisher(specs).publish(name, raw) or .publish_artifact(artifact): exact gate → content-addressed objects → an artifact UUID on SQLite and Postgres; get(artifact_id) verifies the persisted digest; list_for_assessment(id, kind=…) reads metadata |
writing analysis-results files by hand or optional indexing |
| Code facts | services.code_facts, models.code_facts, providers.crypto, providers.crypto_image, providers.crypto_cluster, providers.route_guards, providers.route_ownership, providers.django_routes, providers.python_guards, providers.express_routes, providers.java_routes, providers.python_ownership, providers.express_ownership, providers.ruby_ownership, providers.rust_ownership, providers.syntax, providers.grpc_guards, providers.llm_app, providers.config_matrix, providers.sanitizers, providers.fork_lag, providers.priv_profile, providers.tree, models.elf_formats, models.crypto_formats |
ctx.services().code_facts.collect/list/symbols/language_stats over the latest tool_scan assessment's CodeFactsArtifact; measured LoC, exact symbol filters and snapshot-bound pagination. code_facts.enumerate(kind, ToolRequest) runs a deterministic FactEnumerator over a checkout (CryptoProbe: toolchains, images, policies a checkout configures; the secure-code pre-scans RouteGuardMatrix (Go, Django and DRF through each urlconf's include() chain and routers (providers.django_routes), Flask with its blueprint and API prefixes, FastAPI, aiohttp; a Django, DRF or Flask guard counts only when it is a known auth library guard that no checkout definition shadows, never checkout decorators, mixins, permission classes or hooks (providers.python_guards); Express, Spring and JAX-RS parsed with tree-sitter grammars loaded by providers.syntax), GrpcGuardMatrix (Go gRPC servers, their interceptors, the services registered on them and their methods), LlmAppDetector (whether a checkout is an LLM, agent or MCP application and which files use it, from imports, direct manifest declarations and prompt templates), ConfigMatrix, SanitizerDiscovery and ForkDetector, over providers.tree's symlink-safe file walk); code_facts.route_guards(ToolRequest, resolve_handlers=) places handlers through the symbol index; route rows carry ownership on authenticated id routes (providers.route_ownership: scoped, checked, caller-blind, unchecked, none, unknown or n/a, with the spans behind it; caller-blind and unchecked rows are judged; filled for Spring and JAX-RS from each handler's path variables, caller, lookups by id, checks and one same-repository hop, for FastAPI (providers.python_ownership) from the handler and the dependencies handed a path id, for Express (providers.express_ownership) from the handler, the checkout middleware in its chain and the functions it calls, and for Rails (providers.ruby_ownership) from the action, its before_action callbacks, models, Pundit policies and the CanCanCan Ability, and for axum and actix-web (providers.rust_ownership, which judges the handler's reads by id but never credits a route as protected) from the handler, its auth extractors and one same-crate hop, through sqlx, diesel, sea-orm and in-memory stores); code_facts.grpc_guards(ToolRequest) is the gRPC service x interceptor matrix; code_facts.coverage(ToolRequest, repository_ref_id=) compares the repository's latest threat-model entry points (assessment.threat_model_entry_points) and what the pre-scans detected against the pre-scan for each entry-point kind, as covered, uncovered or unexplained; code_facts.llm_app(ToolRequest) is the LLM-application detection that gates the LLM review; code_facts.priv_profile(ToolRequest, repo) is the operator privilege profile (PrivProfiler: SCC requests, securityContexts, namespaces and install modes, every RBAC rule, and the shipped RBAC no +kubebuilder:rbac marker declares) as the contract PrivProfileArtifact; code_facts.fork_lag(ToolRequest, offline=) adds each tracked upstream version's advisories (exposure.package_advisories), a failed read being the result's network error; code_facts.crypto(ref_id, ToolRequest) adds the crypto packages of the ref's ingested manifest SBOM (services.crypto_packages.CryptoPackagePolicy over exposure.dependencies; NoManifestSbomError when none was ingested). code_facts.image_crypto(digest, ImageRequest) is the image tier (podman/docker inspect, create and cp through ProcessRunner, never running the image; DT_NEEDED from models.elf_formats) plus the crypto packages of the image's SBOM (exposure.image_dependencies; NoImageSbomError); code_facts.cluster_crypto(ClusterRequest, ValidationScope) is the cluster tier (oc exec, openssl s_client parsed by models.crypto_formats), every exec namespace gated by ValidationScope.decide(k8s, exec) and logged. Nothing is recorded: the facts are re-derivable from the commit |
source scans at read time, a side symbol database, byte-based LoC estimates, traust_engine.adapters.crypto_probe, crypto_audit image/cluster tiers, _util/elf.py (linked libraries), traust secure-code-audit enumerate_route_guards.py, traust_engine.adapters.llm_app, expand_config_matrix.py, probe_sanitizers.py discovery, run_fork_advisory_lag.py, operator-priv-profile build_priv_profile.py |
| Detection quality | services.detection_quality, services.rule_mining, models.detection_quality, models.rule_mining, models.validation_benchmark, providers.rule_drafts |
ctx.services().detection_quality: record(BenchmarkTargetArtifact, provenance) stores the recall-benchmark answer key (newest version scored; duplicate ids and held-out seeded targets refused); run_benchmark({target_id: [assessment_id, rerun_id]}) scores each benchmark_audit report with the match ladder (fingerprint → advisory id + path → CWE + path ≥ 0.5 → near miss), injection canaries and rerun stability, score_reports({target_id: [report, rerun]}, auditor_version) scores unrecorded reports (canary runs on local repositories) the same way, derived on demand and storing nothing; scorecards(BenchmarkFilter) scores every recorded benchmark audit per auditor version; BenchmarkRun.scorecard() cuts recall by CWE/severity/provenance/language/split; screen_injection(artifact_id) flags obeyed-injection shapes in an audit report; mine_rules(RulePack) clusters confirmed findings by (primary CWE, language) against a pack, with per-rule precision from occurrence.rule and the calibration worklist; regression_pairs(RegressionPairFilter) gives each confirmed finding a decision recorded fixed on a later commit its files on the commit last reported and on the fix commit (read at the forge; nothing stored), the fixtures of a regression-rule draft; sweep_cards(CardDeck.parse(doc).cards, ToolRequest) runs each semantic precedent card's code-facts enumerator over a checkout and returns the rows its predicate matches as triage candidates (cards that are prose, not recipes, are rejected by CardDeck.parse with every reason); the class-generalization sweep: sweep_collect(force) classifies every confirmed finding rule-expressible or not (with why) into (CWE, language) classes, sweep_drafts() stages a candidate rule per class no pack rule or held draft covers, sweep_rule(RuleSweepRequest) runs one authored rule over the repositories with a completed secure-code audit on their default line (resumable per repository), emit_sweep(rule, force) turns the hits into triage candidates (never findings) with a summary, each stage's state kept in the object store; propose_rule(RuleProposal) is the gate for a rule inference proposes for an uncovered shape (services.rule_proposals: one well-formed rule, no catch-all pattern or path glob, no backreference, lookbehind or nested quantifier, its ruleid:/ok: examples firing exactly, and a capped hit count on the checkout); an admitted proposal is staged as a draft for the same calibrate-and-sweep path; stage_regression_drafts(filter, skip_existing) stages regression_pairs as drafts; rule_lane(RuleLaneRequest) runs the weekly rule-mining lane (mine → collect/draft → regression drafts → delta against the previous run, Stage-A gate reachability per watched pack, allowlisted-rule precision) with RuleLane.exit_code 0 quiet, 1 attention, 2 failed; drafts are written to the adopter's locations.rule_drafts by providers.rule_drafts.RuleDraftDirectory (ctx.register_rule_drafts(root)); the validation-lane benchmark: benchmark_worklist(ValidationBenchmark) (claims, validity unanswered), score_benchmark(bench, run, variant, report) onto benchmark_scorecard(), benchmark_trigger(ToolRequest, since) |
recall-benchmark/{match_benchmark,screen_injection}.py, traust_engine.sweep.{mining,engine,rule_lane,benchmark,calibration}, traust ops/compile_precedent_cards.py, mine-ledger emit_rule_drafts.py |
| Tool runs and audits | services.assessment, models.cloud_config, services.cloud_config |
assessment.run_tools(checkout, [CHECKOV]) over ctx.tools (every tool ready first); CloudConfigFacts.of(CheckovScan) turns Checkov output into cited facts, fact ids, gaps and a snapshot id; assessment.check_audit(doc) then record_audit(doc, ref, provenance): the report gate, a cloud-config assessment (degraded with gaps), every finding it states and its judgment on each as a decision (FindingDecisions.record_assessed) |
run_checkov.py's normalizer, gap register and --validate-report |
| Table repositories | repositories.traust.*, repositories.traust_ops, repositories.traust_ledger, repositories.contract |
one repository per contract table (uow.findings.find_by_fingerprint(...), uow.run_orders.claim(...)); SqlTraustUnitOfWork(engine) opens all 33 on one connection — one transaction across sections |
sqlite3.connect, inline SQL, per-script tables |
| Product definitions | models.product_definitions, providers.product_definitions, services.portfolio |
ctx.product_lookup.resolve_products(ctx.product_definitions(cache_dir).read(), mappings, packages=, repo_urls=, product_ids=) (never opens the record store; ctx.services().portfolio.resolve_products is the same answer; mappings is the recorded product map, portfolio.product_mappings(), recorded with portfolio.record_product_map(doc, provenance)): ownership context from the cached product-definitions registry, a candidate generator never an owner: match tier (mapped > repo-url > advisory slug > none), the contact ladder (only private_tracker_cc is embargo-cleared; non-kerberos values dropped and recorded), the lifecycle window on the clock's day; a missing or malformed cache is unavailable, never no-match. Serialise with model_dump(mode="json", exclude_unset=True) |
traust_engine.registry.products, registry/feeds.py (reads) |
| Repository observation | providers.forge, services.observation, services.portfolio |
ctx.repository_observation() records forge facts as repo-state assessments; PortfolioService.default_ref() resolves the current head from the latest completed default-branch observation, including when a branch returns to an earlier SHA; without one, work needing the current head requires observation first. RepoStateQuery.repo_state() derives exposure from component membership, repository visibility and upstream, plus risk tier and freshness from current domain data |
forge scraping or adopter designation config inside Core |
| Records (pattern) | repositories.sql, repositories.tables |
TableSpec (columns once) + TableFactory (Postgres schemas or SQLite ops_/ledger_ prefixes, by engine) + SqlTableRepository (add/get/update) on SQLAlchemy Core; sqlite or Postgres by URL. record_columns() (mutable: UUIDv4 id + created_at + updated_at) / append_only_columns() (UUIDv4 id + created_at, no updated_at) + _insert_record/_update_record, database-generated via RETURNING; updated_at kept by the contracts touch trigger, SQLite uuid() registered by create_database_engine |
sqlite3.connect, inline SQL, client-minted ids/timestamps |
| Object store | repositories.objects.store |
ObjectStore.put/get/list raw bytes by key, digest-verified (local dir, or repositories.objects.s3.S3ObjectStore with its typed S3Client port and the [s3] extra, selected with artifacts.s3 in traust.yaml or injected as Context(config, object_store=...)). Startup and ambiguous-404 rechecks use a one-key listing within the configured prefix; bucket loss raises RepositoryError (ConfigError at startup), never a cache miss. The IAM policy needs s3:ListBucket for that prefix besides Get/Put; a missing-key 403 is checked by scoped listing, while denied existing keys remain failures. Custom HTTP endpoints are allowed only for loopback (localhost, 127/8, ::1); remote endpoints require HTTPS |
write_text, json.dump |
| REST API | rest (traust-core[rest]) |
traust-server / create_app(context): routes only, one package per domain (rest.ledger, …, listed in rest.domains), shared plumbing in rest.common; bearer token → Principal (Caller), one unit of work per request (CoreServices), typed errors → problem+json, X-Request-ID / traceparent on every response and access-log line; OpenAPI from make api-check. Nothing else in core imports it |
a REST service per repo, each with its own auth, errors and config |
| Model registry routing | rest.registry, providers.model_catalog |
Authenticated GET /v1/registry/resolve?role=…&tier=… → RoleRouting: the role's resolved model, the provider section listing it and that provider's ConnectionConfig (type, base_url, harness, CredentialConfig; supported: false while the SCI worker cannot serve the type — gemini today) — the same resolve_routing in-process callers get; unknown role 404, unresolvable 422 |
each executor parsing the registry's connection blocks and re-implementing the default routing |
| Explicit recording starts | rest.ops, services.operations |
POST /v1/orders/start: StartRecordingRequest (order, lease_seconds) → OpsService.start_recording → existing OpsService.start → native leased RunOrder; verified machine identity and server-owned principal/asset/repository capabilities, no request-supplied worker |
a second queue, machine approval, caller-promised recording permissions |
| Authored secure-code records | rest.assessments, services.assessment |
POST /v1/assessment-records: AssessmentRecordRequest → AssessmentService.record_secure_code → exact canonical report, native assessment/artifact IDs and unjudged IntakeResult from existing finding intake |
scanner relabeling, report-authored validity decisions, changing plain record |
| Assessment intake bindings | rest.assessments, services.assessment, services.findings.decisions |
Authenticated GET /v1/assessments/{assessment_id}/intake → mapping-only AssessmentIntakeBindings; digest-verified secure-code report labels or triage row labels resolve only through their owner and assessment/baseline occurrence scope |
receipt-trusted UUIDs, manufactured creation history, source-title matching |
| Latest recorded assessment | rest.assessments, services.assessment |
Authenticated GET /v1/repositories/{repository_id}/assessments/latest?kind=… → AssessmentService.latest_recorded → one typed Assessment: newest completed or degraded result across all refs and kind aliases; required kind, 404 for no match or unknown repository |
consumer-side assessment-history walks, pending or failed results replacing recorded evidence |
| Exact finding identity read | rest.findings, services.findings |
Authenticated GET /v1/repositories/{repository_id}/findings/by-fingerprint?fingerprint=…&fingerprint_algo=… → FindingService.get_by_fingerprint → typed FindingView; opaque exact fingerprint required, algorithm optional; 404 for an absent match or unknown repository, 409 when an omitted algorithm is ambiguous |
consumer-side finding-history walks, title/ordinal matches, silently picking one algorithm |
| Artifact reads | rest.artifacts, services.artifact_publishing |
Authenticated GET /v1/artifacts/{artifact_id} → native Artifact metadata; GET /v1/artifacts/{artifact_id}/content → digest-verified original bytes through ArtifactPublisher.metadata / payload |
consumer report reserialization, obsolete SDK schema revalidation, direct object/database access |
| Verification records | rest.remediation |
POST /v1/verification-records: exact UTF-8 report text, native Provenance, and run_order_id → VerificationService.record_order → native Verification; a verified machine must own a live lease for the report repository and a server-allowed verification asset |
event envelopes, caller-supplied actor/signer, human countersigns |
| Remediation records | rest.remediation, services.remediation |
POST /v1/remediation-records: exact report bytes, provenance and a live remediate_finding lease; the order's named work item scopes recording and replay |
accepting a different attempt or a triage lease as remediation authority |
| Validation report REST | rest.validation |
POST /v1/validation-records → services.validation.record_order; live leased run_order_id, exact report text, Provenance, native ValidationRecordInput bindings → ValidationRecordResult |
source-label lookups, route-owned authorization or verdict rules, human decision commands |
| Reporting | services.reporting, providers.report_files |
ctx.reporting (the same instance as ctx.services().reporting; it never opens the record store, so file-only callers use it) over a report document the caller holds, nothing stored: validate(payload, XArtifact) → contract Issues; validate_reports(path, strict=, schema=) → a ReportValidation (errors, warnings) per report file at path (a directory sweeps its .json files; the schema comes from each file name unless a contract schema is named): the contract schema, then the family's content checks (services.reporting.checks: counts against findings, canonical ids, metadata.repository, dispositions, review surfaces, OWASP threat ratings) and, with strict, the audit-report warnings; lint(doc, files=None) runs every core Lint registered for the type; render(doc, RenderFormat.MARKDOWN | HTML) (triage in both; security reports and threat models as Markdown: services.reporting.report_markdown, services.reporting.threat_model); lint_threat_models(paths, strict=) lints THREAT_MODEL.md prose (threat_model.lint_threat_model, OWASP cells via services.reporting.threat_rating; ATT&CK and ATLAS ids against the registered catalogs, which the pack always pins; without them the door raises naming the register doors); lost_sections(existing, rendered) (the render guard); sarif_reports(root) is the SARIF sweep's report selection; sarif(doc) for report and cloud-config audits (SarifExport.report, locations.sarif_tool_uri as informationUri). ctx.services().redaction.redact(payload) scrubs secrets (RedactionPolicy; scan(text) finds them without redacting) |
traust_engine.reporting (validate, lint, render, sarif), _util/redact.py |
| Rendering | rendering |
Report → MarkdownRenderer → publish(); markdown_table(); markdown_text() escapes a structured cell as plain text (no HTML, links or inline formatting) |
18 copies of render_md |
| Detection-quality and compliance renderers | rendering.benchmark, rendering.compliance, rendering.compliance_precision |
benchmark_markdown(run) (scoring a runs dir; recorded scorecards are the benchmark-scorecards view) and compliance_precision_markdown(calibration) render typed results with untrusted cells escaped; rendering.compliance.assessment_report(document) → ComplianceService.render_assessment(document) Markdown bytes |
consumer-owned report renderers |
| Compliance field precision | models.insight, services.insight |
insight.publish(ViewName.COMPLIANCE_PRECISION, ViewFilter(framework=…, precision_gate=…)): native calibration under calibration, with shared Markdown/HTML and a packaged view schema |
standalone Markdown helpers or consumer-owned precision calculations |
| Secret detection | models.redaction |
SecretDetector.publication_rules() / evidence_rules() yield ordered SecretRules whose spans(text) return exact SecretSpan(tag, start, end) offsets; RedactionPolicy publishes through the same catalog, so it is the one owner of secret patterns |
a consumer-owned credential regex table |
| Verification worklists | services.remediation, models.remediation, services.findings |
verification.sweep(repository_ids, include_branches=False) keeps repository/ref identity, branch head source and line-specific counts; sweep_markdown(sweep) renders the same queue; findings.on_ref(repository_ref_id) folds resolution on that ref's line |
a second worklist store |
| PQC worklist | services.pqc, models.pqc, services.insight |
pqc.worklist(PqcFilter(...), chokepoints=(...)) owns the existing per-repository selection, ordering and routes; insight.publish(ViewName.PQC_WORKLIST, ViewFilter(...)) adds the contracts-owned model and shared human rendering |
client-side sweep ranking or consumer-owned report formatting |
| Inventory export | services.portfolio, models.inventory, services.insight |
portfolio.inventory_export(max_records=…) composes current ownership, shipping products and cached product-definition contacts; insight.publish(ViewName.INVENTORY_EXPORT, ViewFilter()) returns the canonical model and shared Markdown/HTML/CSV |
client-side per-repository joins or consumer-owned inventory renderers |
| Historical report signatures | services.reporting, services.ledger, models.ledger |
validate_reports(..., signing_pubkey=Path(...)) requires a historical layer document's Merkle tree and public-key signature through LedgerService.verify_document; ReportValidation.integrity holds the typed DocumentVerification |
trusting an unsigned layer |
| Jobs | models.operations, services.operations |
Materializer.materialize(AssetRequest) -> JobResult (succeeded · degraded · refused · failed): the seam traust-engine calls; Router for continuous-ops route() |
each CLI inventing its own exit codes |
| Providers | providers |
the outside world: base (Provider, Readiness, Provenance, registry), tools, sources, feeds (FeedProvider, HttpFileFeed for single-file feeds, validate_payload), http (HttpFetcher + RetryPolicy, the outbound HTTP path), correlation (correlation_scope: scoped request/order/run IDs passed to HTTP and subprocesses), depsdev (DepsDevResolver: deps.dev graphs and default versions through the feeds registry's deps_dev source, answers cached a day in the object store; ctx.deps_dev), process (ProcessRunner, the only subprocess path), report_files (ReportFiles: the report files a caller names on disk), clock, model_catalog (ctx.model_catalog: list prices and cost, plus roles: list_roles(), resolve_role(role, tier) (never below the floor), resolve_routing(role, tier) (the model plus its provider section and that provider's connection — wire type, base_url, harness, credential source — the default, the Anthropic wire and the environment's credential, when the section declares none; an unroutable connection, or a model id two provider sections list, refuses the config load), validate(), stamp(role, model) for a report's model_routing), transcripts (ctx.session_transcripts.source(workspace, skills): Claude Code transcripts as a SpendSource for SpendService.collect and usage_summary, each request's skill inferred from the session's signals) |
ad hoc subprocess/git/fetch code, 22 private run wrappers |
| Tools | providers.tools, providers.tools.toolchain, providers.tools.safe_exec, providers.tools.elf, providers.tools.adapter, providers.tools.fleet_fix, providers.tools.compliance_sources, providers.tools.detection_quality, providers.tools.opengrep, providers.tools.osv_manifests, context.tools |
ctx.register_toolchain(external_tools, safe_exec_profiles) reads both files on first use; ctx.toolchain (Toolchain: external-tools.yaml's ToolPins, version, database, populate, check / preflight and preflight_failures, freeze_env / freeze_flags) probes only the pinned binaries; ctx.safe_exec (SafeExec: the deployment's profiles, validate_argv, vet_command_string, run, run_segments through ProcessRunner, pipelines with no shell); ElfAnalyzer (header, linked libraries through models.elf_formats, byte patterns); AdapterTool wraps a scanner adapter's scan() as a ToolProvider, and core's scanners are registered by name from context.tools.scanner_tools, each one's floor the pin of its binary. ctx.register_fleet_fix() (GitFleetFixApplier: https-only clone, git ls-remote, ast-grep, diffs; also the golden runner), ctx.register_compliance_sources() (GithubAdrSource, RedHatDocSource) and ctx.register_detection_quality(pack_version, canary_sources, canary_repos) (OpengrepRulePacks over the pack ctx.register_rule_pack(pack, allowlist) registered and the rule-pack-allowlist supplements cloned under providers.tools.opengrep's cache, read on first use; GitCanaryMaterializer; OpengrepRuleSweep, one rule over many checkouts, shallow https clones through GitCheckout.clone) set the non-scanner providers; rule packs and allowlists stay pack config passed by path; osv_manifests holds the osv scan's lockfile and manifest readers (only providers/tools/osv* reads them: the portfolio SBOM is syft's) |
traust_engine.toolchain, _util/safe_exec.py, _util/elf.py, adapters/tools.py, adapters/{fleet_fix,compliance_sources,detection_quality}.py, portfolio/parsers.py (for osv), depsdev/ |
| Tool reports | context.tool_reports |
the documents traust adapters <tool> and traust util elf write, one function per tool, each on the binary and timeout the caller passes: opengrep_report, govulncheck_report, gitleaks_report, yara_report, k8s_hardening_report, joern_report, osv_report / osv_sbom_report (OSV API through ctx.advisories and ctx.deps_dev, or offline osv-scanner), elf_report; populatable_tools, toolchain_failures, tool_stamp for traust admin toolchain. Interim: the tool execution API reshapes them |
traust-engine adapters/* CLI entry points (_execute_opengrep_scan, _run_govulncheck, _run_gitleaks, _execute_yara_scan, checkov.Scanner, _run_joern_scan, osv.scan_api / run_scanner, osv_sbom.*), _util/elf.py |
| Code scanners | providers.tools.opengrep, providers.tools.govulncheck, providers.tools.joern |
registered as the opengrep, govulncheck and joern tools; each scan() returns an AdapterResultArtifact built by adapter.adapter_result. ctx.register_rule_pack(pack, allowlist=None) names the opengrep rule pack and the rule-pack-allowlist.yaml enabling its supplemental packs per rule (ctx.rule_pack, ctx.rule_pack_supplements; security content stays pack config, passed by path); opengrep.execute_scan is the full facts report (rules with pins and licenses, supplements applied and skipped, the allowlist, coverage by language), context.tools.load_rule_allowlist parses --rule-allow; govulncheck.run_govulncheck runs under the go-scan safe-exec profile and parse_stream gives one candidate per OSV at its most specific reachability; joern.run_scan is the call-site facts document over a SymbolQuery (with_soundness states the evidence asymmetry) |
traust_engine.adapters.{opengrep,govulncheck,joern} |
| IaC, secrets, malware scanners | providers.tools.cloud_config, providers.tools.k8s_hardening, providers.tools.gitleaks, providers.tools.yara |
registered tools checkov (CheckovProvider: the pinned Checkov, offline, as CloudConfigFacts), gitleaks / gitleaks-history (Gitleaks: secret-free candidates over the rules locations.gitleaks_config names) and yara (Yara: known-malware-family facts over a pinned rule pack); k8s_hardening.Scanner (the KHS-* manifest checks and tenancy signals); adapter_result builds any adapter's AdapterResultArtifact |
traust-engine adapters/cloud_config.py, checkov.py, gitleaks.py, yara.py, _contract_bridge.py |
| Deps and SBOM scanners | providers.tools.osv, providers.tools.osv_sbom, providers.tools.grype |
registered by scanner_tools: grype over a checkout or SBOM path, database frozen by default; osv, a checkout's declared packages looked up through ctx.advisories with coverage reported, osv.scan_api(..., resolver=ctx.deps_dev) for the full candidates document; osv_sbom, an image's syft JSON language packages not installed by an OS package, with mark_grype_duplicates / grype_os_installed_matches and the air-gapped osv-scanner path; CVSS through models.dependencies.cvss_base_score |
adapters/{osv,osv_sbom,grype}.py, osv/ (client: reuses providers.osv) |
| Config + Context | context, context.documents (the YAML codec: yaml_documents, yaml_documents_with_lines, yaml_text, passed to providers, which don't import yaml), context.preflight |
one traust.yaml; Context builds everything (ledger and identity sections). The deployment config home: resolve_config_home(explicit) ($TRAUST_CONFIG_HOME, else ~/.traust/config when it exists), config_path(name) (DeploymentConfigMissing naming the file and its template), optional_config_path(name), config_completeness_audit() ((errors, warnings) for install_traust --doctor). Context.preflight(requirements) (context.preflight): one readiness door the engine's start gate, the platform doors and traust doctor call — a Requirement (models.operations, the pack schema's requires: vocabulary: tools, config keys, recorded facts, feed sources, secrets) is checked against Toolchain.preflight, the traust.yaml keys Config.provided records, the recorded facts, the enabled feed sources, the environment and the config home audit, raising PreflightError with every gap and its fix; schema_dir() / schema_path(name) / validate_config(name, document) and the ops types consumers annotate with (OpsService, SpendService, FeedQuery, Router, Clock) re-exported for consumers |
os.environ, scattered config files, traust_contracts.config |
Repository authority reads stay server-owned:
GET /v1/repositories/{repository_id}/refs/current-default (OpenAPI
get_current_default_ref, generated client GetCurrentDefaultRef) returns the
exact RepositoryRef chosen by PortfolioService.current_default_ref, or 404.
Immutable historical default flags do not independently identify the current head:
the existing observation/default-naming order chooses its ID and SHA.
The latest-recorded assessment read joins repository ownership in SQL and returns
at most one assessment without loading or binding the repository's ref history.
Algorithm-free fingerprint reads use the contracts-owned
(repository_id, fingerprint) index, retaining exact opaque identity and ambiguity
across algorithms. The index is part of the static bootstrap schema; existing
databases require the separately designed upgrade feature.
POST /v1/remediations/{work_item_id}/pull-request (record_remediation_pull_request,
generated RecordRemediationPullRequest) accepts an ExternalRef (system, actual
HTTPS PR URL in ref) and returns WorkItem. A verified person records this
manual action; machine and unverified identities are refused. The attempt's
tracker and pr_opened progress commit together. Replaying the same PR is a no-op,
including after merge; a different PR conflicts. Finding-wide Jira tickets retain
their existing semantics, while PR trackers are scoped by assessment ID and never
spread to other attempts over the same findings. Immutable worker reports are not
rewritten. Historical links remain unscoped. Core validates pairing on tracker
inserts and scope changes and keeps finding repository identity immutable;
the schema retains one guard for repository-ref moves.
POST /v1/remediations/{work_item_id}/progress (record_remediation_progress,
generated RecordRemediationProgress) accepts a ProgressReport (status:
merged or abandoned, the merge commit as merge_sha, who ended it as actor)
and returns the moved WorkItem — the write path for a PR-filing loop that learns
of a merge or close outside any order lease. A verified person, or a machine
granted remediate_finding on the repository in recording.start, may call it
(OpsService.authorize_write, the same grant the scheduling door reads);
unverified and ungranted identities are refused. A run's own statuses still come
from its report. The ending decisions carry the merge SHA and actor as their
rationale; replays are no-ops, and a status the forward-only state machine refuses
answers 409.
RemediationService.record(report, provenance, diff=draft.diff) and the order-bound
record_order(..., diff=...) retain the exact patch as a companion artifact.
REST runners send optional UTF-8 diff text with POST /v1/remediation-records.
The publisher derives its expected digest from the validated report's
patch.diff_sha256; an absent declaration, invalid text, secrets in added
lines and digest mismatches refuse the call before publication. Secrets only in
removed/context lines mean the patch is not retained but the report is still
recorded. That recording response has WorkItem.patch: null and a non-null
patch_refusal naming the secret categories, never the secret values. The reason
is response-only: report-only calls and subsequent get/list reads return
patch_refusal: null; resending the refused diff returns the reason again.
Retained patch and report references attach to the same assessment without adding
tables. WorkItem.patch carries the artifact ID for authenticated
GET /v1/artifacts/{artifact_id}/content reads.
Patch and report uploads are staged before acquiring write intent or findings locks. The locked transaction rechecks the held report and records artifact rows, attachments and ticket links together. Runner leases are checked before staging and again under lock before recording. A conflict or rollback can leave unreferenced content-addressed objects, but no losing artifact metadata; retrying the same bytes is idempotent.
Rendering exception — pending human review (XWING-2355); no human sign-off yet.
Only producer-generated git patches
for RemediationArtifact, stored as ResultKind.REMEDIATION_PATCH through
ArtifactPublisher.publish_companion, are exempt from rendering over the validated
primary model. The report contains the patch's digest and path, not its contents,
so a renderer cannot reconstruct the patch. The exception covers only UTF-8 text
whose SHA-256 matches the validated report's patch.diff_sha256; existing secret
checks remain mandatory and accepted bytes are unchanged. All other companions
must be rendered from the validated primary model. This is not general precedent;
any approval must name the reviewer and link to the approving review or ticket.
Report-only callers remain supported (patch is null). Replaying the same report
with its patch backfills those bytes without changing later progress or resolution;
historical patches cannot be recovered from a producer-local diff_path alone.
Merged progress does not prove a finding fixed: that remains a resolution decision.
The native consumer doors above remain owned by their existing services and models. The remaining correctness changes are:
-
AssessmentService.latest(..., as_of=)selects the latest measurement at or before an inclusive cutoff using the existing assessment index.PortfolioService.default_ref(s)andCodeFactsService.collect/language_statsaccept the same historical cutoff. -
ViewName.LOCselects both the observed default head and its measurement atViewFilter.as_of.compare_as_ofmust precede that time; changes count only repositories measured at both points, not newly measured coverage. Unknown measurements remain null. An explicit positivefirst_party_divisorproduces a labelled estimate, not a measurement. -
Executive language grouping respects the requested historical cutoff. Historical PQC remains explicitly unavailable; no current readiness is substituted.
-
Verification baselines require the original commit and affirmative line identity: the same recorded ref, or a matching non-null ref name. Null names alone do not establish ancestry. Without a matching audit, the baseline remains unset; assessed resolutions stay bound to their recorded refs, including unnamed refs, rather than becoming line-free decisions that can close main. Exact replay retains its baseline.
-
Container image provenance is escaped as literal text inside authored report bodies. Stored provenance is unchanged, and container selection retains the existing
assessment_id/report_stemcontract. -
Triage rendering loads its report-only view on demand to avoid the findings/insight initialization cycle.
-
services.artifactsresolves every named document through the shared artifact catalog, including PQC facts published by the native prescan. Role aliases retain their common schema type; integrity checks and domain-scoped publication permissions remain unchanged. -
ViewName.CRITICAL_MISSESis a global, on-demand view ofDetectionQualityService.critical_misses(window). Usectx.services().insight.build,reportorpublishwithViewFilter(window=TimeWindow(start=…, end=…)); boundaries must be timezone-aware and increasing, with an inclusive start and exclusive end, and the window cannot exceed 31 days. Scope filters andas_ofare refused because dispositions are current, not historical. The model retains the existingCriticalMissrows underwindow/misses, validated bycontracts.validate_view("critical-misses", document)against the separately packagedcontracts.view_schema("critical-misses"), not an artifact schema or stored projection. Shared Markdown/HTML renderers retain IDs, assessment provenance, reason and unknown validity;area_reportedrequires adjudication and is not a confirmed miss or hit. Empty results do not establish coverage or a miss rate. Publishing writes neither database rows nor object bytes. Implicit all-view refreshes omit this global-only view when the requested scope is incompatible; explicit requests still fail rather than dropping filters. Refresh without a window retains the current-month fallback. Explicit windows are accepted only by views that consume them, including optional-window trends. Critical-misses publication uses a cumulative 10,000-input-record budget across window assessments and related history/finding reads, enforced with bounded SQL fetches. Overflow fails with no partial report and asks callers to narrow the window; direct legacy producer calls remain unchanged unlessmax_recordsis supplied. This bounds materialized rows, not database scan/sort work or individual row byte size. The same registry is available atGET /v1/insights/critical-misses?window_start=…&window_end=…; both boundaries are required together. View-schema validation exposes a recursive JSON type without a model-layer dependency in the contracts facade. -
ViewName.INVENTORY_EXPORTis a global, on-demand inventory view. Its schema-valid{rows: [...]}wrapper preserves the eight existing human-label columns and raw JSON values. Repositories retain newest-first(created_at, id)registration order; current owners and shipping products retain their existing sort/deduplication rules. Contacts are optional product-definition candidates fromlocations.feeds_cacheand the recorded product map, never ownership declarations or disclosure authority. Unknown URL parts and unestablished contacts remainNoneinternally; explicit JSON serializers retain the legacy blank strings and empty contact arrays. Human/CSV cells show—for unknowns, while known empty values remain empty. Neither state establishes no accountable party. Malformed optional freshness metadata leaves valid definitions usable with unknown freshness; malformed consumed registry shapes become unavailable without dropping registered inventory or swallowing quota errors. Pydantic 2.11 is the minimum for default alias serialization. Product/team/history and other nondefault filters are refused rather than ignored. Implicit refresh omits this view; explicit publication and authenticatedGET /v1/insights/inventory-exportremain available. Reads share a 10,000-record workload budget across inventory, related rows, recorded mappings and per-repository definition/contact resolution. Shared registry indexing does not consume the export allowance; unrelated registry entries cannot exhaust it. Overflow isValidationError(HTTP 422), never a partial inventory. Larger workloads can useportfolio.inventory_export(max_records=…)directly; the global view and HTTP route retain the default allowance. This budget does not bound shared cache loading or indexing, individual row/file byte size, or database query-planning work. Building and publishing do not fetch feeds, scan, write SQL rows or write object bytes. CSV uses the shared one-line, formula-neutralizing renderer; JSON retains original text. -
ViewName.COMPLIANCE_PRECISIONwraps the existingDetectionQualityService.calibrate_complianceresult undercalibration. Its packaged view schema reuses the canonical compliance-assessment framework vocabulary through the local artifact-schema registry, without network resolution or persisted artifacts.ViewFilter.frameworkis required; optionalprecision_gateis finite and between zero and one, defaulting to the producer's existing gate. Only this view consumes those parameters; unsupported scope, historical and time-window filters are refused. Implicit refresh skips it without a framework and selects only compatible views when calibration parameters are supplied. The existing REST registry acceptsframeworkandprecision_gatequery parameters. Counts retain the producer's distinct-document digest deduplication, automatic not-satisfied firings and attributed human dismissals. Precision at or below the gate flags a check for review, never automatic demotion. No assessments or no firings is unmeasured, not zero or perfect precision; field precision is not evaluator calibration or coverage. Publishing changes neither database rows nor object bytes. -
ViewName.BENCHMARK_SCORECARDSis a global, on-demand view ofDetectionQualityService.scorecards: one recall scorecard per eligible auditor version, scored against one snapshot of the newest answer key. Unadmitted and held-out-only cohorts are omitted; held-out targets remain available only through release scoring. An empty view means no eligible scorecards, not necessarily no recorded audits. The view takes no filters and is excluded from implicit refresh; explicit publication remains available. Reads share a 10,000-materialized-input-record budget across the answer key, audit history, artifact metadata reads and report findings. Matching, rerun comparisons and per-version scoring reuse those inputs without charging them again. Each actual related metadata read still counts; this is not a byte-size or scoring-iteration limit. Overflow raisesValidationError(HTTP 422), never a partial scorecard. The model is validated bycontracts.validate_view("benchmark-scorecards", …). The report renders strict recall, near misses (adjudication items, never detections), injection and stability; unmeasured cells show—, never 0. No recorded answer key isNotFoundError.scorecards(filter, max_records=…)offers the same cumulative bound to direct callers. Its optional auditor-version predicate is applied in SQL before assessment rows consume the budget, so unrelated versions cannot exhaust a selected cohort's allowance. Overflow advice names this direct service; it does not imply that the global view accepts filters. Scoring unrecorded runs staysDetectionQualityService.score_reports, a door, not a view.
Use ctx.checkouts to acquire a pinned commit and release its workspace when finished.
Mirror checkouts and refreshes fetch a missing requested commit directly, including commits
retained only by PR/MR refs, without caching the whole review-ref namespace. GitHub and
GitHub Enterprise use HTTP Basic token authentication for Git; REST authentication is separate.
Eviction skips in-flight acquisitions and registered worktrees. Refresh does not reset the last-checkout idle clock. Local-origin checkouts and history reads accept full SHA-1 and SHA-256 commit IDs, under the deployment's existing process policy.
Eviction discovery stops at bare-mirror roots, so refs such as release.git are never treated
as cache entries.
traust-server calls Context.configure_logging() before serving. Embedded callers call it
once at their own entry point; repeated calls do not add handlers. Core logs use the configured
outputs: stderr and a rotating file under Config.home by default. Select only stderr when a
platform collector owns shipping and retention. The default level is INFO, including the REST
access logger. For example, in the core config YAML:
logging:
outputs: [stderr, file]
level: INFO
levels:
traust_core.rest.access: INFO
format: json # or text
file: logs/traust.log
max_bytes: 10000000
backups: 5REST requests bind a validated X-Request-ID (or traceparent ID) for access, error and
provider events. Background entry points owned by traust or traust-engine must bind
correlation_scope(request_id, order_id=..., run_id=...) themselves; Core passes the scoped
request ID to outbound HTTP and to ProcessRunner as TRAUST_REQUEST_ID.
ValidationAction(kind, verb) describes a scope operation; ValidationTarget supplies its
context, namespace, resource, name, image, artifact ID or URL. ValidationActionKind covers
Kubernetes, container, WASM, browser, credential and inventory operations; it is distinct
from the contracts-owned execution-adapter vocabulary on ValidationPlan.adapter.
ValidationScope.permits(action, target, as_of=date) evaluates expiry, off-limits rules,
control-plane locks and positive grants. The caller supplies the date from Clock and
resolves WASM artifacts to UUIDs before evaluating scope. Binding modes retain their
constituent modes; credential and inventory grants require explicit. Cluster
explicit_namespaces contain only explicit engagement grants, never inline or inferred ones.
Browser verbs come from the first target whose origin matches the concrete URL.
ValidationPlan.requires_approval() is true for interactive, false for auto and
dry-run. It answers the human-review requirement, not permission to execute.
ValidationAttempt.soundness() returns SoundnessResult(verdict, flag) without changing
the attempt. ValidationProbe projects only the needed observations: verdict, verb,
transcript, positive-control outcomes and differential discrimination. The caller supplies
deployment-failure observations and the required controls_required / differential_required
inputs (legacy report thresholds: 0.177.0 / 0.178.0). Producer-stamped flags are preserved;
unsound refutations become inconclusive. At finding level, one sound refuting probe can
sustain a refutation unless a deployment, control or differential gate invalidates it.
Trusted in-process callers may use
ctx.services().validation.record(ValidationArtifact.parse(raw), provenance, bindings).
Remote runners use record_order(doc, provenance, bindings, run_order_id, principal),
the same protected service entry point as REST. Supply real Provenance
(run_id, source=SourceType.VALIDATION_REPORT, occurred_at) from the producing order;
recording does not invent a run, timestamp or execution plan, or execute live probes.
The binding JSON shape is:
{
"sources": [{
"source_report": "audit.json",
"assessment_id": "<existing-source-assessment-uuid>",
"artifact_id": "<existing-source-artifact-uuid>",
"findings": [{"source_id": "F001", "finding_id": "<existing-finding-uuid>"}]
}],
"policy": "current",
"target_attestation": {"attested": true},
"install_failed": false
}Parse that object with ValidationRecordInput.model_validate(...). sources binds report
labels to real assessed artifacts; findings maps source-local labels to real findings
with occurrences in that source assessment. Labels are never reinterpreted as UUIDs or
walked as filesystem paths. Recording checks the source artifact's assessment, baseline,
repository and, where present, report/triage/threat-model labels and fingerprints.
install_failure optionally retains the supplied deployment-failure explanation.
target_attestation.attested must be a boolean; the full attestation's extra fields survive.
ValidationRecordingPolicy.CURRENT ("current") is the strict default: target attestation,
positive controls, differential discrimination and confirmed evidence-grade gates all apply,
even for a reset producer version such as 0.34.3. Only explicit HISTORICAL ("historical")
uses the old numeric release-triplet cutoffs: attestation 0.176.0, controls 0.177.0,
differential 0.178.0, confirmed-grade gate 0.179.0. Valid prerelease/snapshot/build
suffixes do not lower those gates (0.179.0-rc.1 still requires a grade). Malformed
semantic versions are rejected in either mode; E3 confirmations still require review.
ValidationRecordResult returns assessment_ids, artifact_ids, evidence_artifact_ids
and per-finding outcomes with their decision/review IDs. ValidationRecordingEvidence
retains format_version=1, source_report, the report digest, provenance, bindings and policy
outcomes in assessment-owned artifact rows of kind validation-recording; the raw report
keeps kind validation. Findings link to that evidence with finding_external_ref system
traust.validation and ref traust:artifact:<evidence-id>#finding=<finding-id>.
Read it with ctx.services().validation.evidence(evidence_artifact_id), not by parsing a URI.
FindingDecisions.record_validation remains the decision owner; evidence-only outcomes do
not invent neutral decisions or new occurrences that could change severity or locations.
ValidationRecords owns the shared evidence reader used by validation and precedent lookup.
It verifies retained report and companion digests and reuses the recording soundness rules;
check_refutations(decisions) returns a typed per-decision exclusion reason, or None when
eligible. A sound recorded refutation can supply machine precedent context, while human
review, current-disposition and correction guards still apply. Candidate metadata is batched;
artifact reads occur after database reads close. Missing or unverifiable evidence is explicitly
excluded; operational storage failures propagate.
Dependencies point inward only; tests/v1/test_layering.py fails the build on a violation.
flowchart TB
CTX["context"] --> SVC["services"]
SVC --> REN["rendering"] & REP["repositories"]
REN --> REP
REP --> PRV["providers"]
PRV --> MOD["models"]
MOD --> CON["contracts"]
CON --> ERR["errors"]
flowchart LR
ENG["traust-engine<br/>(imports core; loads packs from config)"] -->|"ctx.assets.get(asset).materialize(request)"| MAT["Materializer<br/>thin: validate params"]
LLM["skill / LLM"] -->|"runs"| CLI["traust CLI<br/>thin: argv → AssetRequest"] --> MAT
MAT -->|calls one| SVC["service<br/>the work"]
SVC --> REP["repositories"] & PRV["providers"]
SVC -->|returns| JR["JobResult"]
ENG -->|"next tick: route(state, events, refusals)"| RT["Router (pure)"]
traust and traust-engine import core and call services in-process. The CLI exists only so an LLM can enter the Python world. Worked example: tests/v1/example/continuous_ops/.
| Local user | SCI / team | |
|---|---|---|
| Config | artifacts.path only |
+ database.url (Postgres), object store |
ArtifactPublisher.publish |
validate → objects → SQLite artifact row |
validate → objects → Postgres artifact row |
With no database.url, Context keeps the record store in SQLite at <home>/traust.db (home defaults to ~/.traust) and creates the schema from the traust-contracts DDL on first use. A configured database.url is never touched: whoever provisions it creates the schema (apply_schema(engine, contracts.database_ddl(dialect))). ctx.services() builds every service over one shared unit of work. Contracts owns and tests the schema; core tests repository behaviour.
make serve-local # http://127.0.0.1:8080, Swagger at /docs; SQLite under .local/home
make token NAME=me@example.com # a person's token -> .local/me@example.com.token
make token NAME=runner-1 MACHINE=1 # a runner's token -> .local/runner-1.tokenThe first run writes .local/traust.yaml (home plus identity: { trust_local: true }: this machine's local issuer is trusted, for development only); edit it to add database.url for Postgres. Tokens last seven days. From Go, traust.NewClient(traust.Config{BaseURL: "http://127.0.0.1:8080", Tokens: traust.FileTokenSource{Path: ".local/runner-1.token"}}); a person's token is what submitting orders and decisions need, a runner's what claiming and completing need.
Outside make, the same is traust-server --config traust.yaml and traust-server --config traust.yaml token NAME [--machine]. token refuses a config without identity.trust_local; a deployment trusts its OIDC issuers instead (identity.issuers).
# traust.yaml (local user)
artifacts: { path: ./analysis-results }ctx = Context.from_file(Path("traust.yaml")) # entry point only
publisher = ctx.artifact_publisher(SPECS) # SPECS: the caller's PublishSpecs
published = publisher.publish("triage", raw_bytes)
triage = publisher.get(published.artifact_id)POST /v1/verification-records requires a verified machine bearer token and a strict
{"payload": "<original JSON text>", "provenance": {...}, "run_order_id": "<order UUID>"}
body. The report is parsed as VerificationArtifact without reserializing its evidence
bytes. VerificationService.record_order admits only the live lease holder of an order
for the report's repository and an asset in the server's recording.verification
allowlist (empty by default). provenance.run_id must equal run_order_id; the new
assessment durably records that order, and replay cannot move it to another order.
The lease remains locked through the report's mutations and is rechecked before commit.
Admission and replay return the owner's native Verification; 401, 403, 404, 409 and
422 use application/problem+json. Original finding references must be native UUIDs;
event envelopes and layer IDs are not request fields. Authentication is not a human
countersign: report verdicts remain machine-assessment judgments under the owner's policy.
POST /v1/validation-records requires a verified machine bearer and a strict JSON body
with run_order_id (UUID), payload (the original UTF-8 JSON text), provenance
(Provenance) and bindings (ValidationRecordInput). The service locks that order
through recording and requires its live lease to belong to the actual caller. Its asset
must appear in the server's recording.validation allowlist (empty by default), and
every source assessment must belong to the order's one non-null repository.
provenance.run_id must equal run_order_id; new assessments retain that order ID.
Native assessment, artifact and finding UUIDs are required; source report paths and
labels do not establish identity. The existing validation owner retains exact bytes,
applies policy/target/soundness/grade rules and returns ValidationRecordResult.
Exact retries under the same live lease retain native IDs. A distinct producing order
may record a distinct run of identical report bytes, including a reference to matching
retained report evidence. Each new run gets its own native assessment and artifacts;
existing rows and their order linkage are never adopted or relinked.
Recording neither extends the lease nor completes the order.
Reports remain unsigned machine judgments or review-only evidence, never human
sign-offs. Errors use problem+json: 401 (bearer), 403 (caller, holder, repository or
asset), 404 (unknown order or binding), 409 (lease state, expiry, replay or evidence
integrity), 422 (body, report, binding or run-ID validation).
Code facts reads recorded documents; it never invokes tools. Attach a
CodeFactsArtifact through AssessmentService.record(..., kind=AssessmentKind.TOOL_SCAN, ...).
The latest assessment is authoritative: failed/degraded scans, missing evidence,
and unmeasured sections are explicit errors, not zero counts or older snapshots.
Symbols are ordered by path, line and name and match exact name/path filters.
Facts pages are limited to 1–1000 entries, with cursors bound to the artifact and
requested kinds; a changed latest scan invalidates the cursor. Crypto facts are not
recorded: code_facts.crypto(ref_id, ToolRequest(workspace=checkout)) runs the probe over the
checkout and reads crypto packages from the ref's dependency rows, so ingest the commit's
manifest SBOM first (syft scan dir:<checkout> -o cyclonedx-json, then
exposure.ingest_sbom(raw, ref_id)). The image tier reads the same way from the image's SBOM
(syft scan <name>@<digest> -o cyclonedx-json, then exposure.ingest_image_sbom(raw, digest)).
Other enumerator-specific fact kinds remain with their
skill-port slices.
Java route ownership credits resolver receivers only when they resolve to declared injected fields with admitted injection initialization and no later field writes or setter mutation in the checkout hierarchy, and only while the field's value stays unaliased: a local copy, a method reference, an argument hand-off or a returned field leaves it judged. Unproven fields and request-selected resolver values remain judged. Framework request getters are resolved by qualified type, explicit import (an imported outer class qualifies its nested types) and package: checkout class/record/enum lookalikes do not prove an authenticated caller, while genuine framework references remain eligible. A field's request-ness is judged by the file declaring the field, not the handler's imports.
Publisher output is read by artifact ID. Companion documents are rendered from the validated
primary and stored by digest, but only the primary is an artifact-row document.
The sole rendering exception, pending human review (XWING-2355), is the
remediation git patch, which retains digest-bound
producer text and has its own artifact row.
Aggregates use ArtifactPublisher.stage / stage_companion before acquiring write
locks, then publish_staged / record_companion inside their metadata transaction;
staging performs validation and object I/O only, indexing performs database I/O only.
Publishing failure raises a typed error; an object left by a failed row write is
safe to retry because its key is content-addressed.
All in tests/v1/example/:
| File | Shows |
|---|---|
model.py, repository.py, services.py |
SQL + in-memory repository mechanics over a non-contract table, unit of work, service (predates one-service-per-domain; for contract tables copy repositories/traust/inventory/repository.py) |
artifact_publishing.py |
a publish spec (TRIAGE over TriageArtifact) with a rendered markdown companion |
report.py |
read through a repository → pure build_report → render → publish |
router.py |
a pure router, table-tested |
provider.py |
a tool provider: request DTO, check(), acquire() with provenance |
continuous_ops/ |
engine → materializer → service → repository → JobResult, then a pure router reads the stored state; the same service reached from a CLI |
The real table repository to copy is src/traust_core/v1/repositories/traust/inventory/repository.py (a TableSpec, a narrow protocol, a SqlTableRepository subclass); its tests are in tests/v1/repositories/tables/.
detection_quality.regression_pairs(...) reads finding paths from complete worktrees pinned
to the last-reported and fixed commits. It requires Git, not forge archives: archives can omit
tracked files marked export-ignore. Both worktrees are released on success or failure;
later default-branch changes do not affect the paired contents.
OpsService.recording is the service-owned guard for native report mutations.
It requires a verified machine principal, its own active leased order, the same
repository, a server-authorized asset for the report kind, and provenance whose
run_id equals the order UUID and whose source matches that kind. The order row
stays locked through the report transaction; expiry is checked again before the
outer commit. A caller cannot gain recording authority from a bearer token alone.
An already-dispatched worker can obtain its native order with POST /v1/orders/start
and {"order": {"asset": "verify", "priority": 1, "repository_id": "<registered repository UUID>"}, "lease_seconds": 300}.
The strict StartRecordingRequest returns the persisted RunOrder, including the
database's timestamp precision and timezone representation. The lease holder comes
from the verified machine caller, never the body. OpsService.start_recording checks the
asset's report-recording allowlist and a matching recording.start capability before
delegating to the existing start owner. Each capability has principal (the verified
machine's full machine:... reference), assets, and repository_ids (existing native
UUIDs); all must match within one capability. Missing or empty capabilities grant
nothing. The repository must exist; fleet-wide starts are refused. Lease duration is
positive and at most one day. A planned or queued order is reused without replacing
its admission fields; an active lease or awaiting-approval order is never taken over.
Normal in-process OpsService.start retains its existing orchestration semantics.
The recording.start principal/asset/repository grant shape is a conservative
development review candidate for Shaun, not pre-approved deployment policy.
Recording capabilities default to deny. Bind the deployment's existing asset
names explicitly in traust.yaml; these are authorization allowlists, not defaults
or new pack assets:
recording:
assessment: [audit]
triage: [triage]
validation: [validate]
verification: [verify]
start: [] # Explicit principal/asset/repository grants are required for worker starts.New report assessments store run_order_id at creation. A replay cannot relink an
append-only assessment from another or an unbound run. Independent validation
executions still use their existing provenance-based run identity. Recording does
not heartbeat, complete, or extend the lease; the runner lifecycle remains separate.
POST /v1/remediation-records returns the native WorkItem after
RemediationService.record_order authorizes the lease and records the exact report bytes.
Its asset is the scheduler's remediate_finding, also used for explicit worker starts;
the unmerged remediation asset is not accepted. Until contracts provides a separate
remediation recording section and source type, authorize it in recording.triage and
use TRIAGE_REPORT provenance. This does not authorize remediation leases to record triage.
The report's metadata and findings must match the leased repository.
A named RunOrder.work_item_id constrains both work-item selection and digest replay.
A stale order cannot record against a newer attempt or another finding set. Orders
without a named item retain finding-set selection and digest replay; explicitly started
orders should name the work item when they must distinguish remediation attempts.
Existing native verification inputs are available through
GET /v1/findings/{finding_id}, GET /v1/repositories/{repository_id}/refs and
GET /v1/refs/{repository_ref_id}; the existing onboarding owner can register refs.
VerificationService.prepare consumes an existing native work item.
POST /v1/assessment-records is the explicit secure-code claim-intake workflow.
Its strict AssessmentRecordRequest contains run_order_id, repository_ref_id,
payload (the original UTF-8 report JSON), provenance, and assessor_version.
AssessmentService.record_secure_code authorizes through OpsService.recording
with the default-deny recording.assessment capability and existing
VULN_SCAN_REPORT provenance. It applies the report's full canonical_write gate,
then shares assessment/artifact recording and existing
FindingCandidate.of_report → FindingIntake.accept. The owner fixes kind to
secure_code and assessor to skill:secure-code-audit; no report-authored validity
or resolution becomes a decision. AssessmentRecordResult returns native
assessment_id, artifact_id, and intake (IntakeResult, whose findings maps
original labels to native UUIDs and created names newly admitted findings).
The raw bytes are retained unchanged, the assessment binds its order at creation,
and replay cannot adopt an unbound or differently bound assessment. Plain
AssessmentService.record remains document-only for secure-code reports;
record_scan remains third-party scanner intake only.
GET /v1/assessments/{assessment_id}/intake returns
AssessmentIntakeBindings ({"findings": {"<original label>": "<native UUID>"}}),
with no created history. For secure-code assessments, the read verifies the retained
report's digest, derives its candidates through the same report model, and resolves
them through the finding-intake identity owner. Every match must belong to that
assessment's occurrence set.
For triage assessments, AssessmentService delegates to
FindingDecisions.intake_bindings. That owner verifies the original triage artifact
and shares the recording path's exact fingerprint join against findings reported by
its baseline assessment. The keys are the triage artifact's own row labels (fNNN),
not rewritten orig_id values. All joined non-duplicate rows are included, including
weakness and undetermined rows that produced no decision. Missing evidence, missing
or out-of-scope baselines, and unmatched identities fail closed. No title, ordinal,
or consumer-side matcher is used. Historical report and triage bytes remain unchanged.
Current identity matching is exact; no alias resolver is invented for moved fingerprints.
Authenticated artifact reads expose the existing publisher's native metadata and
digest-verified bytes without reparsing the report through a consumer schema.
Use GET /v1/artifacts/{artifact_id} for Artifact, and
GET /v1/artifacts/{artifact_id}/content for exact application/octet-stream
content. Both use the request's shared unit of work and existing artifact owner.
traust-contracts is a pinned dependency ([tool.uv.sources] git rev in pyproject.toml). traust-core reads its data files (schemas, enums, DDL) from the installed package, through traust_core.v1.contracts only. Nothing is copied or generated.
flowchart LR
C["traust-contracts @ pinned rev<br/>(installed dependency: schemas · enums · DDL)"] --> G["contracts.validate()<br/>write gate, exact"]
C --> D["contracts.database_ddl()<br/>create tables"]
C --> R["models.artifacts.TriageArtifact, ...<br/>fields from the schema"]
| Task | How |
|---|---|
| Move to a new contracts version | change the rev in pyproject.toml, uv sync, make test |
| Try an unreleased contracts change | uv add --editable ../traust-contracts locally (don't commit) |
| Add a named artifact | one line in models/artifacts.py: class XArtifact(ArtifactDocument, name=…, schema=…, kind=…) |
| See an artifact's fields | TriageArtifact.describe() (field tree with types, required/optional); TriageArtifact.schema_path() |
ctx.services().pqc.worklist(PqcFilter(...), chokepoints=(...)) is the one owner of
the former Traust sweep worklist. Selection and ordering are unchanged: one repository,
the representative registered ref ranked by default flag then registration time/ID,
caller chokepoint fragments first, crypto-CWE associations next, then the tail.
Cursors retain <tier-index>|<repository-url>. Global inventory includes repositories
with no registered commit; explicit empty ref scope stays empty rather than widening.
Product and explicit ref scopes intersect.
ViewName.PQC_WORKLIST builds an ephemeral PqcWorklist through that service. Its
packaged view schema retains scope, entries, full liveness, scan provenance and nullable
currency. Use at most one ViewFilter.product_ids value, optional repository_ref_ids
(None: all, (): none), and pqc_chokepoints (nonblank caller URL fragments).
Historical/time-window, team/category, single-repository and unrelated filters are
refused, and other views cannot silently ignore the new worklist parameters. The
existing REST registry accepts repeated product_id, repository_ref_id and
chokepoint query parameters with the same constraints.
WorklistPriorityPolicy owns the unchanged tier decision separately from data reads.
Liveness is batched through PortfolioService.liveness_in(selected_repository_ids), so a
one-ref scope does not enumerate or read observations for unrelated inventory. The ID-scoped
and paginated liveness doors share the same recorded-observation policy.
Publishing renders structured Markdown/HTML without storing rows or object bytes,
scanning, checking out repositories or ingesting dependencies. Unknown scan currency
stays null in the model and renders as —, not stale/false or a readiness claim.
Availability is the existing liveness service's recorded evidence, not an access grant;
this migration does not change when its routing statuses are produced.
Chokepoints remain per-run adopter inputs; no persistence/configuration home is invented.
providers.pqc.PqcScanTool runs the configured pqc-scan binary over a supplied checkout.
Register it with Context.register_pqc_scanner(binary, rules, adapter_version=..., scan_commit=...);
the binary and merged rules pack remain caller-owned inputs, not files vendored into core.
The deployment's ProcessRunner must allow pqc-scan; its timeout and environment policy apply.
Placement exception: consumer-adoption plan 15, decision 4 / row 52 explicitly approves this provider in core, with engine orchestration. This overrides the consuming-package-only security-tool rule in the legacy provider recipe, item 8 (Qodo checklist 6208), as recorded in PR #373. Rule-pack content and readiness policy retain their existing owners; this is not a second engine scanner.
The provider checks the binary's adjacent .sha256 record, stamps the binary/rules, parses
required coverage and raw findings, and retains CBOM bytes. Missing/malformed output and nonzero
exits fail rather than becoming clean scans. Changed binary/rules during a run invalidate its result.
These are raw facts, not readiness verdicts.
Scanner hits require a nonempty checkout-relative source file and a positive integer line when one is supplied. Readiness also requires an executable, checksum-valid binary.
| Scanner output | Default budget |
|---|---|
| Report JSON | 16 MiB |
| Optional CBOM | 16 MiB |
| Findings | 50,000 |
| Combined process stdout/stderr | 1 MiB |
Outputs exceeding a budget fail explicitly, not as clean scans. Capped process capture is
file-backed and monitored while running, with bounded reads into memory; temporary disk usage
can overshoot between checks. PqcScanError.result retains bounded internal diagnostics, but
its public message and ordinary process logs do not include raw scanner output.
Rule discovery recursively includes regular .yml and .yaml files, excluding file symlinks.
Counts accept the scanner's YAML mapping or sequence forms through Context's shared decoder.
The fingerprint binds sorted relative paths and file-content hashes. This replaces the old
root-only stamp format: previously cached stamps refresh once; historical records are not rewritten.
PqcService.prescan remains the owner of post-processing, recorded facts and same-pack reuse;
its crypto census requires an ingested manifest SBOM. Engine schedules work but does not own a
second scanner implementation.
PqcService.scan_states compares source facts against adapter version, rules digest, scanner
commit and binary digest. Scanner replacement or rule changes invalidate source currency.
SBOM-only currency depends on the adapter/mapping pack, not scanner-only metadata.
Zero-file or unmeasured source coverage cannot assert a verified zero-hit or automatically
produce not-applicable readiness, regardless of rule count; recorded facts remain available.
PqcService.check and new record admissions select the installed
pqc-readiness#/$defs/canonical_write contract: sizes are canonical or null,
and blocked_external is an independent optional boolean. Both clock items and
remediations carry that flag through service results; absence stays unknown.
contracts.validate and validate_json accept schema-name JSON Pointer fragments
for these existing opt-in profiles.
Historical artifact parsing and stored effort labels remain unchanged. An exact stored report replays its existing assessment; a different report with deprecated effort is refused before publication. Readiness buckets and scores remain the producer's supplied policy results, not an inference from effort size. First recording, replay and reads use the same persisted timestamp projection.
New AssessmentService.record threat-model runs validate the serialized bytes
against threat-model#/$defs/canonical_write before recording or publication,
including documents already published by reference. Historical parsing and an
exact stored run's replay retain their original bytes and identity.
Replay matches original provenance through the repository's native timestamp
comparison, including SQLite millisecond storage; a distinct stored time still
starts a new run and must satisfy canonical admission.
Mitigation size may be null; external blocking is an independent optional boolean. Quick wins recognize the registry's canonical small sizes and approved historical aliases without changing risk, status or class-closure eligibility. Unknown size is not a quick win. Reported blocking stays separate in JSON and the optional display column; absent blocking is not invented as false.
New report-schema assessment runs validate finding effort/blocking and roadmap effort/priority/blocking against the existing canonical collection subnodes before recording or publication. This applies to every registered report role, including already-published references and alternate document wrappers. Unrelated severity-override constraints are not activated by this rollout.
Effort may be null; blocking remains independent and optional. Roadmap priority must be explicitly authored in its registered domain, without guessed mappings from severity, deadlines or legacy prose. Historical full-schema reads and exact stored-run replay keep their raw values, native timestamp identity and bytes.
FindingDecisions.record validates new triage bytes against
triage#/$defs/canonical_write before writing an assessment, artifact, kind or
decision. A verified defense-in-depth gap uses verdict: true_positive and
kind: weakness, with rule 13 and no severity; the decision validity is
confirmed, not a new triage verdict. Omitted kind preserves the existing
finding kind. An undetermined or manual-test outcome remains review-only,
including when its kind is known.
Explicit kind changes commit with new triage and artifact admission, before per-finding decision writes. Interrupted recording can resume without losing a kind that admission accepted; replay still cannot overwrite a later judgment.
Summary and rendering keep weaknesses separate from actionable vulnerabilities.
Broken weakness citations retain the re-vote requirement. Historical
verdict: hardening artifacts remain readable, and exact baseline-scoped replay
retains stored bytes and IDs without reapplying an old kind over later judgment.
Prepublishing a legacy artifact does not authorize a new deprecated write.
GET /v1/assessments/{assessment_id}/triage-input returns the existing
TriageInput, including native finding and occurrence bindings.
POST /v1/assessments/{assessment_id}/triage-records accepts strict
{"run_order_id": "<owned order UUID>", "payload": "<original UTF-8 JSON text>", "provenance": {...}} and calls
FindingDecisions.record_triage. Its TriageRecordResult always includes the
recorded assessment/artifact UUIDs, even when every finding queues review and
decisions is empty. The in-process record decision-list projection remains
available over the same recording implementation.
The server resolves citation files from the assessment's recorded repository
and exact commit through its existing forge provider (gh/glab and deployment
credentials). Requests cannot select a server checkout path or another repository.
Local and forge views share citation matching; unreadable, unauthorized or
truncated source trees fail rather than being treated as absent evidence.
Source bytes, canonical admission, replay, kind persistence and decision policy
remain with the existing owner.
Authentication alone does not authorize recording. The service requires the
caller's own active repository-scoped order lease, an explicitly authorized
server asset for triage, and provenance bound to that order. New assessments
retain their native run_order_id; replay cannot adopt another or an unbound run.
contracts.normalize_enum(name, value) reads replacement metadata from the
installed pinned contracts data through this module's existing resource loader.
The Python placement and affected-contract review remain pending; this callable
candidate does not activate ledger reads or writers.
from traust_core.v1 import contracts
view = contracts.normalize_enum("source_type", "interactive")
for pair in view.pairs:
print(pair.enum, pair.value)Unlike the existing filename-stem APIs enum_values("source-type") and
enum("source-type"), the normalizer accepts the registry document's exact
name, such as source_type, matching the Go SDK. There is no separator,
case or Unicode folding. Unknown enum names raise ConfigError; unknown values
in a known enum retain their exact spelling, because readers are not validators.
The frozen EnumNormalization contains ordered EnumValue pairs and a
dropped flag. Declared rename/merge/split replacements retain their order.
Retired keys still resolve when absent from current values; a one-way drop keeps
the original pair with dropped=True. Results cannot poison the cached registry.
The currently pinned data declares no deprecations, so values read unchanged;
legacy effort strings are never inferred to be sizes. Free-text mapping and
cross-field policy remain outside this API.
Neutral acceptance cases are authored in contracts' test fixtures and retained as test-only copies in Core and SDK, not packaged runtime definitions. They exercise replacements without choosing production vocabulary or requiring a sibling checkout. No dependency pin or historical payload changes are made.
| Kind | Answers | Lives in |
|---|---|---|
| Core | what the data means and how it is stored, read and run: models, repositories, services, policies | this repo, by role: models/<section>.py, repositories/traust/<section>/<table>.py (+ traust_ops/, traust_ledger/), services/<subject>.py (services/findings/ is a package) |
| Pack policy | when / which: routing rules, cadences, lanes, skills, CLIs | traust (traust_pack_security), not this repo |
Everything here is v1 API, reshaped in place until it is frozen. Each contract table has one repository; beyond add/get/update its methods are the questions asked of that table. Tests mirror src: tests/v1/<role>/test_<subject>.py.
- Services never open connections, build paths, start processes or read env vars; they receive what they need through their constructor.
Contextat entry points only.- Data is parsed into a model once, at the edge. No bare
dictacross a function boundary. - SQLAlchemy Core only: no ORM mapped classes. Models never know about the database; the shared repository base gives only
add/get/update. - Unknown or unmeasured values are
None, never0. - Every table repository has behaviour tests on SQLite and Postgres; stores and providers have contract tests.
- traust-core is the hub: other traust repos depend on it, not the reverse. The only code dependency is traust-ledger's pure functions (Merkle, signing, verifying), imported only by
services/ledger.py; traust-contracts is pinned data. HTTP only in providers (outbound) and the HTTP adapter (inbound).
Services own their domain's use cases and declare the section unit of work they need;
SqlTraustUnitOfWork satisfies those protocols on one connection.
| Domain | Service | Unit of work (tables) |
|---|---|---|
| Portfolio: what we protect | services.portfolio · PortfolioService, PortfolioSync (forge org listings and single repositories into the inventory; import_owners mirrors the one declared ownership source; owner_hints are OWNERS / contributor handles, never stored), RepoStateQuery |
InventoryUnitOfWork (product, release, component, repository, repository_ref, team, ownership) |
| Onboarding: bring a project under the platform | services.onboarding · OnboardingService (plan dry run with gaps, onboard registers product, repositories, branches, teams and plans the baseline orders) |
none: composes PortfolioService, PortfolioSync, OpsService |
| Exposure: known vulns reaching us | services.exposure · ExposureService (refresh_feed snapshots a feed, refresh_feeds every registered one (a failed feed keeps its last snapshot and reports last_error); an incremental feed (providers.feeds.PagedJsonFeed, Red Hat CVEs) builds on its last snapshot; staleness is the latest feed_snapshot; exploit_signals reads EPSS and KEV per advisory from the latest snapshots; exploit_context joins them onto the CVE-bearing items of an artifact's findings / candidates list, with the repository's product reach and owning teams (through PortfolioService); source_document returns a registered feed source's document for one identifier (providers.feeds.FeedSource: NVD, Red Hat CSAF and VEX documents, Scorecard), whose declared probes are ProviderCatalog readiness (feed-source:*); find_advisory fetches an advisory through providers.osv and keeps its identity, aliases and packages, ranges stay on the AdvisoryRecord; ingest_sbom reads CycloneDX / SPDX JSON (models.sbom_formats) into a commit's manifest SBOM; dependencies(ref_id) reads it back as package + version rows (NoManifestSbomError when none was ingested); ingest_image_sbom / image_dependencies(digest) do the same for an image's SBOM on its append-only image row (NoImageSbomError); affected is the blast radius through base images and internal libraries; prepare / record are the impact-analysis skill doors: one commit's evidence and classification (ImpactEvidence.classification), with the providers.impact tiers (govulncheck, source scans) over a checkout the caller passes; resolve_symbols returns the functions the advisory's fix touched (OSV-published, else mined from fix commits through SourceProvider.commit); consumes_fix says whether a commit's SBOMs carry a fix released elsewhere), then an impact assessment and, for an affected commit, its dependency finding) |
TraustUnitOfWork (package, sbom, dependency, image, advisory, advisory_package, feed_snapshot + findings) |
| Assessment: what we looked at | services.assessment · AssessmentService |
AssessmentsUnitOfWork (assessment, artifact, occurrence, location) |
| Findings: what's wrong and every decision on it | services.findings · FindingService + intake, decisions, review, precedents; FindingViewQuery shares rich views/history; IdentityPolicy, RatingPolicy |
TraustUnitOfWork (finding, cwe, finding_cwe, finding_label, finding_external_ref, decision, decision_approver, review_item); Disposition.fold interprets historical classifiers and explicit evidence strength |
| Attestation: human sign-off | services.attestation · AttestationService |
FindingsUnitOfWork — a human answer is a decision that resolves a review_item |
| Identity: who is acting | services.identity · IdentityService; models.identity (Principal, Role) |
none: principals come from providers.identity (OIDC, local user, directory) |
| Operations: orders, leases, spend | services.operations · OpsService, SpendService, SpendViews (ctx.spend_views.usage_summary(source, window, group_by): a source's usage per day, skill and model at list prices, unrecorded; it never opens the record store, and SpendService.usage_summary is the same answer); FeedQuery (the ops feed: orders that stopped or finished, new assessments and recorded releases after a (timestamp, id) cursor, oldest first; composes OpsService, AssessmentService and PortfolioService, whose recorded_release gives the router a release's repositories and change) |
OpsUnitOfWork (run_batch, run_order, spend_event, feed_snapshot, assessment_input) |
| Ledger: proof of decisions | services.ledger · LedgerService: an append grows the layer's tree from its stored frontier (O(log n), never rereads events); decide / restate sign their append, record seals once per triage; verify / checkpoint recompute from events; audit verifies every layer, for the scheduled ledger_audit asset |
LedgerUnitOfWork (layers, events) |
| PQC, code facts, compliance, detection quality | services.pqc, services.code_facts, services.compliance, services.detection_quality |
AssessmentsUnitOfWork — content is read from the assessment's artifacts, never tables |
| Validation: supplied machine reports | services.validation · ValidationService.record, evidence |
TraustUnitOfWork — source/baseline assessments, artifacts, findings decisions and evidence external refs; no live execution |
| Remediation | services.remediation |
TraustUnitOfWork — assessments + decisions + external refs + artifacts |
Insight (dashboards) has no repository: views read services and render.
AssessmentService.latest_recorded(repository_id, kind) returns the newest completed or
degraded assessment across the repository's refs, normalizing historical kind aliases.
GET /v1/repositories/{repository_id}/assessments/latest?kind=<AssessmentKind> exposes the
same typed Assessment (get_latest_recorded_assessment / SDK GetLatestRecordedAssessment).
The kind is required; an unknown repository or no recorded match returns 404, and reads
require the usual authenticated caller. Pending, failed and unknown statuses are excluded.
The scoped assessment query returns at most one row, ordered by occurred_at, created_at
and id descending; consumers need not fetch or walk assessment history.
Direct human judgements use services.findings.decisions.decide(RecordDecision(...), principal).
Authenticate with services.identity.authenticate(Credential(token=...)); the service requires a
verified human, derives the actor from that principal and supplies the clock time when omitted.
RecordDecision requires finding_id, a meaningful rationale and at least one selected validity,
resolution or severity. Evidence grade, source channel, repository refs, artifact and judgement
time are optional. New writes accept only canonical contract enums, including
SourceChannel.INTERACTIVE for a local direct statement.
Retries of the same normalized judged values return the original decision ID, even when the
rationale, source channel or transport time changes. Historical reads normalize deprecated
classifiers without rewriting stored decisions, ledger events or Merkle bytes. Only explicit
E0–E3 grades establish evidence strength (E0 is strongest); actor and source provenance do not.
Weaker or ungraded claims cannot override stronger proof. Verified-human risk acceptance is a
governance closure, not proof of a fix: it can close an earlier technical disposition without
changing its validity or inventing an evidence grade. An administrator's correction uses
restate(Restatement(...), principal), retaining authority, ticket, rationale and independent
approver gates; a retried correction is authorized before deduplication.
services.findings.list(FindingFilter(...), PageRequest(...)) pages the same folded
state as get and baseline, including locations, CWE identifiers and taxonomy labels.
Unrated or unreported findings have effective_severity=None; only a reported or human
rating supplies a severity. review_surfaces(repository_id, as_of=None) merges the latest
audit with later scans and keeps a surface open until its findings are affirmatively closed.
Closing one review item does not bypass the finding's evidence-strength or independent-review gates.
FindingService.get_by_fingerprint(repository_id, fingerprint, fingerprint_algo=None) resolves
opaque fingerprints exactly, without trimming, recomputing identity or matching titles.
GET /v1/repositories/{repository_id}/findings/by-fingerprint exposes the same FindingView
through get_finding_by_fingerprint / SDK GetFindingByFingerprint. The identity query reads
at most two matching rows and refuses ambiguous algorithms with 409. Supplying the algorithm
uses the complete existing identity key; omitting it can examine more entries within the
repository's index scope. Hydrating the selected view retains the normal per-finding
history and context reads, rather than bounding all related records to two.
summary and timeline retain unknown open findings in the registered unrated
bucket of open_by_severity (including JSON output), while their individual effective
severity stays None. The timeline's risk_index is None when any open finding has
neither a CVSS score nor a severity; fleet trends propagate that absence. Known ratings
retain their existing score/band-midpoint arithmetic.
Executive-summary credential rows and SLA escalations retain severity=None and
sort after known severities. Rollup totals include open.unrated; Markdown shows
an unrated column when needed and - for an individual absent severity.
CVSS-floor SLAs still apply to unrated findings: overdue counts and resolved-time
medians use the registered unrated aggregate key without assigning a severity.
assessment.record accepts canonical AssessmentKind values, publishes a document
when provenance names none, and records container/RPM report findings in the same
transaction. Replaying the same document, commit, kind and occurrence time returns the
original assessment, including historical spelling reads; a later occurrence is a new run.
record_audit uses the same record owner and atomically adds the cloud-config judgments.
Explicit report kind passes through FindingCandidate and intake to a new finding;
omission stays unknown. Intake does not infer validity or evidence strength from kind,
and a later observation does not overwrite an existing finding's classification.
Within one batch, duplicate fingerprints share their agreed explicit kind regardless of
input order. Conflicting explicit kinds fail preflight before insertion; this does not
change the no-overwrite policy for findings already present before the batch.
New audit reports omit validation_status when no validity answer is authored.
Any explicit reported value is checked against the canonical validity registry from
serialized bytes, after the exact historical-run replay lookup and before publication.
Correction context is not a validity answer; neither omission nor kind supplies proof.
Historical schemas, stored bytes and exact-run replay retain their existing behavior.
Provenance lead dates and elapsed SLA durations use UTC instants, including across
non-UTC database sessions and daylight-saving transitions. Stored timestamps and
the existing RatingPolicy.sla_due deadline policy are unchanged.
services.insight.SarifExport.document(views, repository_url) projects these views to SARIF;
the registered sarif Insight view (insight.build(ViewName.SARIF, ViewFilter(repository_id=…, as_of=…, categories=…)), GET /v1/insights/sarif?repository_id=…) applies it to one
repository's baseline and refuses a product or team scope.
Unknown severity uses level none, a null traust/severity and no security-severity;
it is never turned into an informational rating. False positives and accepted risks are
suppressions, and resolved findings are absent.
ctx.services().exposure.record(ref_id, document, provenance) validates the retained
payload and requires its classification to agree with the existing evidence rubric,
including schema-declared L4 evidence. The assessment, artifact metadata, finding and
occurrence share one database transaction; intake refusal leaves no partial assessment.
An exact replay returns the original identifiers without inventing a validity decision.
services.attestation.request(finding_id, decision_id, reason) opens one pending item per finding,
reason and proposed decision (including null); repeating the request returns that same item.
A linked decision must concern that finding; different proposals never substitute for each other.
After closure, a new request can ask a second reviewer. services.attestation.pending(...)
and services.findings.review.cases(...) return Page[ReviewItemCase], honoring finding,
reason, status and the returned keyset cursor. The separate review.queue(...) retains
the folded-state queue used for repository/kind filtering.
Each rich case holds the canonical review_item, finding: FindingView, normalized
decision history, external references and Core-selected evidence_artifact_ids.
services.findings.artifacts.metadata(id) reads artifact metadata and .payload(id)
returns digest-verified bytes, including non-schema validation-recording companions.
Typed .get(id) remains for schema documents; validation.evidence(id) verifies
retained validation report context. Voided validation retains its report and companion
without manufacturing a decision.
FindingViewQuery batch-loads context once per page, shared with finding get/list/baseline
reads; callers must not rebuild cards with per-item
get/history calls. Review get and for_finding still return ReviewItem rows; for_finding
includes historical closed items. Historical confirmed review status reads as canonical
accepted, without rewriting the stored row.
attest(review_item_id, principal, AttestationDecision.CONFIRM|REJECT, RecordDecision(...))
requires a verified human for either outcome, including administrators. Both outcomes record the
reviewer's explicit judgement and rationale; rejection never invents an opposite verdict or
silently discards the answer. The decision, its ledger event and the matching review closure share
one transaction. An exact reviewed-answer replay returns the original item; another author,
judgement, rationale or outcome conflicts without leaving an orphan decision.
Reusing an already-recorded normalized judgement also requires its original rationale: decision
identity deliberately excludes rationale, so review cannot silently replace that retained text.
services.findings.review.resolve(review_item_id, statement, principal, status=...) owns that
atomic operation. needs_review(finding_id) includes outstanding requests and an E0 refutation
awaiting a second independent human; needs_signoff(decision) delegates to ReviewPolicy.
Machine false positives outside the auto-accept tier require analyst review; auto-accepts retain
the existing salted audit valve. An E0-confirmed finding remains open after one later E0 human
refutation and closes only after a distinct later E0 human concurs. A repeated signer is not a
second independent judgement, and omitted evidence grade stays null.
Human decide and restate verify and lock the existing ledger layer before a new judgement.
Corruption returns the integrity conflict without changing decisions, reviews, events or proof
metadata. This admission is linear in layer history; the existing frontier append remains
logarithmic. perf/queries/test_ledger.py measures the complete countersign path on both typical
and 10k-event layers, separately from the append-only benchmark. Attestations use the existing
decision event (append_decision), not a parallel append_attestation event.
models.operations exports the contract-backed SessionTask and SessionResult, plus
SessionRecord for runner-computed file digests. OpsService.record_session(order_id, worker, session) appends an immutable manifest under the current, unexpired lease;
identical evidence is idempotent and changed evidence for the same executor session conflicts.
ops.manifest returns session entries when present and preserves the empty-manifest response.
BelowRangeService.record accepts an explicit optional run_order_id independently of legacy
provenance IDs; newly created ordered reviews link to it, manual reviews remain unattributed,
and reused documents preserve their original attribution.
models.operations.OrderState, OrderReason, WorkLane and RescanEventSource
load their values from the pinned contracts registry. OpsService.submit, plan
and schedule validate new routing classifiers; completion and withholding validate
reason codes before changing an order or publishing its run manifest.
An asset such as dashboards is not a lane: unrouted work leaves lane=None.
Existing lane, event-source and reason text remains readable without relabelling
historical rows. Current machine codes keep their existing spelling; diagnostic
prose is emitted in structured logs rather than stored as a new reason code.
DegradedKey owns common gap causes and ledger counters. Context.services()
also permits identifiers from its current feed registry, the insight view model and the below-range coverage-gap reasons;
registering a feed after service construction is respected without fetching it.
Completion rejects unknown keys and invalid counts before changing a lease or
publishing a manifest. Source/view counts are not collapsed into generic causes.
Historical unknown keys remain readable and survive progress updates; original
artifact bytes remain unchanged.
AssessmentService.record_scan(ref_id, raw, format, provenance) owns scanner source
metadata: its assessment records the kind, parsed scanner name/version and actual run
time. FindingCandidate has no origin; parsers and report projections accept no
parallel origin argument. Origin rollups use the finding's first assessment's
assessor, even when another scanner later reports the same finding. Raw report
origin fields and their historical schemas/SDK representations are unchanged.
AssessmentService.record_scan preserves an unambiguous SARIF enclosing function or method
in CodeSpan.symbol and the stored location.symbol. Inline logical locations, run-level
indices and parent references are supported; fully qualified names take precedence. Missing
or ambiguous functions remain unknown. FindingDecisions.prepare attaches prior adjudications
matching that symbol, the vendor-normalized file path and a shared CWE.
Intake reconciles duplicate location claims across the batch before inserting them. A single supplied symbol is retained; conflicting symbols roll back the intake. Retries preserve the original append-only locations, and later assessments can supply additional metadata.
Bound SARIF artifact hashes["sha-256"] claims travel through CodeSpan.hashes into append-only
LocationHash rows, read through repositories.traust.findings.location_hash. The run's unique
repository/revision binding must match the assessed ref. file-bytes-sha256/v1 means exact whole-file
bytes claimed by the scanner, not independently read source. Ambiguous references, invalid digests
and conflicting duplicate claims fail atomically; retries never enrich historical locations.
Precedents still require the symbol, vendor-normalized path and shared CWE. PrecedentReason.basis
is hash_backed only for equal compatible claims bound to the selected verdict's judged commit.
Missing usable claims or unknown verdict binding preserve symbol_only context with an explicit
unavailable_reason; incompatible or unequal comparable claims reject the pair. This evidence
basis is separate from human/machine strength. Candidate locations are paged by (symbol, id) and
anchors are read in bulk, so historical unanchored verdicts remain eligible without per-row I/O.
Scanner candidate batches are revalidated at intake, including copied models and nested locations, before finding writes. Scanner assessment creation and intake share one unit of work, so a malformed batch or later persistence failure leaves no partial assessment. Replay uses an indexed native-timestamp lookup and returns the stored record; SQLite keeps the existing millisecond contract and PostgreSQL its native precision. No verdict or evidence grade is inferred from a scanner report.
| Item | Where |
|---|---|
| First real providers (git, forges; paginated and indexed feeds such as rh-cve and VEX) | packages / providers.feeds |
traust_core.v1 is the platform API, reshaped in place until it is frozen. There are no releases: consumers pin a git commit SHA ([tool.uv.sources] traust-core = { git = "…", rev = "<sha>" }), the same way core pins traust-contracts.
make setup # uv sync + git hooks (lint on commit, conventional subject, lint + testmon-selected tests on push; FULL=1 for the complete suite)
make lint-fix
make test # unit tests
make coverage # + coverage (coverage-html for htmlcov/)
make db-up # shared traust-postgres container (same as contracts and ledger)
make test-integration # Postgres tests (marked `integration`)
make test-repositories # repository layer on SQLite and Postgres
make db-down| Capability | Core owner |
|---|---|
| Aliased advisory context | ExposureService.find_advisory / preparation preserve each alias package's ecosystem, ranges, fixes and source provenance; sweep exposes nullable fix_available. |
| CVE replay | DetectionQualityService.cve_replay, cve_replay_oracle, cve_replay_candidates, cve_replay_trends; typed results in models.cve_replay. |
| Spend declarations | SpendService.record(SpendEvent(...)) retains repository_label, loc, batch, note and optional canonical repository_id. |
| Repository availability | PortfolioService.record_forge_facts, record_liveness, liveness, liveness_in, liveness_reference; Liveness / RepoLiveness in models.inventory. |
| Reviewed claim corrections | FindingService.request_claim_correction, correct_claim, claim; services.findings.claims and models.finding_claims. |
| Configuration cap | ConfigMatrix.enumerate(ToolRequest(..., params={"max_triples": n})) accepts nonnegative integers, retains judged defaults first and reports truncation as a coverage gap. |
CVE replay reads the original bound audit report, not later context. Monitor queries finish before any snapshot/watermark is recorded; failed or malformed sources advance nothing. Presence adjudication requires a verified human principal and evidence. Unknown presence never enters missed-finding trends, and a confirmed miss is not benchmark admission.
Claim requests retain digest-checked proposal bytes and queue review. Acceptance requires
the restate role, ticket, meaningful rationale and configured independent approvers.
It rejects stale bases, changed original occurrence sets and fingerprint collisions.
Title/fingerprint and the accepted claim projection change; original occurrences, reported
severity and disposition do not. claim(finding_id, as_of=...) supports historical reads.
Ledger proof and the finding/review projections share one transaction.
Spend identity resolves only a registered exact URL/UUID or the associated order; ambiguous labels remain supplied text, with cost retained and canonical identity unknown. Ordered events store no independent repository ID: every rollup derives it from the order, including an unknown order repository. Only order-less declarations store canonical repository identity. Declared/measured token double counting is unchanged.
Availability is active, archived, moved, missing, disabled or unknown.
Credential/network failures remain unknown, not missing or reachable. Unqueried repositories
have no invented observation time and are absent from stored snapshots; unchanged status
keeps its original status_since. Availability is separate from successful commit observations.
Imported artifact publication serializes each kind and admits a new row only when database
statement time advances beyond its latest publication. Clock ties retry within a bounded
window; a frozen/backward clock raises RepositoryError without changing history.
SQLite's millisecond resolution limits same-kind publication throughput.
latest_imported returns the retained document and prior ID; publish_successor rejects
stale priors and older-digest reverts. No publication-revision column or historical backfill
is used. New schema changes are bootstrap-only: existing databases are not upgraded by
conditional scripts or automatic reconciliation.
ArtifactPublisher.stage validates and uploads immutable bytes before write/lease locks;
publish_staged publishes their metadata within the caller's atomic transaction.
ArtifactDocument.from_document captures the serialized snapshot, not its caller's mutable
mapping. Native scanner assessments retain finding-intake label/fingerprint bindings so
claim review never guesses an original label from a replacement report.