merge(cleanup): second legacy inventory -- vector index, equality test, unread config keys, status notes

Also drops the removed ousast index line from the scan skill.

Co-Authored-By: Claude Fable 5.1 <[email protected]>
Claude-Session: https://claude.ai/code/session_01EmM1j46qgKMh3zqD1QMwEo
This commit is contained in:
Marius Ciepluch
2026-10-02 20:02:53 +02:00
co-authored by Claude Fable 5.1
17 changed files with 75 additions and 454 deletions
@@ -29,7 +29,6 @@ uv run ousast scan <repo> --mode quick
uv run ousast scan <repo> --mode standard --fail-on verified
uv run ousast scan <repo> --mode deep --fail-on worth-fixing
uv run ousast scan <repo> --mode quick --config openultrasast.toml
uv run ousast index <repo> # chunk the checkout for the embedding index
uv run ousast mcp # the MCP server over stdio, for agent hosts
```
+1 -2
View File
@@ -6,9 +6,8 @@
DEEPSEEK_API_KEY=
# DEEPSEEK_BASE_URL=https://api.deepseek.com
# Embeddings only: OpenAI text-embedding-3-small through OpenRouter.
# Embeddings only (`ousast learn memory embed`, default openai/text-embedding-3-small through OpenRouter).
OPENROUTER_API_KEY=
OPENROUTER_EMBEDDING_MODEL=openai/text-embedding-3-small
# Plane memory store. Default is a local FileStore under ~/ousast-results/plane/memory.
# For an S3-compatible server (RustFS is the tested one): OUSAST_MEMORY=s3://<bucket>[/<prefix>], or
+2 -2
View File
@@ -1,9 +1,9 @@
{
"feature_name": "ai-service-plane",
"created_at": "2026-09-29T15:40:00Z",
"updated_at": "2026-09-29T17:55:00Z",
"updated_at": "2026-10-02T00:00:00Z",
"language": "en",
"phase": "implementation",
"phase": "implementation-complete",
"approvals": {
"requirements": {
"generated": true,
@@ -1,5 +1,7 @@
# Requirements Document: contributor-scan
> **Status 2026-10-02:** paused since 2026-09-23 (its last change). 27 of 51 tasks done: the MAP integration was delivered and `v1.2.0-alpha.1` rolled out NO-GO (`census-repair-verification.md`); the remaining tasks wait on `pre-push-safety-net` M2/M3 and nothing here moved after the ax plane landed. Not active.
## Introduction
`model-grounded-detection` built an arbiter and measured it: 41.3% of the corpus reaches `model_entailed`
@@ -1,5 +1,7 @@
# Requirements Document: finding-feedback-loop
> **Status 2026-10-02:** dormant. Requirements approved and design generated on 2026-09-09; the design was never approved and no tasks exist. It depends on `contributor-scan`, which is paused; reopen it with that spec.
## Introduction
Nine engine defects were found in one session against two repositories. **Three were the same bug in three
+4 -5
View File
@@ -200,10 +200,10 @@ Counted 2026-10-02 from each spec's `tasks.md` checkboxes and `spec.json`.
|---|---|---|
| `learned-decision-engine` | implementation | 11 of 17 tasks done |
| `pre-push-safety-net` | implementation | 67 of 79 tasks done |
| `contributor-scan` | implementation | 27 of 51 tasks done |
| `ai-service-plane` | implementation | 11 of 11 tasks done; the ax plane is the agentic path |
| `contributor-scan` | paused | 27 of 51 tasks done; paused since 2026-09-23 (`v1.2.0-alpha.1` NO-GO), waits on pre-push M2/M3 |
| `ai-service-plane` | complete | 11 of 11; the ax plane is the agentic path |
| `harnessx-removal` | complete | 8 of 8; HarnessX removed 2026-09-30 |
| `finding-feedback-loop` | design | requirements approved, design awaiting approval |
| `finding-feedback-loop` | dormant | requirements approved, design unapproved since 2026-09-09; depends on `contributor-scan` |
| `model-grounded-detection` | complete | 16 of 16; built the arbiter this all rests on |
| `authorization-obligations` | dormant | tasks approved, 0 of 24 started; superseded in practice by the dominance arbiter |
| `reachability-flow-model` | dormant | 23 tasks generated, nothing approved; largely delivered by adopting Joern |
@@ -214,6 +214,5 @@ Counted 2026-10-02 from each spec's `tasks.md` checkboxes and `spec.json`.
> **Retired 2026-09-30:** HarnessX was removed by `harnessx-removal`; the agentic plane is ax (`ai-service-plane`),
> and evolutionary proposals come from the plane memory. The HarnessX specs in the table above are history.
As of 2026-09-09: 97 source modules, 90 load-bearing and 7 standalone capabilities; 814 tests.
151 source modules, 146 load-bearing and 5 standalone capabilities, 0 orphaned
(`benchmarks/measurements/2026-09-08-module-audit.json`, regenerated 2026-10-02); 2,009 tests collected.
(`benchmarks/measurements/2026-09-08-module-audit.json`, regenerated 2026-10-02); 2,023 tests collected.
+2 -4
View File
@@ -126,8 +126,7 @@ Keys and store settings live in `.env` in the working directory (gitignored, nev
```bash
DEEPSEEK_API_KEY=... # every LLM call: model layer, tool hunter, plane tasks, decision engine
OPENROUTER_API_KEY=... # embeddings only
OPENROUTER_EMBEDDING_MODEL=openai/text-embedding-3-small
OPENROUTER_API_KEY=... # embeddings only (`ousast learn memory embed`)
# Plane memory store (maintainers). Unset: a local file store under ~/ousast-results/plane/memory.
OUSAST_MEMORY=s3://<bucket> # or s3://<bucket>/<prefix>; needs the s3 extra
@@ -278,6 +277,5 @@ uv run python dagger/ci.py # containerized CI pipeline
```
Maintainer commands not covered above: `ousast repos` (pinned known-vulnerable checkouts),
`ousast index` (chunk a repository for embedding-index construction), `ousast model candidates`
(what the candidate enumerator can reach) and `ousast learn` (the decision engine's data; see
`ousast model candidates` (what the candidate enumerator can reach) and `ousast learn` (the decision engine's data; see
[docs/decision-engine.md](docs/decision-engine.md)).
+9 -2
View File
@@ -8,8 +8,15 @@
within-pair 0.72-0.93, BLOCK offered nowhere, canary below 0.9 for three families) and that the engine is not
adopted. The model layer's residual question is documented at `model/pipeline.py` (`model/judge.py` was
deleted); a missing provider key is described as it behaves (no question asked, entailed findings reported),
not as a recorded degradation. The `.env` reference lists `OPENROUTER_EMBEDDING_MODEL` and the memory-store
variables; `ousast plane memory-normalise` and `ousast index` are listed.
not as a recorded degradation. The `.env` reference lists the memory-store variables; `ousast plane memory-normalise` is listed.
- **Removed: the unused vector index, `ousast index` and `[embeddings]`** (legacy cleanup, 2026-10-02). `index.py`
keeps only the text chunker the skill router uses; the `VectorIndex` half had no caller, and nothing read the
`chunks.json` the subcommand wrote. `[embeddings] model`/`store` fail to load with the reason, like the other
retired keys; `OPENROUTER_EMBEDDING_MODEL` is no longer read (`ousast learn memory embed --model` names the model).
- **Retired three config keys nothing read** (2026-10-02): `[sandbox] network`, `[sandbox] workspace_readonly` and
`[complexity] top_k` now fail to load naming why. The sandbox always ran with `--network none` and a read-only
workspace regardless of the first two; no stage read `top_k`. `memory_mb`, `timeout_seconds`, `pids_limit` and
`max_hunter_hotspots` are read and stay.
# v2.0.0 (2026-10-02)
+1 -1
View File
@@ -16,7 +16,7 @@ maps each old path to the new one. The records themselves are not rewritten.
| `model_sinks.py` | Stage-1 source-only model classification of sinks. Moved into the package as `plane/tasks/roles.py`. | `independent/selection-v2-model-sinks.json`, `measurements/2026-10-01-decision-engine-harvest/record.json`, both `2026-10-01-decision-engine-leak-audit` records | `05d5699` (named in `selection-v2-model-sinks.json`) |
| `sink_candidates.py` | Diagnostic: vocabulary-declared sink candidates per case. Its declaration patterns live on in `plane/tasks/repo_facts.py`. | `independent/results-v2-model-pipeline.json` ("vocabulary sink-first: 2/17") | no commit recorded |
| `hunt.py` | The v2 tool-hunter run over the population (protocol `hunter-protocol-v2.md`, including its `FAMILY_WORDS` table). Replaced by the plane's `verify` / `agree` tasks. | `independent/hunter-protocol-v2.md`, `independent/results-v2-hunter.json` (a label source in `learn/sources.toml`) | `6bfb05f`, the commit that introduced the protocol, which pins the runner |
| `harnessx_removal_equality.py` | One-off equality proof for `harnessx-removal` (record and compare deterministic outputs). The spec is closed. `tests/test_harnessx_removal_equality.py` still checks that the comparison detects differences. | `measurements/2026-09-30-harnessx-removal-baseline/`, `measurements/2026-09-30-harnessx-removal-equality/` | baseline `ca5caf7`, candidate `b9afb66` |
| `harnessx_removal_equality.py` | One-off equality proof for `harnessx-removal` (record and compare deterministic outputs). The spec is closed; its unit test was dropped on 2026-10-02 so the archived copy is reference only like the rest. | `measurements/2026-09-30-harnessx-removal-baseline/`, `measurements/2026-09-30-harnessx-removal-equality/` | baseline `ca5caf7`, candidate `b9afb66` |
The six `independent` scripts still read the population, `evaluate.py` and the results from
`benchmarks/independent/`. Their `HERE` points there, so outputs land where they always did.
@@ -338,7 +338,6 @@
"classification": "load_bearing",
"reason": "reachable from gate, map_gate, pair_gate, cli",
"importers": [
"cli",
"skills"
]
},
@@ -530,6 +529,7 @@
"importers": [
"learn.compile",
"learn.evaluate",
"learn.examples",
"learn.features",
"learn.leaks",
"learn.program",
@@ -1119,7 +1119,6 @@
"hunter",
"hunter_tools",
"improve.evolve",
"index",
"learn.features",
"learn.labels",
"learn.roles",
+2 -1
View File
@@ -35,7 +35,8 @@ Each instrument contributes one block to a candidate's feature record (`learn/sc
`INSTRUMENTS`): `language`, `quick` (quick rules), `engine` (the Joern model layer), `facts` (repo
facts, such as callers and function span), `source`, `entry_points`, `roles` (model source, sink
and sanitizer roles), `delta`, `verify` and `agree` (the plane's verify passes and their
agreement), and `model_sinks`. A candidate is `(path, function, family)` of one repository and pin
agreement), and `model_sinks` (the archived stage-1 classifier, `benchmarks/archive/model_sinks.py`, whose
logic now lives in `plane/tasks/roles.py`). A candidate is `(path, function, family)` of one repository and pin
where at least one instrument emitted a signal of that family (`learn/features.py`).
An instrument that did not run, had no coverage or failed records that state (`none`, `failed`,
-33
View File
@@ -43,7 +43,6 @@ from .gate import FALSE_POSITIVE_CEILING, RECALL_FLOOR
from .harness import HarnessRuntime, HarnessTraceWriter, write_harness_config
from .hunter import run_hunter_pool, write_hunter_trajectories
from .improve import RoundOutcome, run_improvement
from .index import build_code_chunks
from .learn.cli import add_commands as add_learn_commands
from .learn.cli import run as run_learn
from .mapping import analyze_entry_points, attach_reachability_hints, ingest_sarif, write_entry_points, write_static_hints
@@ -173,11 +172,6 @@ def _main(argv: list[str] | None) -> int:
scan.add_argument("--config", type=Path, default=Path("openultrasast.toml"))
scan.add_argument("--fail-on", choices=("never", "findings", "verified", "worth-fixing"), default="never")
index = subparsers.add_parser("index", help="chunk a local repository for embedding index construction")
index.add_argument("path", type=Path)
index.add_argument("--config", type=Path, default=Path("openultrasast.toml"))
index.add_argument("--chunk-lines", type=int, default=80)
benchmark = subparsers.add_parser("benchmark", help="run a benchmark manifest and write scoreboard artifacts")
benchmark.add_argument("manifest", type=Path)
benchmark.add_argument("--mode", choices=("quick", "standard", "deep"), default="quick")
@@ -426,8 +420,6 @@ def _main(argv: list[str] | None) -> int:
return delivery.exit_code
if args.command == "scan":
return _scan(args.path, args.config, args.mode, args.fail_on)
if args.command == "index":
return _index(args.path, args.config, args.chunk_lines)
if args.command == "benchmark":
return _benchmark(args.manifest, args.config, args.mode)
if args.command == "improve":
@@ -1564,30 +1556,5 @@ def _model_candidates(args: argparse.Namespace) -> int:
return 0
def _index(path: Path, config_path: Path, chunk_lines: int) -> int:
if not path.exists() or not path.is_dir():
raise SystemExit(f"index path is not a directory: {path}")
config = load_config(config_path if config_path.exists() else None)
run = create_scan_run(path, config)
snapshot, targets = preprocess_repository(run.target, run.root / "preprocess" / "file_targets.json")
chunks = build_code_chunks(run.target, targets, max_lines=chunk_lines)
payload = {
"store": config.embeddings.store or "json-local",
"embedding_model": config.embeddings.model,
"repo_root": snapshot.root,
"repo_commit": snapshot.commit,
"chunk_count": len(chunks),
"chunks": [chunk.__dict__ for chunk in chunks],
}
path_out = run.root / "index" / "chunks.json"
path_out.parent.mkdir(parents=True, exist_ok=True)
path_out.write_text(json.dumps(payload, indent=2, sort_keys=True) + "\n")
print(f"scan_id={run.scan_id}")
print(f"run_dir={run.root}")
print(f"chunks={len(chunks)}")
print(f"index_artifact={path_out}")
return 0
if __name__ == "__main__":
raise SystemExit(main())
+15 -25
View File
@@ -10,8 +10,6 @@ from typing import Literal
from openultrasast.contracts import Contract
DEFAULT_VECTOR_STORE = "json-local"
# Retirement notes (harnessx-removal Req 1.3, 3.3). A key that asked for a retired LLM capability fails and names
# the replacement, never a silent downgrade; the old budget section only warns. Every line below that names the
# removed plane carries its retirement date, which the reference-search test holds the rest of the tree to.
@@ -26,6 +24,15 @@ _UNREAD_ON = "retired 2026-10-02 because nothing ever read it"
_UNREAD_DYNAMIC = "no dynamic or deep tier exists; deep mode will define its own keys when it is built"
_UNREAD_EVIDENCE = "evidence tiers come from the program model's ladder (model/ladder.py), not from configuration"
_UNREAD_VARIANTS = "the structural variant search it configured was deleted with semantic/variants.py"
_UNREAD_SANDBOX = (
"the sandbox always runs with `--network none` and a read-only workspace (sandbox/runner.py); that is an "
"invariant the runner enforces, not a knob"
)
_UNREAD_TOP_K = "no stage read it; `[complexity] max_hunter_hotspots` is the one complexity key the scan reads"
_UNREAD_EMBEDDINGS = (
"the json-local vector index it configured and `ousast index` were deleted; the decision engine embeds through "
"`ousast learn memory embed --model` (learn/embeddings.py)"
)
RETIRED_KEYS: dict[tuple[str, str], str] = {
("models", "verifier"): f"{_RETIRED_ON}: LLM verification runs on the plane (`verify` + `agree`, {_PLANE_RUN})",
("fusion", "panel_model"): f"{_RETIRED_ON}: {_RETIRED_PANELS}",
@@ -38,6 +45,11 @@ RETIRED_KEYS: dict[tuple[str, str], str] = {
("evidence", "minimum_patch"): f"{_UNREAD_ON}: {_UNREAD_EVIDENCE}",
("variants", "enabled"): f"{_UNREAD_ON}: {_UNREAD_VARIANTS}",
("variants", "max_mechanisms"): f"{_UNREAD_ON}: {_UNREAD_VARIANTS}",
("embeddings", "model"): f"{_UNREAD_ON}: {_UNREAD_EMBEDDINGS}",
("embeddings", "store"): f"{_UNREAD_ON}: {_UNREAD_EMBEDDINGS}",
("sandbox", "network"): f"{_UNREAD_ON}: {_UNREAD_SANDBOX}",
("sandbox", "workspace_readonly"): f"{_UNREAD_ON}: {_UNREAD_SANDBOX}",
("complexity", "top_k"): f"{_UNREAD_ON}: {_UNREAD_TOP_K}",
}
RETIRED_SECTION = "harnessx" # retired 2026-09-30: loads with one warning and is otherwise ignored (Req 3.3)
RETIRED_SECTION_WARNING = (
@@ -94,16 +106,8 @@ class ModelConfig:
thinking: bool = False
@dataclass(frozen=True)
class EmbeddingConfig:
model: str | None = None
store: str = DEFAULT_VECTOR_STORE
@dataclass(frozen=True)
class SandboxConfig:
network: bool = False
workspace_readonly: bool = True
memory_mb: int = 2048
timeout_seconds: int = 300
pids_limit: int = 512
@@ -148,8 +152,7 @@ class FusionConfig:
@dataclass(frozen=True)
class ComplexityConfig:
# Stage-2 map size and hunter attention. Small defaults keep PR/nightly map work cheap.
top_k: int = 20
# Hunter attention. A small default keeps PR/nightly map work cheap.
max_hunter_hotspots: int = 8
@@ -205,7 +208,6 @@ class PushConfig(Contract):
@dataclass(frozen=True)
class ResolvedConfig:
models: ModelConfig = ModelConfig()
embeddings: EmbeddingConfig = EmbeddingConfig()
sandbox: SandboxConfig = SandboxConfig()
static_analysis: StaticAnalysisConfig = StaticAnalysisConfig()
score: ScoreConfig = ScoreConfig()
@@ -231,7 +233,6 @@ def load_config(config_path: Path | None = None, *, dotenv: bool = True) -> Reso
return ResolvedConfig(
models=_load_models(data.get("models", {})),
embeddings=_load_embeddings(data.get("embeddings", {})),
sandbox=_load_sandbox(data.get("sandbox", {})),
static_analysis=_load_static_analysis(data.get("static_analysis", {})),
score=_load_score(data.get("score", {})),
@@ -277,19 +278,9 @@ def _load_models(value: object) -> ModelConfig:
)
def _load_embeddings(value: object) -> EmbeddingConfig:
data = _section(value)
return EmbeddingConfig(
model=_string(data.get("model")) or _string(os.environ.get("OPENROUTER_EMBEDDING_MODEL")),
store=_string(data.get("store")) or DEFAULT_VECTOR_STORE,
)
def _load_sandbox(value: object) -> SandboxConfig:
data = _section(value)
return SandboxConfig(
network=bool(data.get("network", False)),
workspace_readonly=bool(data.get("workspace_readonly", True)),
memory_mb=_int_value(data.get("memory_mb"), 2048),
timeout_seconds=_int_value(data.get("timeout_seconds"), 300),
pids_limit=_int_value(data.get("pids_limit"), 512),
@@ -349,7 +340,6 @@ def _load_fusion(value: object) -> FusionConfig:
def _load_complexity(value: object) -> ComplexityConfig:
data = _section(value)
return ComplexityConfig(
top_k=_int_value(data.get("top_k"), 20),
max_hunter_hotspots=_int_value(data.get("max_hunter_hotspots"), 8),
)
+8 -207
View File
@@ -1,13 +1,14 @@
"""Text chunking for the skill router (`skills.py`).
The vector index that once lived beside the chunker (`VectorIndex`, `build_vector_index`, `query_vector_index`,
`build_retrieval_package`) and the `ousast index` subcommand that fed it were removed on 2026-10-02: nothing ever
read the `chunks.json` they wrote. The decision engine's embeddings live in `learn/embeddings.py`.
"""
from __future__ import annotations
import hashlib
import json
import math
from dataclasses import asdict, dataclass
from pathlib import Path
from typing import Protocol
from .preprocess import FileTarget, RepoSnapshot
from dataclasses import dataclass
VALID_NAMESPACES = {"repo_code", "docs", "static_findings", "mechanisms", "skills", "traces"}
@@ -24,82 +25,6 @@ class CodeChunk:
metadata: dict[str, str | int | bool]
@dataclass(frozen=True)
class VectorRecord:
chunk: CodeChunk
embedding: list[float]
@dataclass(frozen=True)
class VectorIndex:
store: str
embedding_model: str
repo_root: str
repo_commit: str | None
records: list[VectorRecord]
@dataclass(frozen=True)
class RetrievalHit:
score: float
chunk: CodeChunk
@dataclass(frozen=True)
class RetrievalPackage:
role: str
hits: list[RetrievalHit]
text: str
truncated: bool
class EmbeddingClient(Protocol):
def embed(self, *, model: str, inputs: list[str], timeout_seconds: int = 60) -> list[list[float]]:
raise NotImplementedError
def chunk_file(root: Path, target: FileTarget, *, max_lines: int = 80) -> list[CodeChunk]:
if max_lines < 1:
raise ValueError("max_lines must be at least 1")
text = (root / target.path).read_text(errors="ignore")
lines = text.splitlines()
chunks: list[CodeChunk] = []
for start in range(0, len(lines), max_lines):
chunk_lines = lines[start : start + max_lines]
if not any(line.strip() for line in chunk_lines):
continue
start_line = start + 1
end_line = start + len(chunk_lines)
chunk_text = "\n".join(chunk_lines)
chunks.append(
CodeChunk(
chunk_id=_chunk_id(target.path, start_line, end_line, chunk_text),
namespace="repo_code",
path=target.path,
language=target.language,
start_line=start_line,
end_line=end_line,
text=chunk_text,
metadata={
"path": target.path,
"language": target.language,
"start_line": start_line,
"end_line": end_line,
"has_fuzz_entry_point": target.has_fuzz_entry_point,
"tags": ",".join(target.tags),
},
)
)
return chunks
def build_code_chunks(root: Path, targets: list[FileTarget], *, max_lines: int = 80) -> list[CodeChunk]:
chunks: list[CodeChunk] = []
for target in targets:
chunks.extend(chunk_file(root, target, max_lines=max_lines))
return chunks
def chunk_text_namespace(
*,
namespace: str,
@@ -144,130 +69,6 @@ def chunk_text_namespace(
return chunks
def index_reuse_key(snapshot: RepoSnapshot, embedding_model: str) -> str:
source = {
"root": snapshot.root,
"commit": snapshot.commit,
"file_count": snapshot.file_count,
"languages": snapshot.languages,
"embedding_model": embedding_model,
}
return hashlib.sha256(json.dumps(source, sort_keys=True).encode()).hexdigest()[:24]
def build_vector_index(
*,
snapshot: RepoSnapshot,
chunks: list[CodeChunk],
embedding_model: str,
client: EmbeddingClient,
store: str = "json-local",
batch_size: int = 32,
) -> VectorIndex:
if batch_size < 1:
raise ValueError("batch_size must be at least 1")
records: list[VectorRecord] = []
for start in range(0, len(chunks), batch_size):
batch = chunks[start : start + batch_size]
vectors = client.embed(model=embedding_model, inputs=[chunk.text for chunk in batch])
if len(vectors) != len(batch):
raise ValueError("embedding response count did not match chunk count")
records.extend(VectorRecord(chunk=chunk, embedding=vector) for chunk, vector in zip(batch, vectors, strict=True))
return VectorIndex(
store=store,
embedding_model=embedding_model,
repo_root=snapshot.root,
repo_commit=snapshot.commit,
records=records,
)
def write_vector_index(index: VectorIndex, path: Path) -> None:
path.parent.mkdir(parents=True, exist_ok=True)
path.write_text(json.dumps(_index_to_payload(index), indent=2, sort_keys=True) + "\n")
def read_vector_index(path: Path) -> VectorIndex:
payload = json.loads(path.read_text())
return VectorIndex(
store=str(payload["store"]),
embedding_model=str(payload["embedding_model"]),
repo_root=str(payload["repo_root"]),
repo_commit=payload.get("repo_commit"),
records=[
VectorRecord(
chunk=CodeChunk(**item["chunk"]),
embedding=[float(value) for value in item["embedding"]],
)
for item in payload.get("records", [])
],
)
def query_vector_index(
index: VectorIndex,
query_embedding: list[float],
*,
metadata_filter: dict[str, str | int | bool] | None = None,
limit: int = 5,
) -> list[tuple[float, CodeChunk]]:
matches: list[tuple[float, CodeChunk]] = []
for record in index.records:
if metadata_filter and not _metadata_matches(record.chunk.metadata, metadata_filter):
continue
matches.append((_cosine_similarity(query_embedding, record.embedding), record.chunk))
return sorted(matches, key=lambda item: item[0], reverse=True)[:limit]
def build_retrieval_package(
*,
role: str,
index: VectorIndex,
query_embedding: list[float],
metadata_filter: dict[str, str | int | bool] | None = None,
limit: int = 5,
max_chars: int = 4000,
) -> RetrievalPackage:
hits: list[RetrievalHit] = []
parts: list[str] = []
total = 0
truncated = False
for score, chunk in query_vector_index(index, query_embedding, metadata_filter=metadata_filter, limit=limit):
block = f"[{chunk.namespace}] {chunk.path}:{chunk.start_line}-{chunk.end_line}\n{chunk.text}"
if total + len(block) > max_chars:
truncated = True
break
hits.append(RetrievalHit(score=score, chunk=chunk))
parts.append(block)
total += len(block)
return RetrievalPackage(role=role, hits=hits, text="\n\n".join(parts), truncated=truncated)
def _index_to_payload(index: VectorIndex) -> dict[str, object]:
return {
"store": index.store,
"embedding_model": index.embedding_model,
"repo_root": index.repo_root,
"repo_commit": index.repo_commit,
"records": [{"chunk": asdict(record.chunk), "embedding": record.embedding} for record in index.records],
}
def _metadata_matches(metadata: dict[str, str | int | bool], expected: dict[str, str | int | bool]) -> bool:
return all(metadata.get(key) == value for key, value in expected.items())
def _cosine_similarity(left: list[float], right: list[float]) -> float:
if len(left) != len(right):
return 0.0
numerator = sum(a * b for a, b in zip(left, right, strict=True))
left_norm = math.sqrt(sum(a * a for a in left))
right_norm = math.sqrt(sum(b * b for b in right))
if left_norm == 0 or right_norm == 0:
return 0.0
return numerator / (left_norm * right_norm)
def _chunk_id(path: str, start_line: int, end_line: int, text: str) -> str:
digest = hashlib.sha256(f"{path}:{start_line}:{end_line}:{text}".encode()).hexdigest()[:16]
return f"{path}:{start_line}-{end_line}:{digest}"
+12 -21
View File
@@ -25,7 +25,6 @@ def test_load_config_reads_toml(tmp_path: Path) -> None:
config = load_config(config_path)
assert config.models.ranker == "openrouter/test-ranker"
assert config.embeddings.store == "json-local"
assert config.sandbox.memory_mb == 1024
@@ -74,6 +73,11 @@ def test_retired_llm_keys_fail_naming_the_replacement(tmp_path: Path, body: str,
('[evidence]\nminimum_patch = "root_cause_explained"\n', "`[evidence] minimum_patch`"),
("[variants]\nenabled = false\n", "`[variants] enabled`"),
("[variants]\nmax_mechanisms = 10\n", "`[variants] max_mechanisms`"),
('[embeddings]\nmodel = "openai/text-embedding-3-small"\n', "`[embeddings] model`"),
('[embeddings]\nstore = "json-local"\n', "`[embeddings] store`"),
("[sandbox]\nnetwork = true\n", "`[sandbox] network`"),
("[sandbox]\nworkspace_readonly = false\n", "`[sandbox] workspace_readonly`"),
("[complexity]\ntop_k = 3\n", "`[complexity] top_k`"),
],
)
def test_unread_keys_fail_naming_why(tmp_path: Path, body: str, key: str) -> None:
@@ -140,30 +144,27 @@ def test_write_resolved_config_creates_json(tmp_path: Path) -> None:
payload = json.loads(output.read_text())
assert payload["sandbox"]["memory_mb"] == 2048
# retired 2026-10-02 (never read): the unread sections are not echoed as if they configured something
assert not {"dynamic", "evidence", "variants"} & set(payload) and "patcher" not in payload["models"]
assert not {"dynamic", "evidence", "variants", "embeddings"} & set(payload) and "patcher" not in payload["models"]
def test_complexity_and_regress_defaults_when_section_absent() -> None:
config = load_config(None)
assert config.complexity.top_k == 20
assert config.complexity.max_hunter_hotspots == 8
assert config.regress.max_candidates == 5
assert config.regress.images == ()
assert config.sandbox.network is False
assert config.sandbox.workspace_readonly is True
assert config.sandbox.memory_mb == 2048
assert config.sandbox.timeout_seconds == 300
assert config.sandbox.pids_limit == 512
def test_override_changes_top_k_and_max_candidates_only(tmp_path: Path) -> None:
def test_override_changes_max_hunter_hotspots_and_max_candidates_only(tmp_path: Path) -> None:
config_path = tmp_path / "openultrasast.toml"
config_path.write_text(
"\n".join(
[
"[complexity]",
"top_k = 3",
"max_hunter_hotspots = 3",
"[regress]",
"max_candidates = 2",
]
@@ -172,12 +173,9 @@ def test_override_changes_top_k_and_max_candidates_only(tmp_path: Path) -> None:
config = load_config(config_path)
assert config.complexity.top_k == 3
assert config.complexity.max_hunter_hotspots == 3
assert config.regress.max_candidates == 2
assert config.complexity.max_hunter_hotspots == 8
assert config.regress.images == ()
assert config.sandbox.network is False
assert config.sandbox.workspace_readonly is True
assert config.sandbox.memory_mb == 2048
assert config.sandbox.timeout_seconds == 300
assert config.sandbox.pids_limit == 512
@@ -201,24 +199,17 @@ def test_load_config_reads_regress_image_pins(tmp_path: Path) -> None:
assert config.regress.max_candidates == 4
assert dict(config.regress.images) == {"python": "python:3.12-slim", "javascript": "node:22-slim"}
assert config.complexity.top_k == 20
assert config.complexity.max_hunter_hotspots == 8
assert config.sandbox.memory_mb == 2048
def test_load_dotenv_sets_missing_keys_only(tmp_path: Path, monkeypatch) -> None: # type: ignore[no-untyped-def]
env_file = tmp_path / ".env"
env_file.write_text("OPENROUTER_API_KEY=from-file\nOPENROUTER_EMBEDDING_MODEL=openai/text-embedding-3-small\n")
env_file.write_text("OPENROUTER_API_KEY=from-file\nS3_REGION=us-east-1\n")
monkeypatch.setenv("OPENROUTER_API_KEY", "already-set")
monkeypatch.delenv("OPENROUTER_EMBEDDING_MODEL", raising=False)
monkeypatch.delenv("S3_REGION", raising=False)
load_dotenv(env_file, force=True)
assert os.environ["OPENROUTER_API_KEY"] == "already-set"
assert os.environ["OPENROUTER_EMBEDDING_MODEL"] == "openai/text-embedding-3-small"
def test_embeddings_model_from_env(monkeypatch) -> None: # type: ignore[no-untyped-def]
monkeypatch.setenv("OPENROUTER_EMBEDDING_MODEL", "openai/text-embedding-3-small")
config = load_config(None)
assert config.embeddings.model == "openai/text-embedding-3-small"
assert os.environ["S3_REGION"] == "us-east-1"
-65
View File
@@ -1,65 +0,0 @@
"""The equality instrument (harnessx-removal design section 3) sees a difference when there is one.
A comparison that always says "equal" would prove nothing, so these tests feed it records that differ in one
place each and require it to name that place, and records that differ only in excluded fields and require it
to pass.
"""
from __future__ import annotations
import importlib.util
import json
from pathlib import Path
ROOT = Path(__file__).resolve().parents[1]
_SPEC = importlib.util.spec_from_file_location("hxr_equality", ROOT / "benchmarks" / "archive" / "harnessx_removal_equality.py")
assert _SPEC is not None and _SPEC.loader is not None
equality = importlib.util.module_from_spec(_SPEC)
_SPEC.loader.exec_module(equality)
def _record(path: Path, *, findings: object, suite: dict[str, str], stdout: str = "findings=3\n") -> Path:
(path / "scan").mkdir(parents=True)
(path / "suite.json").write_text(equality.dump(suite))
(path / "scan" / "findings.json").write_text(equality.dump(findings))
(path / "scan" / "stdout.txt").write_text(stdout)
(path / "env.json").write_text(json.dumps({"tree_ish": str(path)}))
return path
def _normalized(raw: dict[str, object], root: str) -> object:
return equality.normalize(raw, root, "/tmp/x")
def test_excluded_fields_and_run_ids_do_not_count(tmp_path: Path) -> None:
a = _normalized({"rule": "sqli", "runtime_seconds": 1.2, "path": "/r1/runs/20260930T101010Z-0123abcd/f.py", "t_ms": 4}, "/r1")
b = _normalized({"rule": "sqli", "runtime_seconds": 9.9, "path": "/r2/runs/20261001T111111Z-ffffffff/f.py", "t_ms": 7}, "/r2")
suite = {"tests.test_x::test_a": "passed"}
diffs = equality.compare(_record(tmp_path / "a", findings=a, suite=suite), _record(tmp_path / "b", findings=b, suite=suite), {})
assert diffs == []
def test_a_changed_finding_is_reported_with_its_key(tmp_path: Path) -> None:
suite = {"tests.test_x::test_a": "passed"}
a = _record(tmp_path / "a", findings=[{"rule": "sqli", "line": 3}], suite=suite)
b = _record(tmp_path / "b", findings=[{"rule": "sqli", "line": 4}], suite=suite)
assert equality.compare(a, b, {}) == ["scan/findings.json: $[0].line: 3 -> 4"]
assert equality.main(["compare", str(a), str(b)]) == 1
def test_a_changed_outcome_or_unmapped_test_is_reported(tmp_path: Path) -> None:
a = _record(tmp_path / "a", findings=[], suite={"t::a": "passed", "t::gone": "passed", "t::old": "skipped"})
b = _record(tmp_path / "b", findings=[], suite={"t::a": "failure", "t::new": "skipped", "t::extra": "passed"}, stdout="findings=2\n")
diffs = equality.compare(a, b, {"deleted": ["t::gone"], "renamed": {"t::old": "t::new"}})
assert diffs == [
"suite.json: t::a passed -> failure",
"suite.json: t::extra new in candidate",
"scan/stdout.txt: first differing line 1: 'findings=3' -> 'findings=2'",
]
def test_a_file_present_on_one_side_only_is_reported(tmp_path: Path) -> None:
a = _record(tmp_path / "a", findings=[], suite={})
b = _record(tmp_path / "b", findings=[], suite={})
(b / "scan" / "fusion.json").write_text("[]\n")
assert equality.compare(a, b, {}) == ["scan/fusion.json: present only in candidate"]
+14 -83
View File
@@ -1,80 +1,9 @@
import json
from pathlib import Path
import pytest
from openultrasast.index import (
CodeChunk,
build_code_chunks,
build_retrieval_package,
build_vector_index,
chunk_text_namespace,
index_reuse_key,
query_vector_index,
read_vector_index,
write_vector_index,
)
from openultrasast.preprocess import RepoSnapshot, preprocess_repository
from openultrasast.index import chunk_text_namespace
class FakeEmbeddingClient:
def embed(self, *, model: str, inputs: list[str], timeout_seconds: int = 60) -> list[list[float]]:
return [[float(len(text)), 1.0] for text in inputs]
def test_build_code_chunks_preserves_metadata_filters(tmp_path: Path) -> None:
repo = tmp_path / "repo"
repo.mkdir()
(repo / "app.py").write_text("def login(user):\n return user\n")
_, targets = preprocess_repository(repo)
chunks = build_code_chunks(repo, targets, max_lines=1)
assert len(chunks) == 2
assert chunks[0].metadata["path"] == "app.py"
assert chunks[0].metadata["language"] == "python"
assert chunks[0].start_line == 1
def test_json_vector_index_round_trips_and_filters(tmp_path: Path) -> None:
repo = tmp_path / "repo"
repo.mkdir()
(repo / "app.py").write_text("def login(user):\n return user\n")
snapshot, targets = preprocess_repository(repo)
chunks = build_code_chunks(repo, targets)
index = build_vector_index(snapshot=snapshot, chunks=chunks, embedding_model="openrouter/embed", client=FakeEmbeddingClient())
output = tmp_path / "index.json"
write_vector_index(index, output)
loaded = read_vector_index(output)
matches = query_vector_index(loaded, [20.0, 1.0], metadata_filter={"language": "python"})
assert loaded.embedding_model == "openrouter/embed"
assert matches[0][1].path == "app.py"
assert json.loads(output.read_text())["store"] == "json-local"
def test_vector_index_rejects_embedding_count_mismatch() -> None:
class EmptyEmbeddingClient:
def embed(self, *, model: str, inputs: list[str], timeout_seconds: int = 60) -> list[list[float]]:
return []
chunk = CodeChunk(
chunk_id="chunk-1",
namespace="repo_code",
path="app.py",
language="python",
start_line=1,
end_line=1,
text="print('x')",
metadata={"path": "app.py", "language": "python"},
)
snapshot = RepoSnapshot(root="/repo", commit=None, file_count=0, languages={})
with pytest.raises(ValueError):
build_vector_index(snapshot=snapshot, chunks=[chunk], embedding_model="model", client=EmptyEmbeddingClient())
def test_namespace_chunks_and_reuse_key_are_metadata_scoped() -> None:
def test_namespace_chunks_are_metadata_scoped() -> None:
chunks = chunk_text_namespace(
namespace="skills",
path="semgrep.md",
@@ -82,21 +11,23 @@ def test_namespace_chunks_and_reuse_key_are_metadata_scoped() -> None:
metadata={"language": "markdown", "vulnerability_class": "mapping"},
max_lines=1,
)
snapshot = RepoSnapshot(root="/repo", commit="abc", file_count=1, languages={"python": 1})
assert len(chunks) == 2
assert chunks[0].namespace == "skills"
assert chunks[0].language == "markdown"
assert chunks[0].metadata["vulnerability_class"] == "mapping"
assert index_reuse_key(snapshot, "embed-a") != index_reuse_key(snapshot, "embed-b")
assert (chunks[0].start_line, chunks[0].end_line) == (1, 1)
assert chunks[0].chunk_id != chunks[1].chunk_id
def test_retrieval_package_is_bounded_by_character_budget() -> None:
chunks = chunk_text_namespace(namespace="docs", path="README.md", text="alpha\n" + "beta" * 100, max_lines=1)
snapshot = RepoSnapshot(root="/repo", commit=None, file_count=1, languages={"markdown": 1})
index = build_vector_index(snapshot=snapshot, chunks=chunks, embedding_model="model", client=FakeEmbeddingClient())
def test_blank_blocks_are_skipped_and_the_language_defaults_to_text() -> None:
chunks = chunk_text_namespace(namespace="docs", path="README.md", text="alpha\n\n\nbeta\n", max_lines=1)
package = build_retrieval_package(role="verifier", index=index, query_embedding=[4.0, 1.0], max_chars=80)
assert [chunk.text for chunk in chunks] == ["alpha", "beta"]
assert chunks[0].language == "text"
assert package.role == "verifier"
assert package.hits
assert package.truncated is True
@pytest.mark.parametrize(("namespace", "max_lines"), [("unknown", 80), ("docs", 0)])
def test_invalid_namespace_or_window_is_rejected(namespace: str, max_lines: int) -> None:
with pytest.raises(ValueError):
chunk_text_namespace(namespace=namespace, path="x", text="y", max_lines=max_lines)