From e1db7e1920c72893782acf01860b4e742b4f724e Mon Sep 17 00:00:00 2001 From: Marius Ciepluch <11855163+norandom@users.noreply.github.com> Date: Fri, 2 Oct 2026 19:32:46 +0200 Subject: [PATCH 1/4] chore(index): remove the unused vector index, the ousast index subcommand and the [embeddings] section Legacy inventory 2, item R1. The vector half of index.py (VectorIndex, VectorRecord, RetrievalHit, RetrievalPackage, the EmbeddingClient protocol, index_reuse_key, build/write/read/query_vector_index, build_retrieval_package and their helpers) had no importer in src; `ousast index` wrote index/chunks.json that nothing read, and was the only reader of `[embeddings]`. index.py keeps CodeChunk and chunk_text_namespace, which skills.py imports; chunk_file/build_code_chunks go with their only caller. `[embeddings] model` and `[embeddings] store` join RETIRED_KEYS (RetiredConfigError naming the reason), the same way the [variants] keys were retired; OPENROUTER_EMBEDDING_MODEL is no longer read, so the README and .env.example drop it (`ousast learn memory embed --model` names the model). test_index.py now tests the chunker only; test_config.py covers the two retired keys and no longer asserts the removed field. The module audit record is regenerated (counts unchanged: 151 modules, 0 orphaned; the index module's importer list shrinks to skills). RELEASE_NOTES Unreleased carries the line. Co-Authored-By: Claude Fable 5.1 Claude-Session: https://claude.ai/code/session_01EmM1j46qgKMh3zqD1QMwEo --- .env.example | 3 +- README.md | 6 +- RELEASE_NOTES.md | 7 +- .../measurements/2026-09-08-module-audit.json | 3 +- src/openultrasast/cli.py | 33 --- src/openultrasast/config.py | 24 +- src/openultrasast/index.py | 215 +----------------- tests/test_config.py | 17 +- tests/test_index.py | 97 ++------ 9 files changed, 43 insertions(+), 362 deletions(-) diff --git a/.env.example b/.env.example index 118e634..6274c0e 100644 --- a/.env.example +++ b/.env.example @@ -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://[/], or diff --git a/README.md b/README.md index 19d8d9c..3d6c74a 100644 --- a/README.md +++ b/README.md @@ -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:// # or s3:///; needs the s3 extra @@ -276,6 +275,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)). diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md index 338ef76..5a47886 100644 --- a/RELEASE_NOTES.md +++ b/RELEASE_NOTES.md @@ -8,8 +8,11 @@ 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). # v2.0.0 (2026-10-02) diff --git a/benchmarks/measurements/2026-09-08-module-audit.json b/benchmarks/measurements/2026-09-08-module-audit.json index 56cfd90..8ce6a1d 100644 --- a/benchmarks/measurements/2026-09-08-module-audit.json +++ b/benchmarks/measurements/2026-09-08-module-audit.json @@ -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", diff --git a/src/openultrasast/cli.py b/src/openultrasast/cli.py index bdf70bc..294b9db 100644 --- a/src/openultrasast/cli.py +++ b/src/openultrasast/cli.py @@ -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()) diff --git a/src/openultrasast/config.py b/src/openultrasast/config.py index db06bde..0aa6bec 100644 --- a/src/openultrasast/config.py +++ b/src/openultrasast/config.py @@ -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,10 @@ _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_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 +40,8 @@ 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}", } RETIRED_SECTION = "harnessx" # retired 2026-09-30: loads with one warning and is otherwise ignored (Req 3.3) RETIRED_SECTION_WARNING = ( @@ -94,12 +98,6 @@ 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 @@ -205,7 +203,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 +228,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,14 +273,6 @@ 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( diff --git a/src/openultrasast/index.py b/src/openultrasast/index.py index 3e179a1..01eb221 100644 --- a/src/openultrasast/index.py +++ b/src/openultrasast/index.py @@ -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}" diff --git a/tests/test_config.py b/tests/test_config.py index 0e7b8e1..e23bdf8 100644 --- a/tests/test_config.py +++ b/tests/test_config.py @@ -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,8 @@ 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`"), ], ) def test_unread_keys_fail_naming_why(tmp_path: Path, body: str, key: str) -> None: @@ -140,7 +141,7 @@ 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: @@ -208,17 +209,11 @@ def test_load_config_reads_regress_image_pins(tmp_path: Path) -> None: 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" diff --git a/tests/test_index.py b/tests/test_index.py index 6cbc0ab..a2179ee 100644 --- a/tests/test_index.py +++ b/tests/test_index.py @@ -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) From adb70b6b982f02ca5e423c04b246ee331d38a1d6 Mon Sep 17 00:00:00 2001 From: Marius Ciepluch <11855163+norandom@users.noreply.github.com> Date: Fri, 2 Oct 2026 19:39:27 +0200 Subject: [PATCH 2/4] chore(tests): drop the HarnessX equality test that guards an archived one-off Legacy inventory 2, item R2. tests/test_harnessx_removal_equality.py loaded benchmarks/archive/harnessx_removal_equality.py by path and checked that its comparison sees a difference. The spec is implementation-complete and the proof is the committed pair of records (2026-09-30-harnessx-removal-{baseline,equality}); the archive README says these scripts are reference only and a record is re-run at the commit it names, not with today's package. A unit test that keeps the archived copy importable contradicted that and would break the next time cli or learn.cli move. The archive README row now says the test was dropped. test_removed_plane_references.py (the Req 2.4 name search) is a different test and stays. Co-Authored-By: Claude Fable 5.1 Claude-Session: https://claude.ai/code/session_01EmM1j46qgKMh3zqD1QMwEo --- benchmarks/archive/README.md | 2 +- tests/test_harnessx_removal_equality.py | 65 ------------------------- 2 files changed, 1 insertion(+), 66 deletions(-) delete mode 100644 tests/test_harnessx_removal_equality.py diff --git a/benchmarks/archive/README.md b/benchmarks/archive/README.md index 357341f..2b72129 100644 --- a/benchmarks/archive/README.md +++ b/benchmarks/archive/README.md @@ -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. diff --git a/tests/test_harnessx_removal_equality.py b/tests/test_harnessx_removal_equality.py deleted file mode 100644 index 55f63bd..0000000 --- a/tests/test_harnessx_removal_equality.py +++ /dev/null @@ -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"] From 6db5a35f072431fc46381d26870193f777ab2c17 Mon Sep 17 00:00:00 2001 From: Marius Ciepluch <11855163+norandom@users.noreply.github.com> Date: Fri, 2 Oct 2026 19:45:37 +0200 Subject: [PATCH 3/4] chore(config): retire unread keys [sandbox] network, [sandbox] workspace_readonly, [complexity] top_k Legacy inventory 2, item R3. The three keys loaded into ResolvedConfig and nothing read them: no `.network`, `.workspace_readonly` or `.top_k` access exists in src outside config.py. The sandbox runner always passes `--network none` and `--read-only` and rejects host networking by argv inspection, so the two sandbox keys were knobs that turned nothing; no stage read top_k, and `[complexity] max_hunter_hotspots` is the one complexity key the scan reads. They join RETIRED_KEYS with the same 2026-10-02 mechanism as the other unread keys (RetiredConfigError naming the reason). The fields and their loaders are removed; memory_mb, timeout_seconds, pids_limit (read by regress/recipes.py and sandbox/runner.py) and max_hunter_hotspots stay. test_config.py covers the three retirements and the override test now exercises max_hunter_hotspots. RELEASE_NOTES Unreleased carries the line. Co-Authored-By: Claude Fable 5.1 Claude-Session: https://claude.ai/code/session_01EmM1j46qgKMh3zqD1QMwEo --- RELEASE_NOTES.md | 4 ++++ src/openultrasast/config.py | 16 +++++++++------- tests/test_config.py | 16 ++++++---------- 3 files changed, 19 insertions(+), 17 deletions(-) diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md index 5a47886..746de2d 100644 --- a/RELEASE_NOTES.md +++ b/RELEASE_NOTES.md @@ -13,6 +13,10 @@ 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) diff --git a/src/openultrasast/config.py b/src/openultrasast/config.py index 0aa6bec..6e0d038 100644 --- a/src/openultrasast/config.py +++ b/src/openultrasast/config.py @@ -24,6 +24,11 @@ _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)" @@ -42,6 +47,9 @@ RETIRED_KEYS: dict[tuple[str, str], str] = { ("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 = ( @@ -100,8 +108,6 @@ class ModelConfig: @dataclass(frozen=True) class SandboxConfig: - network: bool = False - workspace_readonly: bool = True memory_mb: int = 2048 timeout_seconds: int = 300 pids_limit: int = 512 @@ -146,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 @@ -276,8 +281,6 @@ def _load_models(value: object) -> ModelConfig: 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), @@ -337,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), ) diff --git a/tests/test_config.py b/tests/test_config.py index e23bdf8..3dfacb2 100644 --- a/tests/test_config.py +++ b/tests/test_config.py @@ -75,6 +75,9 @@ def test_retired_llm_keys_fail_naming_the_replacement(tmp_path: Path, body: str, ("[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: @@ -147,24 +150,21 @@ def test_write_resolved_config_creates_json(tmp_path: Path) -> None: 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", ] @@ -173,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 @@ -202,7 +199,6 @@ 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 From 61ff5f3c3304ead220bc6044bc7a38b5bd8e9c8b Mon Sep 17 00:00:00 2001 From: Marius Ciepluch <11855163+norandom@users.noreply.github.com> Date: Fri, 2 Oct 2026 19:57:04 +0200 Subject: [PATCH 4/4] docs(kiro): status notes -- ai-service-plane complete, contributor-scan paused, finding-feedback-loop design unapproved; stale lines Legacy inventory 2, the six STATUS NOTE items (the archive README row landed with the R2 commit). ai-service-plane: 11 of 11 tasks checked, spec.json phase set to implementation-complete as 5b96199 did for four other specs. contributor-scan: dated note in requirements.md -- paused since 2026-09-23, 27 of 51 tasks, v1.2.0-alpha.1 NO-GO, remaining tasks wait on pre-push M2/M3. finding-feedback-loop: dated note -- dormant, design unapproved since 2026-09-09, depends on the paused spec. The steering overview's spec table carries the same three states, its leftover 2026-09-09 module-count line is removed and the collected-test count is the measured 2,023. docs/memory-and-detection.md names the archive path of model_sinks, the one stale script name left in docs after 067adb9. Co-Authored-By: Claude Fable 5.1 Claude-Session: https://claude.ai/code/session_01EmM1j46qgKMh3zqD1QMwEo --- .kiro/specs/ai-service-plane/spec.json | 4 ++-- .kiro/specs/contributor-scan/requirements.md | 2 ++ .kiro/specs/finding-feedback-loop/requirements.md | 2 ++ .kiro/steering/overview.md | 9 ++++----- docs/memory-and-detection.md | 3 ++- 5 files changed, 12 insertions(+), 8 deletions(-) diff --git a/.kiro/specs/ai-service-plane/spec.json b/.kiro/specs/ai-service-plane/spec.json index affcf20..42aacdb 100644 --- a/.kiro/specs/ai-service-plane/spec.json +++ b/.kiro/specs/ai-service-plane/spec.json @@ -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, diff --git a/.kiro/specs/contributor-scan/requirements.md b/.kiro/specs/contributor-scan/requirements.md index 95132a6..0b56e0a 100644 --- a/.kiro/specs/contributor-scan/requirements.md +++ b/.kiro/specs/contributor-scan/requirements.md @@ -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` diff --git a/.kiro/specs/finding-feedback-loop/requirements.md b/.kiro/specs/finding-feedback-loop/requirements.md index 28bed51..14f1f54 100644 --- a/.kiro/specs/finding-feedback-loop/requirements.md +++ b/.kiro/specs/finding-feedback-loop/requirements.md @@ -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 diff --git a/.kiro/steering/overview.md b/.kiro/steering/overview.md index a430965..93230be 100644 --- a/.kiro/steering/overview.md +++ b/.kiro/steering/overview.md @@ -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. diff --git a/docs/memory-and-detection.md b/docs/memory-and-detection.md index edb2725..3c0a612 100644 --- a/docs/memory-and-detection.md +++ b/docs/memory-and-detection.md @@ -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`,