mirror of
https://github.com/norandom/OpenUltraSAST.git
synced 2026-11-13 17:58:18 +00:00
74 lines
4.1 KiB
Markdown
74 lines
4.1 KiB
Markdown
# Code Standards (2026-10-29)
|
|
|
|
Applies to new code and amended code from this date. Existing modules are grandfathered
|
|
until their migration is scheduled in the breadth map; no mass renames.
|
|
|
|
## Specification level (the horizontal-capability rule)
|
|
|
|
Specifications describe the way and the concept, not per-target inventories.
|
|
|
|
- A spec defines **capabilities of the acquisition mechanism**: how recognition
|
|
knowledge for any target is represented, proposed, verified, and promoted. It never
|
|
enumerates a target's vocabulary (`ServletHolder`, `doNotifyCommit`,
|
|
`voteOnAttribute`, ...) as work items or acceptance criteria.
|
|
- Per-target knowledge is **data** (fact rows, strategy registrations) produced through
|
|
the acquisition pipeline -- an LLM can produce it per target; the engine verifies it.
|
|
- Concrete repositories and frameworks appear in specs only as **benchmark evidence**:
|
|
named cases the acquired data must satisfy, never lists to implement.
|
|
- Litmus: if a requirement's acceptance criteria would not survive swapping the target
|
|
repository family, the requirement is written at the wrong level. Lift it.
|
|
|
|
## Engine / target split (the anti-monolith rule)
|
|
|
|
The abstract-interpretation engine is agnostic. Target knowledge (framework names,
|
|
recognition vocabulary, WordPress/Stapler/Symfony/Jetty/Laravel specifics) never lives
|
|
inside engine modules.
|
|
|
|
- **Engine modules** (`semantic/obligations/check.py`, `dominance.py`, `cfg_dominance.py`,
|
|
`paths.py`, `resolution.py`, `operations.py`, `trace.py`): no framework or product names.
|
|
If a framework identifier appears in an engine module, the split has been violated.
|
|
- **Target knowledge lives in exactly two visible places:**
|
|
1. **Data**: `ruleset/obligations/*.toml` and future fact files -- operation rows,
|
|
discharger rows, handler-name families. Adding a framework's vocabulary is a data
|
|
change, reviewable in one place.
|
|
2. **Strategy modules**: one module per recognition concern under `mapping/` (registry
|
|
pattern, see below), each file naming its target in its filename and registering
|
|
itself in one table.
|
|
- **The system adjusts by growing data and strategies, never by editing the engine.** The
|
|
fact-proposal lane (LLM proposes, engine verifies, repetition promotes to rows) is the
|
|
automated path for the same rule.
|
|
|
|
## Pattern discipline (GoF)
|
|
|
|
- **Strategy + Registry** for entry recognisers: `mapping/` gains a recogniser registry;
|
|
each framework family is one strategy module. New recognisers land there; the legacy
|
|
blocks inside `mapping.py` migrate family by family (tracked in the breadth map).
|
|
- **Interpreter (data-driven)** for obligation facts: TOML rows are the language; the
|
|
checker interprets them. New target vocabulary = new rows.
|
|
- **Adapter** at engine seams: `hunt/prover.py`'s callers adapter binds the IR to the
|
|
caller index; witnesses flow into the checker through one interface
|
|
(`check_obligations`).
|
|
- **Factory** for witnesses stays inside `obligations/enforcement.py` for now, but
|
|
enforcement is a target-aware seam (it reads Symfony/Laravel/Tornado constructs): treat
|
|
it as adapter territory; engine modules must not import it.
|
|
|
|
## Complexity caps
|
|
|
|
- Cyclomatic (McCabe) complexity: **<= 10 per function**.
|
|
- Logical lines of code: **<= 50 per function, <= 400 per new module**.
|
|
- A task is not done while a function it touched exceeds the caps; split the function.
|
|
- Automated gate (ruff C901 / xenon in CI) is not wired yet; caps are enforced at review
|
|
time. Wiring the gate is tracked in the breadth map.
|
|
|
|
## Idiomatic naming
|
|
|
|
- Names state what a value semantically is, in the pipeline's vocabulary: `entry_point`,
|
|
`trace`, `obligation`, `witness`, `discharge`, `finding`, `judgment`.
|
|
- Collections name their elements: `entry_point_records`, `judgments_by_key`,
|
|
`findings_by_push`. Mappings use `_by_<key>`; sequences are plural. No `eps`, `recs`,
|
|
`d2`, `tmp2`.
|
|
- Booleans are predicates: `is_public`, `has_dominating_guard`, `dominates_entry`. Avoid
|
|
negated names (`not_unflagged`).
|
|
- Row/record destructuring uses the record's field names, not positional `a, b`.
|
|
- Renaming debt in existing modules is recorded in the breadth map, not fixed drive-by.
|