mirror of
https://github.com/norandom/OpenUltraSAST.git
synced 2026-10-19 04:59:28 +00:00
The source, sink, sanitizer, operation, discharger and layout rows are not a pattern list. They are an ontology of program elements and their security relations, and nearly all of this tool accuracy lives there: the family census found five of seven families establishing nothing, every one for an ontology reason with the flow engine ready and nothing to aim at. The hook is general-purpose, not application-specific. The ontology describes libraries, frameworks and language constructs; it does not describe repositories. An entry names an API and is justified by what that API does, carries provenance and a weakness class, and may not encode a path or identifier peculiar to one project -- a plugin bundled library tree was measured and declined on exactly that ground. A measured gap is a candidate, never an entry: accretion by subject is how a general tool becomes a fixture of its benchmark. contributor-scan owns the ontology, so its design carries that statement and a new group 7 makes growth measurable rather than anecdotal: report what a repository calls that nothing models, key sanitizers to the weakness they prevent, skip arbitration where the evidence says there is nothing to find, share one matcher across the queries, require a degradation to declare its scope, and commit golden findings so a silent change cannot pass. pre-push-safety-net carries the general-purpose test for its own reviews, and a task for harnesses that report the cause of their own failure -- named by a week in which three separate causes looked identical as one count. Co-Authored-By: Claude Opus 5 (1M context) <[email protected]> Claude-Session: https://claude.ai/code/session_01EmM1j46qgKMh3zqD1QMwEo
496 lines
34 KiB
Markdown
496 lines
34 KiB
Markdown
# Design: pre-push-safety-net
|
||
|
||
Status: approved for task generation on 2026-09-12 following the maintainer's "ok, next kiro phase".
|
||
Requirements and tasks are approved; group 1 implementation was authorized by "approve." on
|
||
2026-09-12. Runtime and latency claims remain unverified.
|
||
|
||
## Overview
|
||
|
||
The hook is a bounded transaction around the existing ranker and detector. It analyzes immutable
|
||
pushed tips, supplies change context to the ranker, and admits only actionable security regressions
|
||
to the terminal. The ranker remains the scope mechanism. Third-party vendor code stays outside
|
||
targets and graphs. WordPress is one regression workload, not the architecture's organizing unit.
|
||
|
||
The design separates three decisions: the ranker selects questions, the arbiter establishes modeled
|
||
claims, and hook policy decides whether a supported change-related claim merits an interruption.
|
||
An entailed rung is necessary for initial admission but is not sufficient by itself: a wrong fact,
|
||
an irrelevant old defect, or an unactionable witness can still produce noise.
|
||
|
||
### Goals and Non-Goals
|
||
|
||
- Deliver specific security findings with witness, change relationship, location, and repair direction.
|
||
- Bound the entire push and retain exact coverage; reuse matching analysis work without stale verdicts.
|
||
- Measure recall, noise, coverage and latency together across supported languages/frameworks.
|
||
- Do not build a second ranker/scope heuristic, an incremental CPG engine, a daemon, an automatic fixer,
|
||
or a mandatory model service. Do not scan intermediate pushed history in this feature.
|
||
|
||
## What this hook is, and is not — 2026-09-21
|
||
|
||
A general-purpose pre-commit hook, not an application-specific one. It may be measured on WordPress
|
||
plugins and Node applications, and it may not become a tool that only works on them. The practical
|
||
test at review time: would this change still be correct on a repository nobody here has seen? A path,
|
||
a directory name or an identifier peculiar to one project fails that test, and one was declined on
|
||
exactly this ground when a plugin's bundled library tree would have fixed a single subject while
|
||
claiming something false about every other.
|
||
|
||
The fact tables are an ontology of libraries, frameworks and language constructs, and
|
||
`contributor-scan` owns it. Its design records how it may grow and its group 7 makes growth
|
||
measurable rather than anecdotal. Nearly all of this tool's accuracy lives there: the 2026-09 family
|
||
census found five of seven families establishing nothing, every one of them for an ontology reason
|
||
with the flow engine ready and nothing to aim at.
|
||
|
||
## Two invariants this project keeps rediscovering
|
||
|
||
Added 2026-09-21 after the same two defects appeared eleven times between them. Each instance was
|
||
found by measurement, fixed in one place, and then found again somewhere else, because the RULE was
|
||
never written down. Both are cheap to check in review and expensive to find in a benchmark.
|
||
|
||
### A degradation is owned by the narrowest scope that caused it
|
||
|
||
A gap recorded at repository scope silences questions it has no evidence about. The test for a
|
||
repository-wide gap is whether it could make a reported answer WRONG everywhere, not whether it cost
|
||
coverage somewhere.
|
||
|
||
| instance | the blast radius | where it was fixed |
|
||
|---|---|---|
|
||
| vendor exclusion acting as a global completion veto | every question in the repository | M1b, task 10.6 |
|
||
| line ambiguity inherited by every question | every question in a file | M1b |
|
||
| an empty scope blocking another question's comparison | a sibling's answer | task 12.3 |
|
||
| a language partition boundary demoting every answer | 4,425 PHP regions, by 36 JavaScript ones | task 13.4 |
|
||
| a partition census gap demoting every answer | 4,425 PHP regions, by four build artefacts | task 13.4 |
|
||
|
||
The fourth and fifth cost the declared v0.1 target its entire admissibility: a WordPress plugin is
|
||
PHP plus admin JavaScript, so the product could detect defects on its own target and never admit
|
||
one. Neither passing subject could reveal it, because both hold one language.
|
||
|
||
**In review:** every new degradation reason must state the scope it reaches — one question, one file,
|
||
one partition, or the whole read — and a reason that demotes an outcome must justify reaching that
|
||
far. A gap that costs coverage without threatening correctness is a declared exclusion: it stays
|
||
published and it does not demote.
|
||
|
||
### A token matches the thing it names, not text that contains it
|
||
|
||
Every matcher in this project searches text for a declared name, and each time the search was allowed
|
||
to look somewhere the name does not appear AS the thing, it claimed something else.
|
||
|
||
| instance | what it matched instead | where it was fixed |
|
||
|---|---|---|
|
||
| `gets` inside `fgets` | the safe API reported as the dangerous one, 26 times | task 5.13 |
|
||
| `resolve` inside `resolveUrl` | a sanitizer that sanitized nothing | contributor-scan |
|
||
| `user` inside `users` in a SQL string | a textbook IDOR read as guarded | authorization-obligations |
|
||
| a sink word inside a comment | a prototype finding on a handler with no such call | task 14.1 |
|
||
| a dotted setting matched on its last segment | an editor's `setOptions` read as a template engine's | task 14.3 |
|
||
| a flow query reading the call's receiver | `res.end()`, which takes no data, reported like a write | task 14.3 |
|
||
|
||
**In review:** a text match must be bounded AND confined to the part of the node that names the
|
||
callee. Whole-node text, an argument subtree, a receiver and a trailing path segment are all places
|
||
the name can appear without being what is called.
|
||
|
||
## Boundary Commitments
|
||
|
||
### This Spec Owns
|
||
|
||
- Git push inputs, immutable snapshots, comparison identity, and change context passed to the ranker.
|
||
- Local artifact reuse, the shared push deadline, delta attribution and hook alert admission.
|
||
- Concise rendering, explicit finding/coverage/push dispositions, and integration instructions.
|
||
|
||
### Out of Boundary
|
||
|
||
- Ranking features, weights, tier definitions, and exploration: `flow-aware-ranking` owns these.
|
||
- Family facts, sanitizers, graph construction semantics and generic driver correctness:
|
||
`contributor-scan` owns these. Hook integration may extend contracts but may not fork the driver.
|
||
- Broader telemetry mechanisms: `zero-with-a-reason`; durable dismissals and learning:
|
||
`finding-feedback-loop`. Initial rollout does not require either to be complete.
|
||
- Vendor source analysis, new C memory abstractions, dependency CVE inventories, whole-tree assurance.
|
||
|
||
### Allowed Dependencies
|
||
|
||
Use Git, Python's standard library, `preprocess`, `mapping`, `model/regions`, `model/layout`,
|
||
`model/scan`, `model/evidence`, `cpg/backend`, existing arbiters, redaction and report records.
|
||
No new mandatory package or service. Dependency direction is existing engine/types -> push adapters
|
||
and policy -> push runner -> CLI. The engine never imports the push package; shared deadline and
|
||
artifact contracts live at the engine boundary. Ranker internals never depend on Git hook objects.
|
||
|
||
### Revalidation Triggers
|
||
|
||
Changes to engine/frontend/overlay versions, facts/sanitizers, layout, scope rules, ranker mode,
|
||
query depth, deduplication, finding identity, or alert admission invalidate relevant cached results
|
||
and require the security/fixed/benign push suite. Ranker changes also require untouched evaluation.
|
||
Driver contract changes revalidate ordinary `ousast scan`, coverage reports, and repository benchmarks.
|
||
|
||
## Architecture
|
||
|
||
### Existing Architecture Analysis
|
||
|
||
`preprocess_repository` reads the live filesystem and labels it with HEAD; this is not a pushed
|
||
snapshot. `scan_repository` builds a full-root CPG before selecting regions. `ScanBudget` limits
|
||
regions/model calls, not total elapsed time. Backend graphs are temporary, with per-command timeouts
|
||
and cleanup. `order_by_evidence` is implemented but disabled by the ordinary CLI caller.
|
||
|
||
Consequently the current ranker does not bound graph-build cost. A faster ordering alone cannot
|
||
establish a fast pre-push hook, and passing fewer regions must not be described as incremental parsing.
|
||
|
||
```mermaid
|
||
flowchart TD
|
||
A[Git ref updates] --> B[Immutable tips and comparison context]
|
||
B --> C[First-party inputs with vendor exclusions]
|
||
C --> D[Compatible graph or bounded graph build]
|
||
B --> E[Changed and affected context]
|
||
D --> F[Existing ranker selects scope]
|
||
E --> F
|
||
F --> G[Existing arbiters and targeted base comparison]
|
||
G --> H[Change attribution and actionability admission]
|
||
H --> I[Compact hook result and detailed artifact]
|
||
D --> J[Coverage and deadline accounting]
|
||
F --> J
|
||
G --> J
|
||
J --> I
|
||
```
|
||
|
||
The deadline surrounds the entire flow. Cache lookup does not bypass identity checks; a miss does
|
||
not trigger unbounded work. The default path is local and deterministic. An optional LLM may reorder
|
||
within allowed ranker constraints or explain existing evidence; it cannot mint facts or admit alerts.
|
||
|
||
### Language and Framework Contract
|
||
|
||
Requirements 8.1, 8.2, 8.3, 8.4 make abstract analysis a transfer contract, not a claim of automatic
|
||
language coverage. Frontends and versioned facts identify sources, sink argument roles, carried
|
||
values, dispatch, guards, obligations and configuration properties. The common evidence vector,
|
||
ranker scope decisions and arbiters consume these representations. Core ranking must not inspect
|
||
repository names, PHP syntax, WordPress calls or Express API names to assign priority. Equivalent
|
||
normalized evidence receives the same core tier and score regardless of origin; policy admission
|
||
also requires the independently evaluated capability for that origin.
|
||
|
||
Existing seams are `mapping.py` (route/middleware mapping), `ruleset/semantic/*.toml` and
|
||
`ruleset/obligations/*.toml` (reviewed language/framework facts), `model/specs.py` (abstract question
|
||
construction), `model/evidence.py` (shared ranking), and `cpg/queries/` (graph queries). Reuse them;
|
||
do not add a universal source IR or move framework checks into the hook. Frontend normalization
|
||
belongs at the backend/query boundary and must document unsupported constructs. Existing
|
||
language-specific integrations require audit; this design does not claim they already conform.
|
||
|
||
For Express, verify route registration and ordered middleware, request input, imported handlers,
|
||
service calls, callbacks and async boundaries. A function named `authenticate` does not establish
|
||
that its guard governs an operation. Require the same guard/obligation evidence as in other
|
||
frameworks. `node_modules` and other declared third-party trees stay outside targets AND graphs;
|
||
explicit framework/library summaries describe relevant behavior without analyzing vendor source.
|
||
Do not exclude first-party JavaScript merely because it is browser code: record its partition and
|
||
capability separately from the server. TypeScript support also needs frontend evidence, not an
|
||
assumption based on normalizing its language label to JavaScript.
|
||
|
||
PMPro is the development workload; Node/Express checks transfer of the frozen core. Framework/fact
|
||
adaptations are recorded separately from zero-change transfer, and any case used for repairs becomes
|
||
regression data. C and C++ use the same scheduling/reporting contract but may need different abstract
|
||
domains: taint transfer, guard dominance and numeric bounds are distinct capability claims. Keep
|
||
libpng as an envelope/unsupported-bounds control until a suitable domain is implemented and measured.
|
||
Concrete workload roles and required paired changes are in `evaluation.md`.
|
||
|
||
### Technology Stack
|
||
|
||
| Layer | Choice | Role |
|
||
|---|---|---|
|
||
| Push input | Installed Git, object-format-aware commands | Resolve refs, trees and immutable blobs |
|
||
| Runtime | Existing Python >=3.11 CLI and standard library | Transaction, cache, deadline, policy |
|
||
| Analysis | Existing Joern backend; evaluated baseline 4.0.625 | Graph and arbiters, version recorded |
|
||
| Storage | Local content-addressed directories and JSON manifests | Atomic reusable artifacts, no database |
|
||
| Presentation | Existing finding records plus compact hook renderer | Separate findings and coverage |
|
||
|
||
Docker, Compose and provisioning defaults were aligned to 4.0.625 in task 1.1, with a verified
|
||
local PHP/JavaScript image smoke. Host deployment and hook performance remain separate claims.
|
||
|
||
## File Structure Plan
|
||
|
||
New files (proposed, not created by this documentation change):
|
||
|
||
| Path | Responsibility |
|
||
|---|---|
|
||
| `src/openultrasast/contracts.py` | Strict immutable manifest serialization shared by engine and adapters |
|
||
| `src/openultrasast/model/contracts.py` | Generic deadline, change context and exact selected/deferred scope contracts |
|
||
| `src/openultrasast/cpg/artifact.py` | Backend-owned graph identity, census and live lease protocol |
|
||
| `src/openultrasast/push/contracts.py` | Immutable ref/comparison identities and comparison-owned completion/status records |
|
||
| `src/openultrasast/push/snapshot.py` | Parse ref updates, resolve bases, materialize blobs, produce change context |
|
||
| `src/openultrasast/push/cache.py` | Artifact keys, validity, atomic publication and eviction |
|
||
| `src/openultrasast/push/reuse.py` | Complete discovery reuse and backend graph/query reuse through the existing driver |
|
||
| `src/openultrasast/push/policy.py` | Delta identity, actionability admission and push disposition |
|
||
| `src/openultrasast/push/runner.py` | Shared deadline, ranker/engine orchestration, per-ref result aggregation |
|
||
| `src/openultrasast/push/report.py` | Compact terminal result and reproducible push manifest |
|
||
| `benchmarks/push/README.md` and `benchmarks/push/measure.py` | Labeled push recipes and joint quality/latency measurement |
|
||
| `tests/test_push_snapshot.py`, `tests/test_push_cache.py`, `tests/test_push_policy.py`, `tests/test_push_runner.py` | Contract and integration checks below |
|
||
|
||
Contract configuration (task 1.4) uses `[push]`: optional `comparison_base`, positive
|
||
`deadline_seconds` (30), positive `cancellation_allowance_seconds` (2), positive integer
|
||
`cache_max_bytes` (2 GiB), `mode` (`advisory` by default, or `blocking`) and independent
|
||
`incomplete_coverage_policy` (`allow` by default, or `block`). Unknown keys, malformed
|
||
sections and coerced/nonfinite numeric values are rejected. Experimental replay consumes the deadline, mode, coverage policy and optional cache budget.
|
||
An explicit `--cache-dir` enables local reuse; omission retains a cold control. These settings
|
||
do not install a hook or admit experimental capabilities. The cache
|
||
limit is a local storage budget, not a latency claim. Completion records are owned by an
|
||
exact comparison so reused head graphs cannot merge evidence from different bases. Live
|
||
leases have no JSON constructor; only the backend can validate and issue them in task 6.1.
|
||
|
||
Modified files: `cli.py` and `config.py` add explicit push command/configuration;
|
||
`model/regions.py` carries change context without Git-specific logic; `model/scan.py` consumes the
|
||
ranker's selected questions and returns exact selected/deferred IDs, deadline and reusable-graph
|
||
contracts; `cpg/backend.py` owns graph artifact validation and process cancellation. Existing
|
||
`model/evidence.py` changes only through the ranking spec. Packaging pin fixes remain in existing
|
||
Docker/Compose/ops paths. `README.md` documents opt-in integration without overwriting existing hooks.
|
||
|
||
Cross-language validation reuses `tests/test_model_evidence.py` for equivalent normalized evidence,
|
||
`tests/test_model_scan.py` for shared scope/coverage behavior, and existing mapping/fact tests for
|
||
frontend adaptation. Reviewed workload recipes use `benchmarks/repos/*.toml` and the proposed
|
||
`benchmarks/push/` harness. Necessary mapping/fact/query fixes stay within the engine/ranking specs;
|
||
they do not become JavaScript-specific branches in `push/`.
|
||
|
||
## Components and Interfaces
|
||
|
||
### Snapshot Adapter
|
||
|
||
`PushUpdate(local_ref: str, local_oid: str, remote_ref: str, remote_oid: str)` records every input
|
||
line. `PushComparison(head_oid: str, base_oid: str | None, base_reason: str, refs: tuple[str, ...])`
|
||
records immutable identity. Object IDs are validated through Git; no hard-coded SHA-1 length.
|
||
|
||
For existing refs, compare the supplied old and new tips, including force-pushes. For a new branch,
|
||
use a configured comparison ref resolved to an OID and its unique merge base with the pushed tip;
|
||
record that this is a chosen baseline, not the previous remote branch. No configured/unique/local
|
||
base means comparison incomplete. Do not substitute HEAD or a remote-tracking ref silently. No
|
||
automatic fetch inside the hook. Peel annotated tags to commits; other objects are unsupported.
|
||
Deletion updates require no target scan. Deduplicate identical target graphs, but retain separate
|
||
comparisons when the same target has different bases. Multiple refs share one deadline.
|
||
|
||
The snapshot implementation uses `SnapshotAdapter.resolve_updates`, the owned `materialize`
|
||
context manager, and `compare`. Preparation has explicit file, source-byte, tree/diff-output and
|
||
line-mapping limits. Materialization verifies blob identity and records omitted symlinks, gitlinks
|
||
and LFS pointers. Its completeness flag describes source preparation, not security coverage.
|
||
The shipped implementation uses Linux directory-relative cleanup that does not follow links.
|
||
|
||
Materialize tracked blobs directly into isolated scratch storage; do not checkout over the user's
|
||
tree or run filters/build scripts. Use NUL-delimited tree/diff records and argument arrays. Include
|
||
deletions as impact seeds and retain rename attribution. Symlinks are not followed outside the
|
||
snapshot; gitlinks, absent LFS content and unsupported objects produce explicit coverage limits.
|
||
Target-tree content is the analysis population; intermediate history is out of scope.
|
||
|
||
### Ranker Scope Contract
|
||
|
||
The snapshot adapter provides `ChangeContext`: base/head identities, changed/deleted paths and spans,
|
||
manifest/config changes, and known affected first-party relationships. It supplies evidence, not
|
||
an alternative inclusion policy. The existing ranker produces `ScopeDecision`: selected question
|
||
IDs, their priority/evidence, deferred question IDs with reasons, and unresolved context boundaries.
|
||
Identifiers include unit/language/path/function/family to avoid collisions across partitions.
|
||
|
||
Adapter-produced contexts declare `path_encoding="filesystem-bytes-hex"` for structural paths
|
||
(changed/deleted/declaration paths, spans, renames and line correspondences). Use
|
||
`ChangeContext.decode_path` when matching canonical question paths; semantic relationship
|
||
`QuestionIdentity` paths retain their normal encoding. Existing text-context construction remains
|
||
compatible. This preserves unusual and non-UTF-8 filesystem names without ambiguous display escaping.
|
||
The caller supplies known declaration/configuration paths as raw bytes; an unavailable declaration
|
||
inventory is explicitly unresolved. Unique identical lines outside edit hunks provide lexical
|
||
correspondence even after a declaration rename or guard removal. Repeated lines, unmatched moves
|
||
and ambiguous rename attribution remain unresolved; these anchors do not prove semantic function
|
||
identity, guard effects or defect novelty. Those decisions remain with tasks 3.3 and 4.1.
|
||
|
||
Third-party vendor exclusions apply before both preprocessing targets and graph construction.
|
||
Tests remain available but unshipped, consistent with the approved layout decision. The ranker
|
||
cannot reintroduce vendor sources. Known external summaries are versioned facts; without a relevant
|
||
summary, a claim depending on excluded internals is unresolved. Do not assume a missing external
|
||
call is safe. Entry/callee, field, hook and removed-guard context must be represented in the ranked
|
||
question; an unchanged sink may be affected by changed reachability or sanitization.
|
||
|
||
Graph construction and question scope are distinct costs. Initial graph input is the first-party
|
||
repository per supported frontend, or an explicitly declared analysis unit with recorded boundaries.
|
||
Do not create an additional heuristic source-slicer. A mixed-language repository has explicit
|
||
frontend partitions and coverage per partition; unsupported cross-language edges remain unresolved.
|
||
Use old graph relationships only as prioritization hints after a code change, never as current proof.
|
||
If a ranked question's necessary first-party context is absent, expand its declared unit within the
|
||
remaining budget or report it unresolved. No claim of exact tier-0 pruning extends beyond the
|
||
modeled graph/facts/scope; unanswered evidence is never tier 0.
|
||
|
||
### Cache and Engine Boundary
|
||
|
||
The initial driver uses a generic `model/partitions.py` graph handle to route existing
|
||
question requests to physically filtered frontend inputs. It does not select questions:
|
||
one existing ranker order, region budget and `ScopeDecision` cover all partitions.
|
||
`PartitionCoverage` records language, frontend, input paths and byte count, excluded
|
||
paths, unshipped test paths, build status and capability boundaries. Query locations
|
||
return to repository-relative paths before change-context matching. Projection and
|
||
cleanup consume the same execution deadline and cancellation allowance as the backend.
|
||
Runtime origin is `unspecified` when no declaration establishes browser/server scope;
|
||
JavaScript presence alone does not admit an Express capability. TypeScript and C/C++
|
||
retain explicit capability limits. These records prepare later artifact manifests;
|
||
task 6 now binds them to backend-owned persistent graph manifests and isolated leases.
|
||
|
||
Graph key: digest of every included blob/path and applicable declarations, frontend/engine version,
|
||
overlay semantics/options, language, and exclusions. Query key additionally includes graph key,
|
||
question/context/depth, facts/sanitizers, query code and relevant analysis configuration. Result key
|
||
also includes ranking/admission versions and any model/prompt identity. Comparison results include
|
||
both base and head identities. Persist manifests with source byte counts and graph census.
|
||
|
||
These identities deliberately separate graph construction from ranking experiments. A ranking-only
|
||
change can reuse a compatible graph and unchanged query evidence while invalidating affected ranked
|
||
results. A query/fact change invalidates its answers without automatically rebuilding a graph whose
|
||
input/frontend/overlay semantics are unchanged. This supports iteration on the core as well as
|
||
repeated push checks; it does not authorize reusing stale evidence after a source change.
|
||
|
||
Preparation is also a reusable artifact boundary. Task 5.2 measured PMPro exhausting
|
||
30 seconds after materialization and before graph construction. Cache complete discovery
|
||
by immutable source tree identity plus discovery code, shipping/layout facts and configuration;
|
||
retain snapshot readability proofs and declaration/boundary records. Reuse must preserve
|
||
repository-relative locations and must never deserialize repository-controlled executable objects.
|
||
Measure preparation, graph and query hits separately. An identical-tip hit demonstrates reuse,
|
||
not representative changed-code latency. Dependency/configuration changes invalidate affected
|
||
preparation and graph/query context even when an individual source file is unchanged.
|
||
|
||
Rollout approval remains an evidence decision under Requirement 6.5 and evaluation.md.
|
||
Group 6 establishes correctness and bounded reuse; group 8 must still demonstrate the joint
|
||
changed-code latency, completion, precision and recall gates on declared populations. Missing
|
||
independent positive/fixed/benign populations or an empty capability registry cannot yield GO.
|
||
No threshold, population denominator or alert eligibility rule is relaxed by this amendment.
|
||
|
||
Only validated, complete artifacts can satisfy complete-result lookups. Partial artifacts may be
|
||
retained for diagnostics but never promoted by cache presence. Publish atomically from private
|
||
temporary paths under a per-key lock; another push may wait only within its deadline. Apply a
|
||
configured cache size limit and evict unleased entries. No live graph mutation or dependence on
|
||
CPG node IDs remaining stable across builds. A changed input invalidates that graph; incremental
|
||
Joern mutation is neither assumed nor promised. Unchanged declared units and identical snapshots
|
||
can reuse graphs; changed units rebuild. Measure whether this is enough before claiming scale.
|
||
|
||
The maintainer confirmed interest in persistence followed by differential updates on 2026-09-12.
|
||
Persistence is the first optimization; differential graph mutation remains a separate future
|
||
experiment. Its acceptance gate must compare against a cold rebuild on identical inputs and
|
||
semantics, including changed/deleted/renamed declarations, affected call edges and dataflow
|
||
overlays, and invalidation of dependent query/results artifacts. Updating only changed file nodes
|
||
is insufficient. No graph partition may claim complete cross-partition analysis without validated
|
||
relationship handling. This sequencing does not expand the approved implementation into a new
|
||
incremental engine.
|
||
|
||
The backend owns `GraphArtifact` validation/lifecycle. The generic driver accepts an optional
|
||
validated graph lease and an absolute monotonic deadline, preserving its ordinary fresh-build path.
|
||
Queries operate on an isolated lease if loading can modify a graph. Every subprocess timeout is
|
||
bounded by remaining transaction time, including fallback frontend, overlay, shard and retry work.
|
||
Cancel process groups, close leases, then persist coverage within the cancellation allowance.
|
||
|
||
### Delta and Actionability Policy
|
||
|
||
Admission requires all of: an enabled/evaluated capability, `model_entailed` or a future actually
|
||
produced `execution_confirmed` result, a locatable witness, applicable family semantics, no unresolved
|
||
dependency that undermines the claim, supported change attribution, and a concrete repair direction.
|
||
No production caller currently reaches `execution_confirmed`; it is not a prerequisite or a promise.
|
||
|
||
Findings are keyed by family/mechanism and stable source/operation identity, with rename/span mapping;
|
||
line numbers alone do not establish novelty. Re-query relevant base counterparts under the same
|
||
semantics and sufficient comparable context. A missing old finding from a truncated/failed scan is
|
||
unknown, not proof that the head introduced a bug. A removed guard or newly connected source can
|
||
establish a worsened flow at an unchanged sink. Unknown novelty stays in the diagnostic artifact
|
||
with one aggregate comparison-coverage notice. Initial checkout is not automatic acceptance of debt.
|
||
|
||
Deduplicate by defect, retaining distinct witnesses and ref associations. Explain input/operation,
|
||
security consequence, why this change matters and a specific repair locus; do not require automatic
|
||
patch generation. Templates grounded in facts/witnesses are sufficient. Admission rejects generic
|
||
"sanitize input" advice without a supported context/operation. An LLM explanation must cite the
|
||
same witness and cannot raise the rung or invent exploitability.
|
||
|
||
Task 4.2 binds each delta to its base revision, head revision and semantics identity;
|
||
legacy unbound evidence remains readable but cannot be admitted. Eligibility declarations
|
||
are trusted outputs of the later evaluation loader, with positive/reviewed populations,
|
||
calibration and untouched-workload provenance. The default registry is empty. Each
|
||
declaration narrows its semantic context to exact operation symbols, preserving receiver
|
||
spelling; a broad family such as injection cannot select shell advice for a SQL operation.
|
||
Unknown or dynamic spellings remain ineligible. This is admission over existing graph
|
||
witnesses, not a new detector or an alias resolver. Per-candidate records retain the full
|
||
matching evaluation declarations and exact comparison/ref ownership.
|
||
|
||
### Result and Hook Adapter
|
||
|
||
`PushResult` separates `finding_status` (`actionable` / `none`), `coverage_status` (`complete_within_scope`
|
||
/ `incomplete` / `unavailable` / `not_applicable`) and `push_disposition` (`allow` / `block`). Zero
|
||
selected questions cannot mean complete when applicable questions were deferred or context is unknown.
|
||
The detailed manifest retains per-ref/per-capability coverage, supported and unsupported populations,
|
||
all admitted defects, rejected-candidate reasons, exact scope and timings.
|
||
|
||
Initial profile default: advisory, with the same admission policy as blocking. Blocking is opt-in and
|
||
returns nonzero only for admitted new/worsened findings; incomplete coverage defaults to allow plus
|
||
a notice, with a separately configurable strict coverage policy. Show up to three concise defect
|
||
summaries plus the remaining count/artifact path; the cap never changes the exit decision. Do not
|
||
render the full SAST backlog. No actionable findings: at most one bounded status line. Incomplete:
|
||
one notice naming the primary reason and a concrete retry/diagnostic action. Unchanged results may
|
||
be reused, but a still-relevant admitted defect is not dismissed merely because it was printed before.
|
||
|
||
Expose a proposed `ousast pre-push` command receiving Git's stdin and remote arguments, plus explicit
|
||
base/head replay. Hook installation is explicit: preserve `core.hooksPath`, existing hook exit
|
||
semantics, and stdin for other consumers. Provide an integration snippet and refuse automatic
|
||
overwrite of an existing hook. Analysis runs no project code and performs no source modification.
|
||
|
||
## Requirements Traceability
|
||
|
||
| Requirements | Design elements |
|
||
|---|---|
|
||
| 1.1, 1.2, 1.3, 1.4 | Snapshot Adapter: immutable objects, per-ref comparisons, special update cases |
|
||
| 2.1, 2.2, 2.3, 2.4 | Ranker Scope Contract: change context, vendor exclusion, boundary/guard handling |
|
||
| 3.1, 3.2, 3.3, 3.4, 3.5 | Delta and Actionability Policy: admission, witnesses, comparison and deduplication |
|
||
| 4.1, 4.2, 4.3, 4.4 | Cache and Engine Boundary: shared deadline, complete artifact identity and leases |
|
||
| 5.1, 5.2, 5.3, 5.4, 5.5 | Result and Hook Adapter: separate statuses, compact rendering, explicit policies |
|
||
| 6.1, 6.2, 6.3, 6.4, 6.5 | Testing Strategy and Rollout: capability admission and joint gates |
|
||
| 7.1, 7.2, 7.3, 7.4 | Local runtime, model redaction/budget, manifests, non-destructive integration |
|
||
| 8.1, 8.2, 8.3, 8.4 | Language and Framework Contract; frozen-core transfer and workload matrix in evaluation.md |
|
||
|
||
## Error Handling
|
||
|
||
Unreadable input or failed census invalidates the affected graph. Missing base invalidates novelty
|
||
comparison. Missing context invalidates affected checks. Deadline exhaustion preserves completed
|
||
findings and records deferred work. These states remain visible even when the hook permits the push.
|
||
If result persistence fails, print one artifact-write notice and retain the real finding/coverage
|
||
decision in the terminal; do not fabricate a successful cache entry or hang while retrying.
|
||
|
||
## Testing Strategy and Rollout
|
||
|
||
1. Snapshot contracts (1.1–1.4, 7.1, 7.4): dirty trees, non-HEAD pushes, multiple refs with different
|
||
bases, force-push/new-branch/missing-object cases, tags, deletion-only pushes, unusual paths,
|
||
symlinks/gitlinks, and coexistence with an existing hook.
|
||
2. Ranker/context tests (2.1–2.4): handler-to-unchanged-sink, field/hook flows, removed guards,
|
||
first-party cross-file context, vendor source absent from both graph and targets, unresolved
|
||
vendor boundary, polyglot partition coverage. No second selection heuristic in the push adapter.
|
||
3. Admission tests (3.1–3.5, 5.1–5.5): high rung with wrong/unsupported semantics is rejected;
|
||
unchanged backlog and missing-base claims stay out; new supported defects include actionable
|
||
witnesses; one defect reached by many regions prints once; advisory and blocking share admission.
|
||
4. Deadline/cache tests (4.1–4.4, 7.3): source/context/fact/query/version invalidation, corrupt/partial
|
||
entries, concurrent pushes, timeout during build/overlay/query/retry, process-group termination,
|
||
no repeated per-ref budget reset, exact selected/deferred IDs rather than slicing old order.
|
||
5. Real push evaluation (6.1–6.5, 8.1–8.4): use PMPro for core development, then freeze the core
|
||
and replay labeled introducing/fixing/benign changes on a JavaScript/Express Node API workload.
|
||
Preserve Python regressions and a separate untouched Node API repository; training applications
|
||
and cases used to fix mapping are not fresh holdouts. Include injection, path and authorization/
|
||
configuration where supported, plus middleware and cross-module/async controls. Test equivalent
|
||
normalized evidence for identical core decisions. Keep libpng's envelope and unsupported bounds
|
||
separate from a future C/C++ positive control. See `evaluation.md` for selection and labeling gates.
|
||
|
||
Measure the accepted experimental acceptance profile in requirements on declared hardware and fixed workloads.
|
||
Warm means prior reusable work from the preceding state, not only repeating the identical tip.
|
||
Report identical-tip hits separately from a one-function change and a dependency/configuration
|
||
change. Include first installs, cold graphs, growing repository size, multi-ref pushes and incomplete
|
||
outcomes. Freeze the applicable population before ranker selection; deferred questions count against
|
||
completion. Report p50/p95 plus timeout counts, observed precision with confidence intervals, recall,
|
||
benign interruption rate and fixed-side silence. A low-noise score with low recall/coverage fails.
|
||
|
||
Rollout order: reconcile engine pins and benchmark honesty; prove one end-to-end change/witness/fix
|
||
with the existing ranker; establish deadline/cache feasibility; run untouched and benign evaluation;
|
||
admit passing capabilities in advisory mode; consider opt-in blocking only after these gates pass.
|
||
LLM ranking is a later experiment, not a prerequisite. If realistic changed-unit rebuilds cannot
|
||
meet the deadline/coverage gates, keep the hook experimental and revisit the graph strategy with
|
||
measurements; do not solve the miss by silently shrinking context or lowering alert standards.
|
||
|
||
### Group 7 integration decisions
|
||
|
||
The Git interface produces one aggregate report, retaining all resolved updates and
|
||
per-comparison change contexts. Deletions/no-work are not applicable; unresolved
|
||
updates remain gaps. The hook installer resolves only the effective hook directory
|
||
before exclusively creating the final filename, so final symlinks cannot redirect an
|
||
installation. Explicit prior hooks retain their original input, arguments and exit
|
||
behavior; their own runtime is separate from the Safety Net analysis deadline.
|
||
|
||
Optional model assistance is presentation-only witness selection after admission.
|
||
Only an existing admitted witness index can be selected. No generated prose becomes
|
||
an alert, and no model response changes scope, evidence strength, novelty, admission
|
||
or enforcement. A model/prompt-specific response is not cached; deterministic analysis
|
||
artifacts still use their existing semantic identities. Configuration, prompt
|
||
preparation and HTTP calls share the transaction deadline. Pre-push never implicitly
|
||
loads the working tree's `.env`; explicit model configuration uses the existing loader
|
||
with that behavior disabled. Unknown price or missing usage is an unknown cost, not zero.
|