Files
Open_Ultra_SAST/docs/memory.md
T
Marius CiepluchandClaude Opus 5.5 95a252b89b docs: MkDocs site with flow diagrams; RustFS permissions
Material for MkDocs site (mkdocs.yml, optional extra `docs`) with Mermaid via
the superfences custom fence. New pages: overview, scanning (modes, quick
rules, Joern engine, priors, pre-push delta), the plane on ax (one task as a
sequence, golden snapshot, Run DAGs), memory (row kinds, write/read paths,
FileStore vs S3, verify at startup), memory and detection, and RustFS setup
(admin steps, complete agent policy, .env, refusal messages, Select limit).
The ops READMEs are included at build time; evaluation gains the population
lifecycle and the fold layout. tests/test_docs_site.py runs the strict build
when mkdocs is importable and checks Mermaid blocks and the RustFS page.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01EmM1j46qgKMh3zqD1QMwEo
2026-10-02 08:17:40 +02:00

132 lines
7.6 KiB
Markdown

# Memory
One store, keyed by repository and pin, holds what plane runs and the decision engine learned.
The code is `src/openultrasast/plane/memory.py`; the same layout serves both backends.
How the stored rows feed detection is on [Memory and detection](memory-and-detection.md).
## What is stored
```
index.jsonl one row per ingested (run, task): sha256 of its rows, row count
facts/<sha256>.json repository facts by content (identical facts stored once)
repos/<host>__<owner>__<name>/<pin>.jsonl the rows of one repository + 40-hex pin, sorted by id
excerpts/<sha256>.txt code excerpts, by content
embeddings/<model>/<excerpt sha>.json an excerpt's embedding vector, per embedding model
responses/<request sha>.json cached model responses
programs/<program id>.json compiled decision-engine programs
```
**Rows.** Every row carries `id`, `kind`, `repo`, `pin`, `run`, `task`, `population`, `split`
and `image` (`ROW_FIELDS`); `id` is the sha256 of (kind, run, task, subject), so a repeated row
replaces itself. The kinds (`KINDS`) and who writes them:
| Kind | Written by | Holds |
| --- | --- | --- |
| `facts` | `remember` task | the facts entry of a case (points at `facts/<sha256>.json`; reused by memory key) |
| `verdict` | `remember` task | per candidate: each verify pass's answer, the final outcome, whether a tie-break ran, cost |
| `unit_cost` | `remember` task | per verify pass and file: usd, calls and token usage |
| `alert` | `remember` task | a quick-scan or engine alert on the vulnerable or fixed pin (loop Runs) |
| `coverage` | `remember` task | per language and pin: whether any rule could have fired there |
| `features` | `remember` task, from the `features` task | one feature record per candidate and family |
| `proposal_outcome` | `ousast improve --memory` (not with `--dry-run`), and the loop's `improve` step | what happened to each memory proposal in a round |
| `example` | `ousast learn memory build` | a labelled case: label + feature record + excerpt sha (no rationale) |
| `label`, `decision`, `experiment`, `arm_outcome`, `experiment_result` | reserved for later decision-engine tasks | declared in `KINDS`; not written by the code on main yet |
**Blobs** (`put_blob`) are content-addressed: a 64-hex name under a fixed prefix. Excerpts are
written by `learn memory build` (`learn/examples.py`), embeddings by `learn memory embed`
(`learn/embeddings.py`, keyed by excerpt sha so an excerpt is embedded once), responses by the
program's caller (`learn/program.py`, keyed by the sha256 of model, parameters digest, messages,
temperature, sample and format, so a rerun replays at no cost), and compiled programs by
`learn compile` (`learn/compile.py`).
## Write path
```mermaid
flowchart LR
tasks["Plane tasks: facts, verify a/b/c, agree, alerts, features"] --> rem["remember task: memory.jsonl"]
rem --> recv["Delivered to the host with the run's artifacts"]
recv --> ingest["ingest after ousast plane run, or ousast plane remember RUN"]
loop["Loop improve step: proposal_outcome"] --> ingest
improve["ousast improve --memory: proposal_outcome"] --> put
build["ousast learn memory build: labels + features + excerpts"] --> put["put_rows, put_blob"]
embed["ousast learn memory embed"] --> put
compile["ousast learn compile, evaluate"] --> put
ingest --> idx{"index.jsonl: same run, task and sha256?"}
idx -- "yes" --> skip["Skipped"]
idx -- "no" --> put
put --> store[("Memory store")]
```
- `ingest` validates every row first: a malformed file fails before anything is written.
- Rows are merged into their repository + pin object by `id`. Every object is written whole and the
index last, so an interrupted ingest leaves nothing a repeat would not repair.
- Each row object carries tags (`TAG_FIELDS`: `repo`, `pin`, `kind`, `family`, `run`,
`population`, `split`) listing the distinct values of its rows (`*` when they exceed 256
characters).
- The plane's store failures never change a run's result: an ingest can be repeated by hand
(`cli.py`, `_plane_memory`).
## Read path
```mermaid
flowchart LR
store[("Memory store")] --> seed["seed before a Run"]
seed --> reuse["Facts reused by memory key: repo, pin, candidates, runner image"]
seed --> snap["memory-snapshot: rows after the train-on-test guard"]
store --> examples["learn: load example rows, excerpts, embeddings"]
examples --> retr["Retrieval of similar labelled cases"]
store --> responses["Response cache: replay at no cost"]
store --> imp["ousast improve --memory: alert, verdict, coverage rows"]
imp --> guard["Train-on-test guard"]
guard --> rules["Rules M1, M2 to rule-status proposals"]
rules --> gate{"Unchanged validator and gate"}
```
- **Seed** (`memory.seed`): a Run task whose `openultrasast.io/memory-key` matches stored facts gets
them and is marked done; a changed pin, candidate set or runner image finds nothing and
recomputes. The loop's `memory-snapshot` is seeded the same way, because a sandboxed task cannot
read the store.
- **Retrieval** (`learn/retrieve.py`): see
[Memory and detection](memory-and-detection.md#3-retrieval-of-similar-labelled-cases).
- **`improve --memory`** (`improve/memory.py`): rows from holdout pairs, from the gated manifest's
own cases and from any `--qualify-population` are dropped before any rule sees them. See
[Architecture](architecture.md#proposals-from-plane-memory).
## Backends: FileStore and the S3 store
`OUSAST_MEMORY` selects the backend (`open_store`):
| | `FileStore` | `MinioStore` (S3: RustFS or MinIO) |
| --- | --- | --- |
| Selected by | `file:///path`, or nothing (default `$OUSAST_RESULTS/plane/memory`) | `minio://<bucket>[/<prefix>]` |
| Needs | a local disk; refuses to write below 1 GiB free | the `minio` extra, `MINIO_*` settings from `.env` or the environment |
| Filtered reads | read and filter locally | S3 Select pushdown (`SelectObjectContent` over JSON Lines); kind filter by object tag |
| Object version cited by provenance | the file's content hash | the bucket's object version id |
| Bucket setup | none | done once by an admin, verified at every start ([RustFS setup](rustfs.md)) |
### Verify at startup, no fallback
The S3 store never configures its bucket. Each time a `MinioStore` is opened for a bucket and
prefix (once per process), `verify_bucket()` checks, using reads plus one small probe object at
`<prefix>/_probe/select.jsonl`:
1. versioning is `Enabled` (provenance cites object versions);
2. an enabled lifecycle rule expires `<prefix>/runs/` after a number of days, as a plain prefix
filter, and no enabled rule expires the whole store;
3. S3 Select answers a probe query over JSON Lines;
4. the probe's object tags are readable.
Anything missing is named, with the admin command that fixes it, in one `MemoryStoreError`, and
the store does not start. There is no local or fetch-and-filter fallback: a filtered read that
S3 Select cannot answer raises a `MemoryStoreError` instead of returning "no rows". The exact
messages are listed on [RustFS setup](rustfs.md#what-the-store-verifies-at-startup).
### Queryable fields per kind (in progress)
RustFS's Select infers an object's JSON schema from its leading rows, so a `where` field missing
there fails with `EvaluatorBindingDoesNotExist` even when a later row has it (measured
2026-10-02; `memory.py`, `MinioStore._select`). On main today the store turns that failure into a
`MemoryStoreError` naming the field, never into an empty answer. A fixed set of queryable fields
per row kind, so that every filtered field is present in every row of its kind, is in progress
and not on main as of this page.