diff --git a/docs/README.md b/docs/README.md index a550368..0374cb7 100644 --- a/docs/README.md +++ b/docs/README.md @@ -62,6 +62,8 @@ follow-up ADRs. | Analysis artifact reference | `docs/reference/ARTIFACTS.md` | | Schema/version matrix | `docs/reference/SCHEMA-VERSIONS.md` | | Performance measurements | `docs/BENCHMARK.md` | +| As-built computational model (program equation, inputs, effects) | `docs/computational-model.md` | +| Design recovery: data structures, algorithms, invariants, state machines, matrix | `docs/design/` | | Data sources: GRNOC, RouteViews, RIPE RIS | `docs/sources/` | | Terminology (normative definitions) | `docs/GLOSSARY.md` | | Case studies | `case-studies/` (per-case README files) | diff --git a/docs/audits/2026-08-documentation-inventory.md b/docs/audits/2026-08-documentation-inventory.md index cae23d6..e372c90 100644 --- a/docs/audits/2026-08-documentation-inventory.md +++ b/docs/audits/2026-08-documentation-inventory.md @@ -215,7 +215,7 @@ The following lists are compared with `git ls-files` by must be classified in a table above (or in `repository-inventory.json` for non-documentation files). -### Tracked Markdown files (89, excluding `spec/`) +### Tracked Markdown files (96, excluding `spec/`) ``` .github/PULL_REQUEST_TEMPLATE.md @@ -244,9 +244,15 @@ docs/ADRs/MONOCLE-DATAPLANE.md docs/ADRs/README.md docs/ADRs/RIPE-RIS-SUPPORT.md docs/BENCHMARK.md +docs/computational-model.md docs/DATA_PROVENANCE.md docs/DECISIONS.md docs/DESIGN.md +docs/design/algorithms.md +docs/design/algorithm-data-matrix.md +docs/design/data-structures.md +docs/design/invariants.md +docs/design/state-machines.md docs/DOMAIN.md docs/GLOSSARY.md docs/MONOCLE_EVALUATION.md @@ -279,6 +285,7 @@ docs/audits/2026-08-repository-truth-audit.md docs/audits/2026-08-second-network-neutrality.md docs/audits/2026-08-smithville-source-refresh.md docs/audits/2026-08-specification-coverage.md +docs/audits/2026-08-wirthian-design-recovery.md docs/audits/README.md docs/audits/external-links-2026-08.md docs/evaluation/ALPHA-FREEZE.md @@ -315,7 +322,7 @@ tests/fixtures/README.md -### Tracked files under `docs/` (69) +### Tracked files under `docs/` (76) ``` docs/ADRs/CASE-STUDY-LAYER.md @@ -325,9 +332,15 @@ docs/ADRs/MONOCLE-DATAPLANE.md docs/ADRs/README.md docs/ADRs/RIPE-RIS-SUPPORT.md docs/BENCHMARK.md +docs/computational-model.md docs/DATA_PROVENANCE.md docs/DECISIONS.md docs/DESIGN.md +docs/design/algorithms.md +docs/design/algorithm-data-matrix.md +docs/design/data-structures.md +docs/design/invariants.md +docs/design/state-machines.md docs/DOMAIN.md docs/GLOSSARY.md docs/MONOCLE_EVALUATION.md @@ -360,6 +373,7 @@ docs/audits/2026-08-repository-truth-audit.md docs/audits/2026-08-second-network-neutrality.md docs/audits/2026-08-smithville-source-refresh.md docs/audits/2026-08-specification-coverage.md +docs/audits/2026-08-wirthian-design-recovery.md docs/audits/README.md docs/audits/external-links-2026-08.md docs/audits/repository-inventory.json diff --git a/docs/audits/2026-08-repository-truth-audit.md b/docs/audits/2026-08-repository-truth-audit.md index 8d7fe51..50c7fce 100644 --- a/docs/audits/2026-08-repository-truth-audit.md +++ b/docs/audits/2026-08-repository-truth-audit.md @@ -24,14 +24,14 @@ This audit verifies that every tracked file is classified, that every current st ## Summary -Tracked files: **463** · inventory entries: **463** +Tracked files: **470** · inventory entries: **470** | Category | Files | |---|---| | Immutable or generated evidence | 148 | | Production source | 106 | -| Normative current documentation | 42 | -| Historical decision record | 41 | +| Normative current documentation | 48 | +| Historical decision record | 42 | | Reviewed case-study interpretation | 30 | | Script or developer tool | 25 | | Configuration | 24 | @@ -293,9 +293,16 @@ Tracked files: **463** · inventory entries: **463** | `docs/audits/2026-08-second-network-neutrality.md` | Historical decision record | maintainers | dated audit evidence | no | historical | status and applicability reviewed in this audit | none | reviewed in this audit | | `docs/audits/2026-08-smithville-source-refresh.md` | Historical decision record | maintainers | dated audit evidence | no | historical | status and applicability reviewed in this audit | none | reviewed in this audit | | `docs/audits/2026-08-specification-coverage.md` | Historical decision record | maintainers | dated audit | no | historical | status and applicability reviewed in this audit | none | reviewed in this audit | +| `docs/audits/2026-08-wirthian-design-recovery.md` | Historical decision record | maintainers | dated audit | no | historical | status and applicability reviewed in this audit | none | reviewed in this audit | | `docs/audits/README.md` | Historical decision record | maintainers | dated audit index | no | historical | status and applicability reviewed in this audit | none | reviewed in this audit | | `docs/audits/external-links-2026-08.md` | Normative current documentation | maintainers | dated curl checks of documented external URLs | no | current | line-by-line reviewed in this audit | none | reviewed in this audit | | `docs/audits/repository-inventory.json` | Configuration | maintainers | reviewed classification of tracked files | no | current | reviewed in this audit | none | reviewed in this audit | +| `docs/computational-model.md` | Normative current documentation | contributors and maintainers | implementation + canonical evidence (see docs/README authority map) | no | current | line-by-line reviewed in this audit | none | reviewed in this audit | +| `docs/design/algorithm-data-matrix.md` | Normative current documentation | contributors and maintainers | implementation + canonical evidence (session 56 design recovery) | no | current | line-by-line reviewed in this audit | none | reviewed in this audit | +| `docs/design/algorithms.md` | Normative current documentation | contributors and maintainers | implementation + canonical evidence (session 56 design recovery) | no | current | line-by-line reviewed in this audit | none | reviewed in this audit | +| `docs/design/data-structures.md` | Normative current documentation | contributors and maintainers | implementation + canonical evidence (session 56 design recovery) | no | current | line-by-line reviewed in this audit | none | reviewed in this audit | +| `docs/design/invariants.md` | Normative current documentation | contributors and maintainers | implementation + canonical evidence (session 56 design recovery) | no | current | line-by-line reviewed in this audit | none | reviewed in this audit | +| `docs/design/state-machines.md` | Normative current documentation | contributors and maintainers | implementation + canonical evidence (session 56 design recovery) | no | current | line-by-line reviewed in this audit | none | reviewed in this audit | | `docs/evaluation/ALPHA-FREEZE.md` | Normative current documentation | contributors | reviewed protocol | no | current | line-by-line reviewed in this audit | none | reviewed in this audit | | `docs/evaluation/EVALUATION-DATA-HANDLING.md` | Normative current documentation | evaluators | reviewed protocol | no | current | line-by-line reviewed in this audit | none | reviewed in this audit | | `docs/evaluation/EXTERNAL-PILOT-CHECKLIST.md` | Normative current documentation | facilitators | reviewed protocol | no | current | line-by-line reviewed in this audit | none | reviewed in this audit | diff --git a/docs/audits/2026-08-wirthian-design-recovery.md b/docs/audits/2026-08-wirthian-design-recovery.md new file mode 100644 index 0000000..49fdd21 --- /dev/null +++ b/docs/audits/2026-08-wirthian-design-recovery.md @@ -0,0 +1,275 @@ +# Wirthian design-recovery audit (2026-08) + +Historical audit, not normative. Describes the as-built computational +model of inim recovered at a pinned commit. Current normative documents +are [../computational-model.md](../computational-model.md) and +[../design/](../design/). + +## Scope and method + +Design-recovery audit using the expanded Wirthian formulation +(Program = data structures + invariants + algorithms + state transitions ++ effects). Three explicitly separated passes: + +1. **Reconstruction** — candidate computational model built from code, + tests, schemas, tracked artifacts. +2. **Falsification** — independent skeptical pass seeking counterexamples + to every candidate invariant and authority claim. +3. **Synthesis** — only claims that survived, or are explicitly marked + INFERRED/UNKNOWN, entered the maintained docs. + +No implementation repair was performed in this session. + +## Repository state + +- Starting commit: `92f83d896faa3b4a205406ce618c7789a13c3789` (main, + clean; matches the expected starting HEAD `92f83d8`). +- Branch: `session-56-wirthian-design-recovery`. +- Ending documentation commit before merge: recorded at PR merge. +- Environment: linux/amd64, rustc 1.97.1, cargo 1.97.1, git 2.43.0. +- Actual test count at start: 1481 passing (`cargo test --locked`). +- Doc-test count: 0 (no doctests present). +- Open PRs at start: none (11 merged, PR #11 = Session 55). + +## Commands run + +- `git fetch --all --prune`, `git pull --ff-only origin main` +- `cargo build --locked`, `cargo test --locked` (1481 passed), + `cargo test --doc --locked` (0) +- `cargo package --locked --list` (463 files; 0 `data/`/`*.sqlite`) +- `scripts/audit-docs.sh` — **fails at the starting commit** (see + Findings F-8) +- `inim demo init` / `inim demo verify` (offline, deterministic) +- `inim serve` on loopback with GET/POST probes + DB hash before/after +- `inim project-scope show/audit`, `inim analysis-job audit` +- answer-key generation ×2 (`--db`) and drift comparison +- No live-source commands were run (no GRNOC, RouteViews, RIPE RIS, + PeeringDB, RIR contact). + +## Intermediate reports (untracked, outside the repository) + +- Reconstruction report: SHA-256 + `0588a82679482aa882c1808d440d6fa8d72561c63fffe920d3509b7103886142` +- Falsification report: SHA-256 + `dec7eeca1dadd77856fd991843301c4e63a0febd9d8ca6677c892ce00ef203d5` + +Both reports were frozen before the next pass; neither was committed. + +## Repository areas reviewed + +- Entry points and CLI dispatch (`src/main.rs`), web router + (`src/catalog/web/mod.rs`), worker (`src/worker.rs`), publication + (`src/catalog/jobs/*`), catalog (`src/catalog/`), domain + (`src/domain/`), ingestion (`src/ingest/`), orchestration + (`src/orchestrate.rs`), lifecycle (`src/lifecycle.rs`), cohort + (`src/cohort.rs`), waves/sequitur (`src/waves.rs`, `src/sequitur/`), + output/report (`src/output.rs`, `src/report.rs`), profiles/sources + (`src/profiles/`, `src/sources/`), scripts, evaluation material, + case-study data, migrations, Cargo packaging, CI workflow. + +## Structures recovered + +Central structures (34) and supporting structures (16) are cataloged in +[../design/data-structures.md](../design/data-structures.md). Central +examples: `RouteKey`/`ObserverPrefixKey`, `RouteObservation`, +`FrozenCohort`, `StreamLifecycle`, `AnalysisPlan`, `ArchivePlan`, +`AnalysisJob`, `Verdict`/`ObservedResultKind`/`ExpectationAssessmentKind`, +`AnalysisOutcome`, `RoutingFinding`, `ProjectScope`, SQLite tables +`event_snapshots`, `manifest_revisions`, `analysis_plans`, +`analysis_runs`, `analysis_artifacts`, `analysis_jobs`. + +## Algorithms recovered + +23 named algorithms (A1–A23) plus distributed/unnamed algorithms are +cataloged in [../design/algorithms.md](../design/algorithms.md). + +## State machines recovered + +8 state machines in [../design/state-machines.md](../design/state-machines.md): +source event lifecycle (derived), plan readiness, analysis job, worker +lease, run publication, observer-prefix lifecycle, provisional open-event +analysis, project-scope recheck. All states and legal transitions were +verified against implementation (`JobState` + +`legal_transition`, `CatalogStatus` precedence, worker lease timings). + +## Invariants evaluated + +57 invariants are registered in +[../design/invariants.md](../design/invariants.md): +33 enforced, 5 partially enforced, 1 assumed, 2 contradicted/suppressed, +0 claimed-only, 0 unknown. + +## Experiments performed (all offline) + +| # | Experiment | Result | +|---|------------|--------| +| E1 | `cargo build --locked` | success | +| E2 | `cargo test --locked` | 1481 passed | +| E3 | `demo init` (fresh temp catalog) | ok; 4 events, 12 runs, GRNOC corpus imported | +| E4 | `demo verify` | ok; no source access; no absolute paths | +| E5 | read-only server on loopback + GET/POST probes + DB hash | 15 GET routes 200; POST mutations 415; DB SHA-256 unchanged; `-wal`/`-shm` sidecars created then removed at shutdown | +| E6 | answer-key generation ×2 with `--db` | byte-identical; byte-identical to tracked `answer-key.json` | +| E7 | `project-scope show/audit` on demo catalog | 0 excluded plans/jobs/runs/artifacts | +| E8 | `analysis-job audit` on demo catalog | orphan final directories reported (runtime leftovers), not deleted | +| E9 | `cargo package --list` | 463 files; no `data/`/`*.sqlite`; reviewed evidence trees packaged | +| E10 | `scripts/audit-docs.sh` at clean HEAD | **FAILS**: pre-existing `CHANGELOG.md:3` "Session 55" narrative | + +## Claims contradicted + +1. **"Artifact listing and access share one resolver"** — contradicted. + At least four resolvers exist with different candidate sets/order: + `resolve_artifact` (`src/catalog/artifact_path.rs`), a hand-rolled + workbench resolver (`src/catalog/workbench.rs`, hardcoded four + case-study dirs), a demo fallback (`src/catalog/demo.rs`), and a + direct join in orphan reconciliation (`src/catalog/jobs/publish.rs`). + Same relative path can resolve differently per consumer. +2. **"Gap/unknown continuity always suppresses strong verdicts"** — + contradicted for the empty-transitions path: `derive_verdict` + returns `NoObservableBgpImpact` before the continuity gate + (`src/assess.rs:208-224`); with archive gaps and zero transitions + the verdict is "No route-state change observed", not + `InsufficientVisibility`. The module doc claims suppression + (`src/assess.rs:19-20`). The gaps + empty-transitions combination is + untested. + +## Claims narrowed + +3. **"Open runs always have cutoff provenance"** — a Ready-status plan + for an open event with no cutoff is storable via the import path + (`src/catalog/import.rs`); execution is blocked only at queue/worker + time. `analysis_end_utc` without reviewed provenance is representable + (meta sidecar optional; generic fallback sentence). +4. **"Artifact paths cannot escape root"** — `catalog_root.join(rel)` + without component validation in `src/catalog/workbench.rs` and the + demo fallback (`src/catalog/demo.rs`) permits escape for a + crafted/legacy row; `resolve_artifact` and document serving validate. +5. **"Project-scope exclusion is rechecked before source access"** — + enforced in the worker (after claim) and at queue/retry, but the + standalone `inim analyze` CLI and `orchestrate.rs` never load + `ProjectScope`. +6. **"Observed result cannot inherit expectation wording"** — clean + presentation labels, but `report.json` `result.verdict` carries + expectation vocabulary and the API exposes stored verdict strings + verbatim. +7. **"Read-only browsing leaves the database unchanged"** — logical rows + unchanged (E5), but the catalog opens read-write WAL so `-wal`/`-shm` + sidecars are created. +8. **"Source snapshots are fully immutable"** — snapshot rows are + immutable, but `catalog_events.last_seen` is updated by + `upsert_event`. + +## Unknowns + +- Whether the gaps + empty-transitions path occurs on a real completed + run (requires archive acquisition — not performed; the E2 test suite + does not cover the combination). +- Whether the source-extraction reuse caveat (predicate-2-only streams + missed when reusing an origin-keyed extraction) manifests at + production scale. + +## Discrepancies (intended vs as-built) + +| Area | As implemented | As claimed | Apparently intended | Classification | Evidence | Consequence | +|------|----------------|------------|---------------------|----------------|----------|-------------| +| Continuity gate | empty-transitions returns strong verdict before gate | module doc says strong verdicts suppressed on unknown continuity | gate should precede empty case | partially enforced invariant | `src/assess.rs:208-224` vs `:19-20` | run with gaps + no changes may overstate "no route-state change" | +| Artifact resolution | 4 resolvers | single resolver authority | one resolver | duplicated authority | `artifact_path.rs`, `workbench.rs`, `demo.rs`, `publish.rs` | same path can resolve differently per consumer | +| Scope enforcement | worker/queue/retry only | "rechecked before source access" | all execution paths | abstraction leak | `src/worker.rs`, `src/main.rs` | standalone analyze unguarded | +| Open-event cutoff | storable Ready plan without cutoff via import | manifest validation requires cutoff | one enforcement point | partially enforced invariant | `src/catalog/import.rs`, `src/manifest.rs` | catalog may show Ready for un-runnable plan | +| WAL sidecars | read-write WAL open | "read-only server" | open read-only | documentation drift | `src/catalog/db.rs`, `src/catalog/web/server.rs` | -wal/-shm files appear under read-only use | +| Changelog gate | "Session 55" narrative in CHANGELOG | audit expects no session narrative in normative docs | format change should not break gate | historical residue / documentation mismatch | `CHANGELOG.md:3`, `scripts/audit_docs.py` | CI red at starting commit (F-8) | +| Extracted verdict strings | stored verdicts exposed verbatim in API | human labels from `ObservedResultKind` | machine + human separation | documentation drift | `src/catalog/web/view.rs`, `src/output.rs` | consumers may see expectation vocabulary | + +## Design findings by severity + +- **P0**: none demonstrated. +- **P1**: + - F-1 Continuity-gate bypass (Claim 2): demonstrated in code, untested + combination; affects the observed result of a completed run. + Confidence: OBSERVED (code + module doc contradiction). Currently + reproduced: code-path only; not reproduced on a real run. + Smallest follow-up: a unit test for `assess(..., true, &[])` + + decision on gate ordering. +- **P2**: + - F-2 Duplicated artifact resolvers (Claim 1). Confidence: OBSERVED. + Smallest follow-up: route all consumers through `resolve_artifact` + or document divergence; add a resolver-equivalence test. + - F-3 Unvalidated `root.join(rel)` in workbench/demo (Claim 4). + Confidence: OBSERVED (code); exploit requires a crafted/legacy row. + Smallest follow-up: reuse `resolve_artifact` validation. + - F-4 Storable Ready plan for open event without cutoff (Claim 3). + Confidence: OBSERVED. Smallest follow-up: enforce at import or + classify such plans Blocked. + - F-5 Standalone analyze has no scope enforcement (Claim 5). + Confidence: OBSERVED. Smallest follow-up: load `ProjectScope` in + `cmd_analyze` or document the boundary. + - F-6 Expectation vocabulary in stored/API verdict strings (Claim 6). + Confidence: OBSERVED. Smallest follow-up: API projection via + `ObservedResultKind`/`ExpectationAssessmentKind`. +- **P3**: + - F-7 WAL sidecars under read-only serving (Claim 7). Confidence: + OBSERVED (experiment). + - F-8 Pre-existing red CI from the CHANGELOG session-narrative format + (E10). Confidence: OBSERVED (`gh run list` shows + `92f83d89 failure CI`; the "Session 55" heading was introduced only + by the HEAD commit). Not fixed in this session per scope rules. +- **Unknown**: F-9 extraction-reuse predicate caveat; F-10 real-run + occurrence of F-1. + +## Candidate ADR register + +| Title | Decision already implicit? | Current alternatives | Consequences | Evidence | Worth formalizing? | +|-------|---------------------------|----------------------|--------------|----------|---------------------| +| Immutable evidence vs reviewed interpretation | yes — snapshots/manifests append-only, reviews separate | mutable interpretation rows | provenance preserved | `event_snapshots`, `manifest_revisions` UNIQUE hashes | yes | +| SQLite + filesystem publication boundary | yes — staging → rename → import | DB-only artifacts | orphan windows, divergence detectable | `src/catalog/jobs/publish.rs` | yes | +| Observer-prefix stream as analysis unit | yes — `ObserverPrefixKey` aggregation | route-instance-only analysis | stream-level conclusions | `src/lifecycle.rs` | yes | +| ADD-PATH-aware route identity | yes — `RouteKey.path_id` | prefix-only identity | mixed keyed/unkeyed ambiguity flagged | `src/domain/route.rs` | yes | +| Plan/job/run separation | yes — three distinct tables | merged execution record | independent axes | `analysis_plans`, `analysis_jobs`, `analysis_runs` | yes | +| Project-scope overlay | yes — `config/project-scope.toml`, exact matching | access control | overlay, not authz | `src/catalog/scope.rs` | yes | +| Source profile vs source adapter | yes — `NetworkProfile` + `SourceFamily` | hardcoded per-source logic | family-correct behavior | `src/profiles/`, `src/catalog/archive_plan.rs` | yes | +| Zero-baseline stop before UPDATE acquisition | yes — insufficient-visibility artifact set | proceed with no cohort | no fabricated findings | `src/orchestrate.rs` | yes | +| Read-only web boundary | yes — POST-only mutations, loopback default | write-enabled mode with acknowledgement | safe evaluation browsing | `src/catalog/web/mod.rs`, `src/catalog/web/server.rs` | yes | +| Presentation diagrams as non-canonical projections | yes — SVG derived from reviewed/lifecycle data | embed diagrams in canonical artifacts | layout never claims semantics | `src/catalog/web/path_diagram.rs` | yes | + +## Suggested future experiments + +1. **Continuity-gate combination test** — unit test + `assess(unknown_continuity=true, transitions=[])`; expected + `InsufficientVisibility` per the module doc. Cost: minutes. +2. **Resolver-equivalence test** — property test over artifact rows: + every consumer resolves the same path. Cost: hours. +3. **Extraction-reuse predicate probe** — with a fixture, run analysis + for two predicates sharing an origin set and compare cohort + completeness. Cost: hours. +4. **Real-run F-1 probe** — acquire a run with known UPDATE gaps and no + transitions; check the report verdict. Cost: archive acquisition + (outside freeze). +5. **Read-only open mode** — serve the catalog via + `open_catalog_readonly` and verify no sidecars. Cost: small. + +## No-code-change statement + +No production Rust behavior, schema, migration, template, CSS, API, +CLI, adapter, predicate, lifecycle algorithm, finding, result, +assessment, project-scope policy, network profile, entity taxonomy, +case-study interpretation, canonical artifact, or source snapshot was +changed in this session. No analysis was rerun, no worker executed, no +source was contacted, no runtime file was committed, and no evidence was +regenerated. + +## Evaluation status + +External evaluation sessions: **zero**. Pilot registry unchanged +(no navigation edit required). + +## Maintained documents created + +- `../computational-model.md` +- `../design/data-structures.md` +- `../design/algorithms.md` +- `../design/invariants.md` +- `../design/state-machines.md` +- `../design/algorithm-data-matrix.md` +- this audit + +Navigation updated: `../README.md` (docs index), `./README.md` +(audits index). No other documentation was modified. diff --git a/docs/audits/README.md b/docs/audits/README.md index 32a4baf..1c07db3 100644 --- a/docs/audits/README.md +++ b/docs/audits/README.md @@ -32,6 +32,7 @@ header say so. | `2026-08-documentation-inventory.md` | 2026-08 | documentation-surface inventory | `91ac498` | checked lists (drift-guarded) | — | | `2026-08-specification-coverage.md` | 2026-08 | specification coverage matrix | `91ac498` | navigation aid; not normative | — | | `2026-08-documentation-spec-conformance.md` | 2026-08 | final documentation conformance audit | `91ac498` | this session's audit | — | +| `2026-08-wirthian-design-recovery.md` | 2026-08 | as-built computational-model recovery (reconstruction/falsification/synthesis) | `92f83d8` | current normative model in `docs/computational-model.md` + `docs/design/` | — | ## Rules diff --git a/docs/audits/repository-inventory.json b/docs/audits/repository-inventory.json index 39a3f95..5257ded 100644 --- a/docs/audits/repository-inventory.json +++ b/docs/audits/repository-inventory.json @@ -3702,5 +3702,61 @@ "authoritative": "reviewed", "generated": false, "current": false + }, + { + "path": "docs/computational-model.md", + "category": "Normative current documentation", + "audience": "contributors and maintainers", + "authoritative": "implementation + canonical evidence (see docs/README authority map)", + "generated": false, + "current": true + }, + { + "path": "docs/design/algorithms.md", + "category": "Normative current documentation", + "audience": "contributors and maintainers", + "authoritative": "implementation + canonical evidence (session 56 design recovery)", + "generated": false, + "current": true + }, + { + "path": "docs/design/algorithm-data-matrix.md", + "category": "Normative current documentation", + "audience": "contributors and maintainers", + "authoritative": "implementation + canonical evidence (session 56 design recovery)", + "generated": false, + "current": true + }, + { + "path": "docs/design/data-structures.md", + "category": "Normative current documentation", + "audience": "contributors and maintainers", + "authoritative": "implementation + canonical evidence (session 56 design recovery)", + "generated": false, + "current": true + }, + { + "path": "docs/design/invariants.md", + "category": "Normative current documentation", + "audience": "contributors and maintainers", + "authoritative": "implementation + canonical evidence (session 56 design recovery)", + "generated": false, + "current": true + }, + { + "path": "docs/design/state-machines.md", + "category": "Normative current documentation", + "audience": "contributors and maintainers", + "authoritative": "implementation + canonical evidence (session 56 design recovery)", + "generated": false, + "current": true + }, + { + "path": "docs/audits/2026-08-wirthian-design-recovery.md", + "category": "Historical decision record", + "audience": "maintainers", + "authoritative": "dated audit", + "generated": false, + "current": false } ] \ No newline at end of file diff --git a/docs/computational-model.md b/docs/computational-model.md new file mode 100644 index 0000000..da992d4 --- /dev/null +++ b/docs/computational-model.md @@ -0,0 +1,208 @@ +# inim — computational model (as-built) + +Status: current normative documentation, recovered from implementation at +commit `92f83d8` by the Wirthian design-recovery audit +([dated audit](audits/2026-08-wirthian-design-recovery.md)). +Labels: OBSERVED (code/schema/test), INFERRED (strong support, not +enforced), CLAIMED (documented, not established), UNKNOWN. + +## What inim is + +inim is a single-binary Rust application (one library crate, one `inim` +binary) that explains externally visible BGP route-state changes around +operator-declared network events. It is a local analysis and review +system: it never measures traffic or service state, never contacts the +event source at analysis time, and it publishes its conclusions as +immutable per-run artifacts plus derived catalog projections. + +## Principal inputs + +1. **Source records** — external event bytes: GRNOC public task-viewer + records, Internet2 ticket fixtures, tracked immutable source snapshots + (`/.source.json` + optional `.meta.json`). +2. **Reviewed interpretation** — reviewed manifest revisions + (`manifests/*.json`, schema v2), ticket reviews, case-study data files + (`case-studies/*/case-study.json`), network profiles + (`src/profiles/`), project-scope policy (`config/project-scope.toml`). +3. **Public BGP archives** — RouteViews and RIPE RIS RIB/UPDATE archives, + acquired by the worker at execution time (never at review time). +4. **Evaluation material** — scenario manifests + (`evaluation/scenarios.toml`), the deterministic demo manifest, and + task documents. + +## Principal data structures + +The complete catalog is in [design/data-structures.md](design/data-structures.md). +The central ones: + +- `RouteObservation` / `RouteKey` (collector, peer IP, prefix, optional + path ID) — normalized BGP observation and ADD-PATH-aware route-instance + identity (`src/domain/observation.rs`, `src/domain/route.rs`). +- `FrozenCohort` — baseline observer-prefix admission + (`src/cohort.rs`). +- `StreamLifecycle` — per-observer-prefix lifecycle (`src/lifecycle.rs`). +- `AnalysisPlan` / `ArchivePlan` — pre-execution plans (`src/plan.rs`, + `src/catalog/archive_plan.rs`). +- `AnalysisJob` / `JobState` — durable execution state + (`src/catalog/jobs/mod.rs`). +- `Verdict` / `ObservedResultKind` / `ExpectationAssessmentKind` — + result vocabulary (`src/domain/assessment.rs`). +- `AnalysisRun` / `AnalysisArtifact` — published evidence records + (SQLite tables `analysis_runs`, `analysis_artifacts`). +- `RoutingFinding` — operator-facing presentation model + (`src/catalog/workbench.rs`). + +## Transformations + +The pipeline is described in [design/algorithms.md](design/algorithms.md). +The main transformations: + +1. **Ingest** — MRT elements → `RouteObservation` (the only module that + imports `bgpkit-parser`; `src/ingest/mod.rs`). +2. **Preflight** — RIB observations → per-collector frozen cohort + (`src/orchestrate.rs` phase A, `src/cohort.rs`). +3. **Reconstruct** — observations → route-instance state → transitions + (`src/routes.rs`), then observer-prefix aggregation → `StreamLifecycle` + (`src/lifecycle.rs`). +4. **Group** — transitions → semantic waves (`src/waves.rs`) and + operator findings (`select_principal_findings`, + `src/catalog/workbench.rs`). +5. **Derive** — transitions/lifecycles + reviewed expectation → verdict, + observed result, expectation assessment (`src/assess.rs`, + `src/domain/assessment.rs`). +6. **Publish** — staged outputs → validated immutable run directory → + catalog rows (`src/catalog/jobs/publish.rs`). +7. **Project** — catalog + evidence → workbench/event/run pages, + path/fabric diagrams, API responses (`src/catalog/web/*`), and the + evaluation answer key (`scripts/build-evaluation-answer-key.py`). + +## What is persisted + +- **SQLite catalog** (`inim catalog init`): events, snapshots, manifest + revisions, plans, runs, artifacts, stream/wave summaries, case studies, + tickets, relationships, reviews, jobs, heartbeats. Schema migrations + via `PRAGMA user_version` (`src/catalog/migrations.rs`). +- **Immutable run directories** on the filesystem under a catalog root + (`data/runs///…` for worker publication; reviewed + evidence under `case-studies/*/out` and `case-studies/*/pilot/out` are + tracked in Git). +- **Derived caches** (gitignored): RIB/UPDATE caches and source + extractions under a cache root (`src/derived_cache.rs`, + `src/catalog/source_extract.rs`). + +## What is immutable + +- Source snapshots (append-only rows keyed by content SHA-256; + `UNIQUE (event_id, content_sha256)`). +- Reviewed manifest revisions (keyed by `sha256`, `UNIQUE`). +- Analysis plan revisions (keyed by `sha256`, `UNIQUE`). +- Published analysis runs and their artifact rows (immutable by + convention; import rejects hash mismatches and conflicting immutables). +- Completed / Cancelled / Failed jobs (mutation is rejected by the + service layer; retry creates a new job linked via `original_job_id`). + +## External effects + +- Filesystem writes: run publication, staging, demo catalog creation, + document import, cache writes. +- SQLite writes: catalog import, job lifecycle, sync, review. +- Network reads: archive acquisition and GRNOC sync — **execution-time + only**, owned by the worker (`--offline` disables it). +- Subprocess: `git rev-parse --short HEAD` for import provenance. +- HTTP responses: the read-only-by-default web server + (`inim serve`); all mutations are POST and require `--enable-writes`. +- Terminal output: CLI commands. +- No telemetry, no authentication, no external service calls except the + two source families above. + +## What inim produces + +Per run: a validated immutable artifact set (report, transitions, +lifecycle, semantic waves, withdrawal audit, evidence appendix, archive +manifest, limitations) plus catalog projections and workbench pages. Per +event: a derived readiness record and, when reviewed and analyzed, an +observed result (one of route-state changes observed, no route-state +change observed, insufficient qualifying visibility, analysis incomplete) +and an expectation assessment (consistent, partially consistent, less or +more externally visible change, not assessable). + +## What inim deliberately does not conclude + +- It does not conclude traffic, circuit, or service impact; the scope + statement on every observed result says observation is limited to + externally exported BGP route state at selected public-BGP observer + sessions. +- It does not conclude about events whose reviewed relationship is not + directly observable in public BGP, or that lack reviewed origin + attribution. +- It does not fabricate conclusions for excluded project-scope material. +- It does not turn infrastructure failure into a routing verdict: + `AnalysisOutcome::Incomplete` never renders as a visibility statement. + +## Program equation + +> The program is fundamentally a set of **immutable evidence and +> reviewed-interpretation** data structures (source snapshots, manifest +> revisions, plans, published runs and artifacts, plus policy and +> case-study overlays), transformed by **ingestion, cohort freezing, +> route reconstruction, lifecycle and finding derivation, verdict +> derivation, publication, and catalog projection** algorithms, under +> the invariants **of identity (ADD-PATH-aware route identity), source +> and plan immutability, provenance, temporal consistency, project +> scope, observer eligibility, artifact integrity, and read-only +> presentation**, in order to produce **observer-scoped explanations of +> public BGP route-state change around operator-declared events, with +> reviewed expectations assessed and presentation kept distinct from +> evidence**. + +This equation is the synthesis of the reconstruction and falsification +passes; the dated audit records the claims that did not survive. + +## Reading path + +Ordered by conceptual dependency (about 12 entries; symbols are current +at the pinned commit): + +1. `src/domain/route.rs` — `RouteKey`, `ObserverPrefixKey`, + `RouteTransition`, `TransitionKind` (route-instance identity). +2. `src/domain/observation.rs` — `RouteObservation`, `ObservationKind` + (ingestion boundary). +3. `src/domain/assessment.rs` — `Verdict`, `ObservedResultKind`, + `ExpectationAssessmentKind` (result vocabulary). +4. `src/manifest.rs` — `Manifest`, `TransitPredicateMapping` (reviewed + interpretation, schema v2). +5. `src/plan.rs` — `AnalysisPlan`, `AnalysisBlockReason` (planning). +6. `src/catalog/archive_plan.rs` — `ArchivePlan`, `CollectorPlan` + (archive selection). +7. `src/cohort.rs` — `FrozenCohort`, `freeze_cohort` (baseline + admission). +8. `src/lifecycle.rs` — `StreamLifecycle`, `StreamCategory` + (lifecycle derivation). +9. `src/catalog/jobs/mod.rs` — `JobState`, `legal_transition` + (durable execution). +10. `src/catalog/jobs/publish.rs` — `validate_staged`, + `publish_staged_run` (publication). +11. `src/catalog/artifact_path.rs` — `resolve_artifact` (artifact + resolution authority). +12. `src/catalog/workbench.rs` — `RoutingFinding`, + `select_principal_findings` (operator-facing presentation). + +## Future incremental audit format (design checksum) + +Documented maintenance method only — no automation in this session. + +For any future commit range `A..B`, report only changes to: + +- **data structures** (structs, enums, tables, JSON records, directory + conventions); +- **semantic identity** (keys, normalization, hash inputs); +- **invariants** (additions, removals, enforcement changes); +- **algorithms** (new, renamed, reordered, complexity changes); +- **state transitions** (state sets, legal transitions, recovery); +- **effects** (new filesystem/SQLite/network/subprocess effects); +- **authority boundaries** (who writes a fact, who projects it); +- **information-loss boundaries** (where precision is discarded); +- **complexity** (cardinalities, memory, streaming behavior). + +For each change record: **before**, **after**, **evidence** (path + +symbol or named test), **compatibility consequence**, **tests**. diff --git a/docs/design/algorithm-data-matrix.md b/docs/design/algorithm-data-matrix.md new file mode 100644 index 0000000..83ed397 --- /dev/null +++ b/docs/design/algorithm-data-matrix.md @@ -0,0 +1,90 @@ +# inim — algorithm–data-structure matrix (as-built) + +Status: current normative documentation, recovered at commit `92f83d8`. +For each major algorithm: structures read, structures mutated, structures +produced, invariants relied upon, effects, principal tests. + +Legend: R = read, M = mutated, P = produced. Invariant ids refer to +[invariants.md](invariants.md). + +## Source / review + +| Algorithm | Structures read | M | P | Invariants relied upon | Effects | Principal tests | +|-----------|-----------------|---|----|------------------------|---------|-----------------| +| A1 MRT parse + normalize | MRT archives | — | `RouteObservation` | ID-1 | file read | `src/ingest/mod.rs` | +| A16 catalog import precedence | manifests, out dirs, snapshots, fixtures | catalog rows | event/snapshot/plan/run/artifact rows | PR-1, PR-2 | DB write, git subprocess | `import_completed_event_creates_analysis_run`, `repeated_import_is_idempotent`, `artifact_hash_mismatch_is_rejected` | +| A21 case-study projection | case-study.json, documents | catalog rows | case study, links, plans | PR-2, PV-7 | DB write | `src/catalog/case_study_import.rs` tests | + +## Planning / policy + +| Algorithm | Structures read | M | P | Invariants relied upon | Effects | Principal tests | +|-----------|-----------------|---|----|------------------------|---------|-----------------| +| A10 plan construction | manifest | — | `AnalysisPlan` | PR-6 | none (no network) | `src/plan.rs` | +| A11 archive selection | case study, targets, family constants | — | `ArchivePlan` | SC-* | none | `src/catalog/archive_plan.rs` | +| A12 canonical plan hashing | manifest payload | — | plan sha256 | PR-3 | none | `plan_hash_is_deterministic`, `plan_hash_normalizes_collector_order` | +| A18 project-scope matching | `config/project-scope.toml` | — | scope decision | SC-1..SC-5 | none | `project_scope_policy_test.rs`, `project_scope_enforcement_test.rs` | +| A19 readiness/status derivation | snapshots, manifests, plans, runs, reviews | — | `CatalogStatus`, `Analyzability` | TC-4 | DB read | `src/catalog/status.rs` | + +## Execution / publication + +| Algorithm | Structures read | M | P | Invariants relied upon | Effects | Principal tests | +|-----------|-----------------|---|----|------------------------|---------|-----------------| +| A13 job queue/claim/lease/retry | `analysis_jobs`, events | job rows | job events | JB-1..JB-6, ID-9 | DB write | `two_workers_cannot_claim_same_job`, `worker_claims_oldest_job_deterministically`, `expired_lease_is_detected_and_not_auto_resumed`, `illegal_state_transition_is_rejected` | +| A14 analysis execution | archives, caches | caches, staging | transitions/lifecycles/waves | RR-*, TC-5 | network, file write | `tests/job_workflow_tests.rs`, `tests/queued_analysis_e2e_test.rs` | +| A15 artifact validation/publication | staging dir, `analysis_runs` | catalog rows, final dir | run + artifacts | AR-3..AR-6, PR-4 | file rename, DB write | `incomplete_stage_is_not_visible_as_run`, `publication_is_idempotent_for_same_job`, `invalid_artifact_blocks_publication` | +| A23 insufficient-visibility artifact set | RIB metadata | out dir | 9-file artifact set | RR-2 | file write | `empty_preflight_returns_insufficient_visibility` | + +## BGP reconstruction + +| Algorithm | Structures read | M | P | Invariants relied upon | Effects | Principal tests | +|-----------|-----------------|---|----|------------------------|---------|-----------------| +| A2 RIB preflight + cohort freeze | `RouteObservation` | — | `FrozenCohort` | RR-1, RR-2 | none | `src/cohort.rs` tests | +| A3 derived caches / extraction | archives, caches | cache files | cached entries | ID-1, PR-4 | file write | `derived_cache.rs` schema-version tests | +| A4 route-state reconstruction | observations | in-memory state | `RouteTransition` | ID-1, ID-10, RR-5 | none | `src/routes.rs`, lifecycle instance tests | +| A5 lifecycle derivation | transitions, cohort | — | `StreamLifecycle` | ID-10, RR-3, RR-4 | none | `nonfinal_instance_loss_does_not_make_withdrawn_lifecycle` | +| A6 diff/tokenize | `RouteState` | — | `TransitionKind` | RR-5 | none | `src/tokenize.rs` | + +## Findings / results + +| Algorithm | Structures read | M | P | Invariants relied upon | Effects | Principal tests | +|-----------|-----------------|---|----|------------------------|---------|-----------------| +| A7 wave detection | transitions | — | `ImpactWave` | determinism | none | `src/waves.rs` | +| A8 verdict/result derivation | transitions, lifecycles, expectation | — | `Verdict` + projections | FR-1..FR-6, TC-5 | none | `empty_transitions_is_no_impact`, `unknown_continuity_suppresses_strong_verdict` | +| A9 finding grouping | lifecycles, transitions, reviewed labels | — | `RoutingFinding` | FR-4, FR-5, FR-6 | none | `principal_findings_prefer_observer_diversity` | + +## Presentation / evaluation + +| Algorithm | Structures read | M | P | Invariants relied upon | Effects | Principal tests | +|-----------|-----------------|---|----|------------------------|---------|-----------------| +| A17 artifact resolution | catalog rows, filesystem | — | resolved path | AR-1, AR-2 | file stat | `artifact_path.rs` containment tests | +| A20 demo init/verify | tracked reviewed trees | demo DB | demo catalog + manifest | PV-2, PV-3, SC-2 | file write | `demo verify` gates; CI evaluation-smoke | +| A22 answer-key generation | tracked artifacts, demo manifest | — | answer key | PV-3 | file write | CI drift check | +| Path/fabric diagrams | lifecycle evidence, reviewed attachments | — | SVG | PV-4, PV-5, PV-6 | HTTP response | `fabric_diagram_contains_no_fabric_asn`, `observed_path_not_labeled_commercial_relationship`, `final_path_not_assumed_baseline` | + +--- + +## Coupling observations + +- **Excessive fan-in**: `AnalysisPlan`/`AnalysisPlanRecord` is read by + readiness derivation, queue, worker, workbench, plan review page, and + audit tooling; its payload is the authority for plan semantics. +- **Excessive fan-out**: `resolve_artifact` is the documented authority + but is not the only resolver (AR-1) — the artifact path fact is + duplicated across resolver implementations. +- **Algorithms coupled to unrelated representations**: `derive_analyzability` + reads `analysis_plans.payload` JSON via string parsing + (`serde_json::from_str`) rather than a typed projection; `derive_status` + re-parses manifest payloads. +- **Algorithms reading both canonical and presentation structures**: + workbench loaders read `stream_lifecycle_summaries` (projection) and + `lifecycle.json` (canonical) and reconcile them for exact path + evidence — a duplication that requires synchronization discipline. +- **Mutable structures participating in semantic identity**: none of the + identity-bearing types are mutable after insertion; the only mutable + identity-adjacent row is `catalog_events.last_seen` (see PR-1). +- **Duplicated structures requiring synchronization**: run evidence + exists both as canonical artifact JSON and as projected SQLite rows + (`stream_lifecycle_summaries`, `semantic_wave_summaries`, + `run_transitions`); import asserts counts match and can be rebuilt + from artifacts (`transition_import_is_streamed_or_bounded`, + `transition_index_can_be_rebuilt_from_artifact`). diff --git a/docs/design/algorithms.md b/docs/design/algorithms.md new file mode 100644 index 0000000..2d10564 --- /dev/null +++ b/docs/design/algorithms.md @@ -0,0 +1,329 @@ +# inim — algorithm catalog (as-built) + +Status: current normative documentation, recovered at commit `92f83d8`. +Algorithms are named even when distributed across modules. Classification: +**standard** (textbook), **domain adaptation** (standard adapted to BGP), +**repository-specific** (specific to inim), **distributed/unnamed** +(no single named implementation; spread across modules). + +--- + +## A1. MRT parse and normalize + +- Symbols: `ObservationStream::from_local_file`, `IngestContext` + (`src/ingest/mod.rs`), consumed by `src/orchestrate.rs`. +- Purpose: convert bgpkit-parser `BgpElem` records into inim-native + `RouteObservation` values at the single ingestion boundary. +- Inputs: MRT archive file path + role (RIB/UPDATE) + collector identity. +- Outputs: iterator of `RouteObservation` (with `ObservationId`, + provenance, path_id). +- State read: none (streaming). State mutated: none. +- External effects: file read only. +- Preconditions: caller supplies role/collector (never inferred). +- Postconditions: bgpkit types do not leak beyond this module. +- Ordering: archive order index assigned by the coordinator. +- Determinism: deterministic per file. +- Idempotence: parse is re-runnable. +- Termination: per file, bounded. +- Failure behavior: `InimError` variants (decode, unsupported, missing + baseline, discontinuity). +- Complexity: O(records); streaming. +- Classification: domain adaptation. + +## A2. RIB preflight and frozen-cohort admission + +- Symbols: `run_inner_impl` phase A (`src/orchestrate.rs`), + `freeze_cohort` (`src/cohort.rs`). +- Purpose: select the best baseline RIB per collector (latest at/before + warmup) and admit observer-prefix keys whose baseline has a + target-origin instance satisfying the transit predicate. +- Inputs: RIB observations, `origin_asns`, `TransitPredicate`. +- Outputs: `FrozenCohort` (admitted keys + baseline instances), + per-collector counts. +- State mutated: derived RIB cache and source extraction cache. +- Preconditions: RIB at/before warmup exists for the collector. +- Postconditions: only admitted streams enter reconstruction. +- Determinism: deterministic; `BTreeMap`/`BTreeSet` iteration. +- Complexity: O(RIB observations); in-memory materialized. +- Failure: missing RIB → collector skipped + limitation; zero retained + collectors → `AnalysisOutcome::InsufficientVisibility`. +- Classification: repository-specific (domain adaptation of baseline + qualification). + +## A3. Derived RIB/UPDATE cache and source extraction + +- Symbols: `rib_cache_key`, `load_rib_cache`, `load_update_cache`, + `extraction_key`, `load_origin_extraction` + (`src/derived_cache.rs`, `src/catalog/source_extract.rs`). +- Purpose: avoid re-parsing unchanged archives; reuse origin-matching + observations across predicates. +- Inputs: archive sha256 + collector + origin set + predicate identity + + manifest revision. +- Outputs: cached entries or miss. +- Invariants: schema versions gate format + (`RIB_CACHE_SCHEMA_VERSION=2`, `UPDATE_CACHE_SCHEMA_VERSION=2`, + `OBSERVATION_SCHEMA_VERSION=2`); a cache miss must not alter results. +- Caveat (falsification): extraction reuse is keyed on origin set only; + a different predicate with the same origin set can miss + predicate-2-only streams (cohort-completeness risk). +- Classification: repository-specific cache. + +## A4. Route-state reconstruction + +- Symbols: `RouteStateMachine` (`src/routes.rs`), + `build_lifecycles`/per-instance simulation (`src/lifecycle.rs`). +- Purpose: apply observations to per-`RouteKey` state, emit transitions. +- Inputs: baseline + update observations in archive order. +- Outputs: `RouteTransition` sequence. +- State mutated: in-memory state maps, event-baseline map. +- Ordering: chronological per route instance; archive order for ties. +- Determinism: deterministic for a fixed input order. +- Correctness dependencies: ADD-PATH-aware identity (ID-1); duplicate + suppression; one path-ID withdrawal does not mark stream absence + (ID-10). +- Complexity: O(observations) with per-key hash lookups. +- Classification: domain adaptation. + +## A5. Observer-prefix lifecycle derivation + +- Symbols: `StreamLifecycle` builders (`src/lifecycle.rs`). +- Purpose: aggregate per-instance history into per-observer-prefix + lifecycle: category, flags, first change, absence, restoration, + cooldown, final state. +- Inputs: transitions + baseline instances + horizon (warmup/event/ + cooldown). +- Outputs: `StreamLifecycle` vector. +- Determinism: deterministic; chronological transitions retained. +- Correctness dependencies: stream absence requires final-instance loss; + add-path ambiguity suppresses strong conclusions. +- Complexity: O(instances + transitions) per stream. +- Classification: repository-specific. + +## A6. Transition diff and tokenization + +- Symbols: `diff_states`, `tokenize`, `TransitionSymbol` + (`src/tokenize.rs`). +- Purpose: classify a state change into `TransitionKind` (with + orthogonal `GenericTransitionEffects`). +- Determinism: deterministic. +- Classification: domain adaptation. + +## A7. Wave detection and motif extraction + +- Symbols: `detect_waves`, `build_wave`, `sequitur_motif` + (`src/waves.rs`, `src/sequitur/`). +- Purpose: group transitions into temporal clusters (gap threshold) and + derive SEQUITUR motifs. +- Determinism: sorts transitions by timestamp; deterministic. +- Note: waves do NOT feed the episode presentation model + (`src/catalog/workbench.rs`); they appear in phase summaries and + artifacts. +- Classification: repository-specific (deterministic clustering). + +## A8. Verdict and result derivation + +- Symbols: `derive_verdict`, `assess` (`src/assess.rs`); projections + `observed_result_kind`, `expectation_assessment_kind`, + `assessment_kind` (`src/domain/assessment.rs`). +- Purpose: combine transitions/lifecycles + reviewed expectation into a + `Verdict`, then project onto observed-result and expectation-assessment + axes. +- Ordering: empty-transitions early return precedes the continuity gate + — see [invariants TC-5](invariants.md) (falsification finding). +- Determinism: deterministic. +- Classification: repository-specific. + +## A9. Finding grouping and principal-finding selection + +- Symbols: `select_principal_findings`, `RoutingFinding` builders + (`src/catalog/workbench.rs`). +- Purpose: group lifecycle evidence into operator-facing findings; + select principal vs additional findings with observer-diversity + preference. +- Determinism: deterministic (sorted selection). +- Classification: repository-specific (presentation). + +## A10. Plan construction and blocking + +- Symbols: `plan_from_manifest`, `plan_analysis`, + `AnalysisPlanStatus`, `AnalysisBlockReason` (`src/plan.rs`). +- Purpose: produce a pre-execution plan or a blocker before any network + activity. +- Blockers: `MissingReviewedEntityMapping`, + `MissingReviewedTransitPredicate`, `MissingAnalysisEndForOpenTicket`, + `InvalidAnalysisWindow`, `UnsupportedManifestRevision`. +- Classification: repository-specific. + +## A11. Archive selection and planning + +- Symbols: `build_plan_for_families`, `archive_url_for`, + `rib_interval_secs`, `SourceFamily::{RouteViews, RipeRis}` + (`src/catalog/archive_plan.rs`). +- Purpose: per-collector, per-family baseline RIB + optional validation + RIB + 5-minute UPDATE sequence over the horizon; estimates bytes. +- Family differences: URL construction, RIB cadence, compression — + encoded in `SourceFamily` helpers, not in generic code. +- Target coverage: only HistoricallyReviewed targets enter; others are + blocked with recorded reasons. +- Determinism: schedule generated from one cadence; `BTreeSet` dedupe. +- Classification: repository-specific. + +## A12. Canonical plan hashing + +- Symbols: `canonical_plan_hash` (`src/catalog/jobs/plan.rs`). +- Purpose: SHA-256 over the canonical serialization of the manifest + payload; covers execution-relevant fields, ignores generated + timestamps and display labels; normalizes collector order. +- Tests: `plan_hash_is_deterministic`, + `plan_hash_normalizes_collector_order`, + `plan_hash_changes_for_execution_field`. +- Classification: repository-specific. + +## A13. Durable job queue, claim, lease, cancel, retry + +- Symbols: `queue`, `claim_next`, `renew_lease`, `mark_stale_leases`, + `request_cancel`, `observe_cancel`, `retry`, `heartbeat` + (`src/catalog/jobs/service.rs`). +- Queue: idempotency — active duplicate returns existing job. +- Claim: `BEGIN IMMEDIATE` transaction prevents double-claim; oldest + job first. +- Lease: 90 s default; heartbeat 15 s; stale leases detected, never + auto-resumed. +- Cancel: queued jobs cancel directly; executing jobs enter + `CancelRequested` and observe cancellation cooperatively. +- Retry: new job with `original_job_id`, attempt+1; old job immutable. +- Tests: `two_workers_cannot_claim_same_job`, + `worker_claims_oldest_job_deterministically`, + `expired_lease_is_detected_and_not_auto_resumed`. +- Classification: repository-specific (SQLite-backed). + +## A14. Analysis execution and archive acquisition + +- Symbols: `run_inner_impl` phases B–G (`src/orchestrate.rs`), + `LiveArchiveDiscovery` (`src/discover.rs`), `CacheScanDiscovery` + (offline), bounded download/parse pools. +- Purpose: discover archives, cache, parse baseline + updates, freeze + cohort, reconstruct, derive evidence, write artifacts. +- Continuity: UPDATE gaps set `any_continuity_unknown` (feeds A8). +- Cancellation: cooperative `AtomicBool` checks between stages. +- Offline mode: `--offline` + `CacheScanDiscovery` avoids network. +- Classification: repository-specific. + +## A15. Artifact validation and publication + +- Symbols: `write_execution_metadata`, `validate_staged`, + `publish_staged_run`, `import_finalized_run`, `reconcile_orphans` + (`src/catalog/jobs/publish.rs`). +- Purpose: write to `data/jobs//staging/`, validate + (required artifacts present; plan hash matches; schema versions), + rename staging → final immutable location (same filesystem), import + into catalog, complete job. +- Failure windows: crash between rename and import leaves an orphan + final directory (detected by audit, not auto-repaired). +- Idempotence: publication idempotent for the same job (test). +- Classification: repository-specific. + +## A16. Catalog import precedence + +- Symbols: `import_repository`, `import_one`, + `case_study_snapshot_for`, `ticket_fixture_for` + (`src/catalog/import.rs`). +- Purpose: import tracked manifests + run outputs into the catalog. +- Snapshot precedence: (1) tracked reviewed case-study snapshot + `/.source.json` (+ meta), (2) tracked offline + fixture, (3) manifest-derived. +- Run import: completed runs only; preflight-only dirs never imported. +- Idempotence: repeated import is idempotent (test). +- Classification: repository-specific. + +## A17. Artifact resolution + +- Symbols: `resolve_artifact` (`src/catalog/artifact_path.rs`). +- Purpose: resolve a catalog-relative artifact path under a root via + conventional candidates: `/`, `/out/`, + `/case-studies//out/`, `/case-studies// + pilot/out/`; first existing wins. +- Containment: rejects absolute and parent-relative paths. +- Falsification: other modules use their own resolvers with different + candidate sets (see [invariants AR-1](invariants.md)). +- Classification: repository-specific. + +## A18. Project-scope matching + +- Symbols: `ProjectScope::load`, `excluded_entity_name`, + `excluded_asn`, `excluded_source_record`, `normalize_exact` + (`src/catalog/scope.rs`). +- Purpose: exact-normalized matching of excluded entities/ASNs/source + records; used by view filters, queue/worker checks, demo verify. +- Determinism: deterministic. +- Classification: repository-specific. + +## A19. Readiness and status derivation + +- Symbols: `derive_status` (`src/catalog/status.rs`), + `derive_analyzability` (`src/catalog/analyzability.rs`). +- Purpose: derive `CatalogStatus` (8 values, deterministic precedence) + and readiness (14 values) from stored inputs — never stored as truth. +- Classification: repository-specific. + +## A20. Demo init and verify + +- Symbols: `demo_init`, `demo_verify`, `import_pilot_runs` + (`src/catalog/demo.rs`). +- Purpose: build a fresh deterministic offline catalog from tracked + reviewed material only; verify expected events, artifact resolution, + absence of exclusions and absolute paths. +- Precedence: reviewed case-study trees only; runtime `out/`/`data/` + play no part. +- Determinism: demo-manifest has no timestamps. +- Classification: repository-specific. + +## A21. Case-study projection + +- Symbols: `import_case_study` (`src/catalog/case_study_import.rs`), + `build_plan`/`apply_pilot_result` (`src/catalog/archive_plan.rs`). +- Purpose: import reviewed case-study data files, link events (never + fabricating source snapshots), store draft archive plans and pilot + results. +- Classification: repository-specific. + +## A22. Answer-key generation + +- Symbols: `scripts/build-evaluation-answer-key.py`. +- Purpose: derive evaluator answers from tracked reviewed artifacts + + demo manifest; hard-requires artifacts; drift check in CI. +- Determinism: no randomness/timestamps; byte-deterministic (experiment). +- Classification: repository-specific. + +## A23. Insufficient-visibility artifact set + +- Symbols: `write_insufficient_visibility_artifacts` + (`src/orchestrate.rs`). +- Purpose: when zero retained collectors, write the standard artifact + shapes with empty content: `report.json`, `report.txt`, + `limitations.json`, `archive_manifest.json`, `transitions.json`, + `semantic_waves.json`, `lifecycle.json`, `withdrawal_audit.json`, + `evidence_appendix.jsonl`. +- Classification: repository-specific. + +--- + +## Distributed/unnamed algorithms + +- **Event-window construction and warmup/cooldown arithmetic**: spread + across `src/manifest.rs` (`event_window()`), `src/plan.rs`, and + `src/catalog/archive_plan.rs`; no single named function. +- **Cross-observer aggregation**: spread across `src/waves.rs`, + `src/catalog/observer_compare.rs`, `src/catalog/phase_summary.rs`, + and workbench loaders. +- **Information-loss boundaries**: normalization in `src/ingest/mod.rs`, + aggregation in `src/lifecycle.rs`, grouping in + `src/catalog/workbench.rs` — see [the dated audit](../audits/2026-08-wirthian-design-recovery.md). + +## Algorithms with significant fan-in/fan-out + +- `freeze_cohort` (A2) — input to reconstruction, lifecycle, caches. +- `derive_analyzability` (A19) — reads manifests, plans, runs, reviews, + case-study plans. +- `resolve_artifact` (A17) — read by demo verify, web run page, artifact + audit — but not by every consumer (see AR-1). diff --git a/docs/design/data-structures.md b/docs/design/data-structures.md new file mode 100644 index 0000000..670dba3 --- /dev/null +++ b/docs/design/data-structures.md @@ -0,0 +1,156 @@ +# inim — data-structure catalog (as-built) + +Status: current normative documentation, recovered at commit `92f83d8`. +Structures are grouped by computational role, not by source file. Only +structures that explain the program are listed; a secondary list covers +supporting implementation structures. + +Labels: OBSERVED (code/schema), INFERRED (strong support, not enforced), +CLAIMED (documented, not established), UNKNOWN. + +## 1. External and normalized source records + +| Structure | Concept | Identity | Lifetime | Authority | Mutability | Invariants | Principal algorithms | Evidence | +|-----------|---------|----------|----------|-----------|------------|------------|----------------------|----------| +| `RouteObservation` | One normalized BGP observation (RIB entry, announcement, withdrawal, session boundary) | `ObservationId` (run-local u64) | analysis run | `src/ingest/mod.rs` (conversion boundary) | immutable in memory | carries `collector`, `peer_ip`, `peer_asn`, `prefix`, `path_id`, `provenance` | MRT parse + normalize | `src/domain/observation.rs` | +| `RouteKey` | Route-instance identity | `(collector, peer_ip, prefix, path_id)` | analysis run | `src/domain/route.rs` | immutable | ADD-PATH aware; hashes/orders | cohort freeze, lifecycle, caches | `src/domain/route.rs` | +| `ObserverPrefixKey` | Observer-prefix aggregate identity | `(collector, peer_ip, prefix)` | analysis run | `src/domain/route.rs` | immutable | no path_id; distinct from RouteKey | stream aggregation | `src/domain/route.rs` | +| `EventSnapshot` | Immutable source record row | `(event_id, content_sha256)` | catalog lifetime | catalog insert (sync/import) | append-only | never updated/deleted; raw payload preserved | snapshot insert/dedupe | SQLite `event_snapshots`; `src/catalog/store.rs` | + +## 2. Reviewed interpretation and policy + +| Structure | Concept | Identity | Lifetime | Authority | Mutability | Invariants | Principal algorithms | Evidence | +|-----------|---------|----------|----------|-----------|------------|------------|----------------------|----------| +| `Manifest` (schema v2) | Reviewed analysis manifest | `(event_id, payload sha256)` | catalog lifetime | reviewed tracked file `manifests/*.json` | immutable revision | open events require `analysis_end_utc`; `TransitPredicateMapping` status Reviewed for ready plans | planning, migration | `src/manifest.rs` | +| `TransitPredicateMapping` | Reviewed transit predicate + provenance | embedded in manifest | catalog lifetime | reviewed | immutable | `is_ready()` = Reviewed + predicate present | plan readiness, archive selection | `src/manifest.rs` | +| `TicketReview` | Reviewed interpretation of a source record (roles, applicability, entity mapping) | `(external_id, reviewed_at)` | catalog lifetime | reviewed JSON import (`corpus-review`) | append-only revisions | role/applicability vocab validated | review import, analyzability | `src/catalog/review.rs` | +| `CaseStudyDataFile` | Reviewed case-study data file (entity taxonomy, interconnection context, documents, links) | `slug` | catalog lifetime | tracked `case-studies/*/case-study.json` | immutable revision | attachments list is the only fabric input | case-study import | `src/catalog/case_study_import.rs` | +| `ProjectScope` | Reviewed project-scope exclusion policy | config file identity | process + catalog | `config/project-scope.toml` | reviewed file | exact-normalized matching only | scope filtering, queue/worker checks | `src/catalog/scope.rs` | +| `NetworkProfile` | Source/network profile (Internet2, Indiana GigaPOP) | enum variant | compile-time | `src/profiles/` | code | title convention → expectation | expectation derivation | `src/profiles/mod.rs` | + +## 3. Planning + +| Structure | Concept | Identity | Lifetime | Authority | Mutability | Invariants | Principal algorithms | Evidence | +|-----------|---------|----------|----------|-----------|------------|------------|----------------------|----------| +| `AnalysisPlan` | Pre-execution plan | plan payload sha256 | catalog lifetime | planning algorithm | immutable revision | `AnalysisPlanStatus::{Ready, Blocked}` | plan_from_manifest | `src/plan.rs` | +| `AnalysisPlanRecord` | Stored plan row | `sha256 UNIQUE` | catalog lifetime | import/queue | append-only | blocked plans not queueable | readiness derivation | SQLite `analysis_plans` | +| `ArchivePlan` / `CollectorPlan` / `ExpectedFile` | Archive selection plan per collector/family | case study | catalog lifetime | `build_plan_for_families` | Draft until saved | family-correct URLs/cadence | archive planning | `src/catalog/archive_plan.rs` | +| `BlockedTarget` | Reviewed target that cannot be analyzed | source label | plan lifetime | reviewed target status | immutable | only HistoricallyReviewed targets enter | target coverage | `src/catalog/archive_plan.rs` | +| `Analyzability` | Derived readiness record | `event_id` | derived on read | `derive_analyzability` | never stored | 14-state readiness vocabulary | readiness derivation | `src/catalog/analyzability.rs` | + +## 4. Durable execution + +| Structure | Concept | Identity | Lifetime | Authority | Mutability | Invariants | Principal algorithms | Evidence | +|-----------|---------|----------|----------|-----------|------------|------------|----------------------|----------| +| `AnalysisJob` | Durable execution state for one plan revision | job id string | catalog lifetime | job service | state machine | 16 `JobState` values; forward-only transitions; terminal jobs immutable | queue, claim, lease, cancel, retry | `src/catalog/jobs/mod.rs`, `service.rs` | +| `JobEvent` | Append-only job event log | `(job_id, sequence)` | catalog lifetime | job service | append-only | structured detail bounded (4096 B) | event append | `src/catalog/jobs/mod.rs` | +| `WorkerHeartbeat` | Worker liveness | `worker_id` | catalog lifetime | worker | updateable | lease 90 s, heartbeat 15 s | heartbeat, stale detection | `src/catalog/jobs/service.rs` | +| `RunRecord` | Published analysis run row | `(plan_id, started_at)` | catalog lifetime | publication | immutable | status/vote distinct from outcome | import, status derivation | SQLite `analysis_runs` | +| `ArtifactRecord` | Published artifact row | `(run_id, relative_path)` | catalog lifetime | publication | immutable | sha256 recorded; relative path | artifact import/audit | SQLite `analysis_artifacts` | + +## 5. Protocol evidence and route identity + +| Structure | Concept | Identity | Lifetime | Authority | Mutability | Invariants | Principal algorithms | Evidence | +|-----------|---------|----------|----------|-----------|------------|------------|----------------------|----------| +| `FrozenCohort` | Admitted baseline observer-prefix keys + baseline instances | set of `ObserverPrefixKey` | analysis run | `freeze_cohort` | immutable after freeze | admission requires origin + transit predicate | cohort freeze | `src/cohort.rs` | +| `RouteState` | One route instance at a time | `RouteKey` | analysis run | reconstruction | mutated by transitions | carries timestamp, attributes, path_id | route-state machine | `src/domain/route.rs`, `src/routes.rs` | +| `RouteTransition` | State change between two route states | sequence + kind | analysis run | reconstruction | append | `TransitionKind` vocabulary + orthogonal `GenericTransitionEffects` | diff/tokenize, waves, lifecycle | `src/domain/route.rs`, `src/tokenize.rs` | +| `RouteStateMachine` | In-memory map of current states | `HashMap` | analysis run | `src/routes.rs` | mutated | event baseline map retained | reconstruction | `src/routes.rs` | + +## 6. Transitions and lifecycle + +| Structure | Concept | Identity | Lifetime | Authority | Mutability | Invariants | Principal algorithms | Evidence | +|-----------|---------|----------|----------|-----------|------------|------------|----------------------|----------| +| `StreamLifecycle` | Full lifecycle of one observer-prefix stream | `(collector, peer_ip, prefix)` | analysis run | lifecycle derivation | immutable after derivation | category + flags; retains all `RouteKey` histories | lifecycle derivation | `src/lifecycle.rs` | +| `LifecycleTransition` | Lightweight transition record | timestamp + phase + kind | analysis run | lifecycle derivation | immutable | phase vocabulary | lifecycle derivation | `src/lifecycle.rs` | +| `StreamCategory` | Primary classification of a stream | enum | analysis run | lifecycle derivation | immutable | Unchanged/PrependOnly/PathChangedStillViaTransit/DepartedTransitPath/Withdrawn | finding derivation | `src/lifecycle.rs` | +| `StreamFlags` | Secondary flags (restored, not_restored, multiple_cycles, add_path_ambiguous) | enum fields | analysis run | lifecycle derivation | immutable | ambiguity suppresses strong conclusions | restoration classification | `src/lifecycle.rs` | +| `StreamRestoration` | Restoration event record | per stream | analysis run | lifecycle derivation | immutable | restoration kinds | restoration classification | `src/lifecycle.rs` | + +## 7. Findings and results + +| Structure | Concept | Identity | Lifetime | Authority | Mutability | Invariants | Principal algorithms | Evidence | +|-----------|---------|----------|----------|-----------|------------|------------|----------------------|----------| +| `ImpactWave` / `WaveMotif` | Temporally concentrated transition group + SEQUITUR motif | wave id (run-local) | analysis run | `detect_waves` | immutable | deterministic gap clustering | wave detection | `src/waves.rs` | +| `RoutingFinding` | Operator-facing routing story at one observer session | `stable_id` | presentation | workbench derivation | derived on read | presentation model; exact paths in streams | finding grouping, `select_principal_findings` | `src/catalog/workbench.rs` | +| `Verdict` | Machine verdict enum | enum variant | analysis run + report | `derive_verdict` | immutable | 16 variants; `observed_result_kind` and `expectation_assessment_kind` are projections | verdict derivation | `src/domain/assessment.rs` | +| `ObservedResultKind` | Observed route-state result (4 values) | enum | analysis run + report | verdict projection | immutable | labels never contain expectation wording | result derivation | `src/domain/assessment.rs` | +| `ExpectationAssessmentKind` | Expectation assessment (7 values) | enum | analysis run + report | verdict projection | immutable | references reviewed expectation | assessment | `src/domain/assessment.rs` | +| `AnalysisOutcome` | Run outcome (completed / insufficient_visibility / incomplete) | tagged enum | analysis run + report | outcome assembly | immutable | infrastructure failure never a routing verdict | outcome assembly | `src/outcome.rs` | + +## 8. Artifacts and provenance + +| Structure | Concept | Identity | Lifetime | Authority | Mutability | Invariants | Principal algorithms | Evidence | +|-----------|---------|----------|----------|-----------|------------|------------|----------------------|----------| +| Immutable run directory | Published artifact set on disk | `data/runs///…` (worker); `case-studies/*/out//…` (reviewed, tracked) | catalog lifetime | publication | immutable | staging → validate → rename | publication | `src/catalog/jobs/publish.rs` | +| Artifact relative path | Catalog-relative path stored per artifact | unique per run | catalog lifetime | publication | immutable | no absolute/parent-relative | artifact resolution | SQLite `analysis_artifacts`; `src/catalog/artifact_path.rs` | +| `ExecutionMetadata` | Volatile metadata written into staging | plan_hash + stage | staging lifetime | worker | transient | plan hash must match queued plan | validation | `src/catalog/jobs/publish.rs` | +| `StreamPathEvidence` | Exact path evidence for one stream | `(run, collector, peer_ip, prefix)` | presentation | `load_lifecycle_evidence` | derived | exact paths retained | path diagrams | `src/catalog/web/path_diagram.rs` | + +## 9. Catalog projections + +| Structure | Concept | Identity | Lifetime | Authority | Mutability | Invariants | Principal algorithms | Evidence | +|-----------|---------|----------|----------|-----------|------------|------------|----------------------|----------| +| `CatalogStatus` | Derived analyst-facing status (8 values) | event | derived on read | `derive_status` | never stored | deterministic precedence | status derivation | `src/catalog/status.rs` | +| `StreamLifecycleSummary` | Projected stream summary row | `(run_id, collector, peer_ip, prefix)` | catalog lifetime | import | immutable | counts match report | import | SQLite `stream_lifecycle_summaries`; `src/catalog/import.rs` | +| `SemanticWaveSummary` | Projected wave row | `(run_id, wave_id)` | catalog lifetime | import | immutable | counts match report | import | SQLite `semantic_wave_summaries` | +| `RunTransitionRecord` | Projected transition row | `(run_id, seq)` | catalog lifetime | import | immutable | references canonical artifact | import | SQLite `run_transitions`; `src/catalog/import.rs` | +| `CaseStudyEventLink` | Case-study ↔ event link | case-study + event | catalog lifetime | case-study import | append-only | never fabricates source snapshots | case-study projection | SQLite `case_study_event_links` | + +## 10. Presentation models + +| Structure | Concept | Identity | Lifetime | Authority | Mutability | Invariants | Principal algorithms | Evidence | +|-----------|---------|----------|----------|-----------|------------|------------|----------------------|----------| +| View models (`DashboardView`, `EventView`, `RunView`, `WorkbenchView`, …) | Server-rendered page models | per request | request | `src/catalog/web/view.rs` loaders | read-only | scope-filtered | view construction | `src/catalog/web/view.rs` | +| `PathStateView` / `ObservedPath` / `PathNode` | AS-path diagram states | per stream | request | `comparison_states` | read-only | exact paths + compaction for display | path diagrams | `src/catalog/web/path_diagram.rs` | +| `FabricView` / `FabricAttachmentView` | Layer-2 fabric diagram | per case study | request | `interconnection_context.attachments` | read-only | only reviewed attached networks | fabric diagram | `src/catalog/web/path_diagram.rs` | + +## 11. Evaluation structures + +| Structure | Concept | Identity | Lifetime | Authority | Mutability | Invariants | Principal algorithms | Evidence | +|-----------|---------|----------|----------|-----------|------------|------------|----------------------|----------| +| Scenario manifest (`evaluation/scenarios.toml`) | Reviewed evaluation scenarios | schema v1 + scenario ids | tracked | reviewed | immutable | no answers embedded | task-to-page mapping | `evaluation/scenarios.toml` | +| Demo manifest | Deterministic demo catalog summary | schema v1 | tracked (generated) | `demo init` | regenerated | no timestamps; byte-deterministic | demo verification | `src/catalog/demo.rs` | +| Answer key (`evaluation/generated/answer-key.json`) | Generated evaluator answers | schema v1 | tracked (generated) | generator | regenerated | derived from artifacts; drift check in CI | answer-key generation | `scripts/build-evaluation-answer-key.py` | + +## Supporting implementation structures + +- `AnalysisPlan`/`EventWindow`/`TicketLifecycle`/`ImpactExpectation` + (`src/domain/event.rs`, `src/domain/expectation.rs`) — plan inputs. +- `ObservationAttributes`/`Communities`/`ObservationProvenance` + (`src/domain/observation.rs`) — route attribute payload. +- `IngestContext`/`InimError` (`src/ingest/mod.rs`) — parsing context. +- `CacheControl`/`CachedArchive`/`RibCacheEntry`/`UpdateCacheEntry` + (`src/derived_cache.rs`) — cache bookkeeping. +- `TargetSet`/`TargetStream` (`src/target.rs`) — preflight target model. +- `ImpactWave`/`MotifClass`/`Sequitur` grammar (`src/waves.rs`, + `src/sequitur/`) — wave analysis. +- `ErrorCode` constants (`src/catalog/jobs/mod.rs`) — failure taxonomy. +- `EventSnapshot`/`CatalogEvent`/`CaseStudy`/`CaseStudyTarget` + (`src/catalog/domain.rs`) — catalog row models. +- `ProjectScope`/`ExcludedEntity`/`ExcludedSourceRecord` + (`src/catalog/scope.rs`) — policy model. +- `FindingAudit`/`FindingChronologyAudit` (`src/catalog/workbench.rs`) — + audit tooling models. +- `AsnIdentityRegistry`/`RelationshipView` (`src/catalog/workbench.rs`, + `src/catalog/web/path_diagram.rs`) — identity/relationship labels. +- `PerfReport`/`ArchiveMetric`/`StageMetric` (`src/perf.rs`) — performance + artifacts. + +## Structures with unclear authority + +- Event title: exists in `event_snapshots.normalized_json`, manifest + target label, and report sections; the workbench prefers the latest + snapshot title + reviewed entity data (INFERRED). +- Result/assessment labels: `report.json` carries both machine verdict + names (`result.verdict`) and human labels; the API exposes stored + strings verbatim (see [invariants FR-1](invariants.md)). + +## Structures permitting invalid states + +- A Ready-status plan for an open event with no cutoff is storable via + the import path (see [invariants PR-6](invariants.md)). +- An artifact row can exist without its file and vice versa; divergence + is detected by audit but not prevented (see AR-5). +- `catalog_events.last_seen` is mutable while snapshot rows are not + (see PR-1). diff --git a/docs/design/invariants.md b/docs/design/invariants.md new file mode 100644 index 0000000..434f4ac --- /dev/null +++ b/docs/design/invariants.md @@ -0,0 +1,141 @@ +# inim — invariant register (as-built) + +Status: current normative documentation, recovered at commit `92f83d8`. +Every invariant is the result of a reconstruction + falsification pass +(see [the dated audit](../audits/2026-08-wirthian-design-recovery.md)). + +Status vocabulary: + +- **enforced** — a schema constraint, service-layer check, or test suite + prevents the violation (a single test is not enough; see the + falsification column). +- **partially enforced** — enforced on the main path but an alternate + path exists. +- **assumed** — required for correctness but not enforced anywhere. +- **claimed** — stated in documentation/comments/names but not + independently established. +- **unknown** — insufficient evidence. + +Falsification attempt column records the concrete check or counterexample +from the session-56 falsification pass. + +--- + +## Identity + +| ID | Statement | Status | Enforcement | Falsification attempt | +|----|-----------|--------|-------------|----------------------| +| ID-1 | Route identity is ADD-PATH aware: `RouteKey = (collector, peer_ip, prefix, path_id: Option)` | enforced | `RouteKey::with_path_id` used at cohort freeze (`src/cohort.rs`), lifecycle (`src/lifecycle.rs`), caches (`src/derived_cache.rs`); tests `derived_cache.rs:1025,1246,1308` | survived; no prefix-only key in reconstruction paths | +| ID-2 | Aggregate identity `ObserverPrefixKey` (no path_id) is distinct from route-instance identity | enforced | `ObserverPrefixKey` type (`src/domain/route.rs`); stream summaries aggregate at this level | survived | +| ID-3 | Catalog event identity is `(source_kind, external_id)` | enforced | `UNIQUE (source_kind, external_id)` on `catalog_events` (`src/catalog/migrations.rs`) | survived | +| ID-4 | Source snapshot identity is `(event_id, content_sha256)` | enforced | `UNIQUE (event_id, content_sha256)` on `event_snapshots` | survived | +| ID-5 | Manifest revision identity is the payload `sha256` | enforced | `sha256 TEXT NOT NULL UNIQUE` on `manifest_revisions` | survived | +| ID-6 | Plan revision identity is the canonical plan hash | enforced | `sha256 TEXT NOT NULL UNIQUE` on `analysis_plans`; `canonical_plan_hash` (`src/catalog/jobs/plan.rs`) | survived; hash covers execution fields, ignores generated timestamps/labels (tests `plan_hash_is_deterministic`, `plan_hash_normalizes_collector_order`) | +| ID-7 | Run identity is `(plan_id, started_at)` | enforced | `UNIQUE (plan_id, started_at)` on `analysis_runs` | survived | +| ID-8 | Artifact identity is `(run_id, relative_path)` | enforced | `UNIQUE (run_id, relative_path)` on `analysis_artifacts` | survived | +| ID-9 | Job identity is the job id string; retry creates a new job linked via `original_job_id` | enforced | `retry()` inserts a new row, never mutates the old (`src/catalog/jobs/service.rs`); tests `service.rs:1575,1803,2122` | survived | +| ID-10 | A route-instance withdrawal does not imply observer-prefix absence | enforced | lifecycle absence requires loss of the final instance (`src/lifecycle.rs`); tests `nonfinal_instance_loss_does_not_make_withdrawn_lifecycle`, `cohort.rs:360` | survived | + +## Provenance + +| ID | Statement | Status | Enforcement | Falsification attempt | +|----|-----------|--------|-------------|----------------------| +| PR-1 | Source snapshots are immutable | enforced (rows); partially (event row) | no UPDATE/DELETE on `event_snapshots`; `UNIQUE (event_id, content_sha256)`; inserts dedupe by sha (`src/catalog/store.rs`); test `ticket_edit_creates_new_snapshot` (`src/catalog/sync.rs`) | survived; caveat: `catalog_events.last_seen` is updated by `upsert_event` | +| PR-2 | Reviewed interpretation never alters source bytes | enforced | import writes DB rows only (`src/catalog/import.rs`); test `reviewed_interpretation_does_not_modify_source_snapshot` (`src/catalog/review.rs`) | survived | +| PR-3 | Plan hash covers all execution-relevant fields | enforced | `canonical_plan_hash` (`src/catalog/jobs/plan.rs`) + tests `plan_hash_changes_for_execution_field`, `plan_hash_ignores_generated_timestamp` | survived | +| PR-4 | Published artifact bytes match the recorded SHA-256 | partially enforced | import rejects hash mismatch (`tests/import.rs artifact_hash_mismatch_is_rejected`); run page re-verifies; not enforced on every serving path | survived (see AR-2 for resolver divergence) | +| PR-5 | Cutoff (analysis end) has reviewed provenance | partially enforced | meta sidecar carries `fetched_at_utc`; absence yields a generic fallback sentence (`src/catalog/web/view.rs`) | narrowed: `analysis_end_utc` without provenance is representable | +| PR-6 | Open events require an explicit analysis cutoff | partially enforced | manifest validation (`src/manifest.rs`); worker/queue reject (`src/catalog/jobs/plan.rs`, `src/worker.rs`) | narrowed: import path can store a Ready plan for an open event with no cutoff (`src/catalog/import.rs`) | + +## Temporal consistency + +| ID | Statement | Status | Enforcement | Falsification attempt | +|----|-----------|--------|-------------|----------------------| +| TC-1 | Event baseline is distinct from pre-finding state | enforced | both are distinct fields/labels in presentation (`EventBaseline`, `Pre-finding` in `comparison_states`, `src/catalog/web/path_diagram.rs`) | survived | +| TC-2 | Analysis-final state is not assumed to equal baseline | enforced | final state is recorded separately; test `final_path_not_assumed_baseline` (`src/catalog/web/path_diagram.rs`) | survived | +| TC-3 | Zero is distinct from not-applicable | enforced (Option types) | `Option`, `Option` throughout lifecycle; absent vs zero distinguished in renderers | survived | +| TC-4 | Run staleness never invalidates an old run | enforced | `CatalogStatus::Stale` derived, never mutates runs (`src/catalog/status.rs`) | survived | +| TC-5 | Gap/unknown continuity suppresses strong verdicts | partially enforced | continuity gate in `derive_verdict` (`src/assess.rs`) | **contradicted**: empty-transitions early return precedes the gate; gaps + zero transitions yield `NoObservableBgpImpact` not `InsufficientVisibility` (`src/assess.rs:208-224`); combination untested | + +## Project scope + +| ID | Statement | Status | Enforcement | Falsification attempt | +|----|-----------|--------|-------------|----------------------| +| SC-1 | Project-scope exclusion does not alter canonical evidence | enforced | scope only filters views/queries; exclusion never rewrites artifacts (`src/catalog/scope.rs`, `src/catalog/web/view.rs`) | survived | +| SC-2 | Excluded events are hidden from default web/API views | enforced | view-layer scope filters + `demo verify` checks excluded events absent from demo | survived (experiment: excluded count 0 in demo audit) | +| SC-3 | Scope is rechecked after claim, before source access | enforced (worker); absent on standalone analyze | worker recheck `src/worker.rs`; queue-time check `src/catalog/jobs/plan.rs` | narrowed: `inim analyze` and `orchestrate.rs` never load `ProjectScope` | +| SC-4 | An excluded plan cannot be queued | enforced | queue validates plan hash against scope (`src/main.rs`, `src/catalog/jobs/plan.rs`) | survived | +| SC-5 | Scope matching is exact-normalized | enforced | `normalize_exact` (`src/catalog/scope.rs`) | survived | + +## Route reconstruction + +| ID | Statement | Status | Enforcement | Falsification attempt | +|----|-----------|--------|-------------|----------------------| +| RR-1 | Cohort admission requires a baseline instance matching target origin AND transit predicate | enforced | `freeze_cohort` (`src/cohort.rs`) | survived | +| RR-2 | No qualifying cohort cannot produce lifecycle counts | enforced | zero retained collectors → insufficient-visibility artifact set with empty lifecycles (`src/orchestrate.rs`) | survived | +| RR-3 | Mixed keyed/unkeyed ADD-PATH encoding suppresses strong stream-level conclusions | enforced | `add_path_ambiguous` flag (`src/lifecycle.rs`) | survived | +| RR-4 | Stream absence requires loss of the final route instance | enforced | lifecycle withdrawal computation (`src/lifecycle.rs`); tests listed under ID-10 | survived | +| RR-5 | Duplicate/unchanged observations are suppressed | enforced | `TransitionKind::Duplicate` classification in `tokenize::diff` | survived | + +## Findings and results + +| ID | Statement | Status | Enforcement | Falsification attempt | +|----|-----------|--------|-------------|----------------------| +| FR-1 | Observed result vocabulary never contains expectation wording | enforced (presentation labels) | `ObservedResultKind::human_label` (`src/domain/assessment.rs`) + negative tests | narrowed: report `result.verdict` and API expose stored verdict strings that DO carry expectation vocabulary | +| FR-2 | Insufficient visibility is distinct from no-change | enforced | distinct enum variants `InsufficientQualifyingVisibility` vs `NoRouteStateChangeObserved` | survived (see TC-5 for a derivation-path overlap) | +| FR-3 | Completed job does not imply route change | enforced | job state orthogonal to outcome; test `completed_insufficient_visibility_is_not_failed_job` | survived | +| FR-4 | Target visibility is distinct from relationship visibility | enforced | `RelationshipView.observed` hardcoded false; stream matches counted separately (`src/catalog/web/view.rs`) | survived | +| FR-5 | Direct peer session is distinct from AS-in-path evidence | enforced | `DirectPeerToNamedPlane` vs `IndirectPathViaNamedPlane` (`src/catalog/workbench.rs`); `observation_kind` derived from peer ASN membership | survived | +| FR-6 | Finding selection is deterministic | enforced | `select_principal_findings` sorts; tests assert stable selection | survived | + +## Jobs and execution + +| ID | Statement | Status | Enforcement | Falsification attempt | +|----|-----------|--------|-------------|----------------------| +| JB-1 | Job state transitions are explicit and forward-only | enforced | `legal_transition` + `stage_advance` (`src/catalog/jobs/mod.rs`); test `illegal_state_transition_is_rejected` | survived | +| JB-2 | One worker claims a job at a time | enforced | transactional claim `BEGIN IMMEDIATE` (`src/catalog/jobs/service.rs`); test `two_workers_cannot_claim_same_job` | survived | +| JB-3 | Stale leases are detected, not silently resumed | enforced | `mark_stale_leases`; tests `expired_lease_is_detected_and_not_auto_resumed` | survived | +| JB-4 | Retry never mutates the failed/cancelled job | enforced | `retry()` (see ID-9) | survived | +| JB-5 | A cancelled job is immutable and retryable | enforced | `is_retryable` (`src/catalog/jobs/mod.rs`); tests | survived | +| JB-6 | Job state, execution stage, analysis outcome, observed result, and expectation assessment are separate axes | enforced | distinct enums/columns; module doc `src/catalog/jobs/mod.rs` | survived | + +## Artifacts and publication + +| ID | Statement | Status | Enforcement | Falsification attempt | +|----|-----------|--------|-------------|----------------------| +| AR-1 | Artifact listing and artifact access share one resolver | assumed | `resolve_artifact` is the documented authority (`src/catalog/artifact_path.rs`) | **contradicted**: at least four resolvers with different candidate sets/order (`artifact_path.rs`, `workbench.rs`, `demo.rs`, `publish.rs`) | +| AR-2 | Artifact paths remain inside the configured root | partially enforced | `resolve_artifact` rejects absolute/parent-relative; document serving canonical-contains (`src/catalog/web/view.rs`) | narrowed: `catalog_root.join(rel)` without validation in `src/catalog/workbench.rs` and the demo fallback | +| AR-3 | A staged run is not visible until publication | enforced | staging under `data/jobs//staging`; test `incomplete_stage_is_not_visible_as_run` (`src/catalog/jobs/publish.rs`) | survived | +| AR-4 | Publication is idempotent for the same job | enforced | test `publication_is_idempotent_for_same_job` | survived | +| AR-5 | Catalog–filesystem divergence is detectable | enforced | `reconcile_orphans` reports missing/orphaned entries (`src/catalog/jobs/publish.rs`) | survived (experiment: orphan dirs reported, not auto-deleted) | +| AR-6 | The catalog-relative path stored in the DB is the authority for locating the artifact | enforced | import rejects absolute/parent-relative paths | survived | + +## Presentation and evaluation + +| ID | Statement | Status | Enforcement | Falsification attempt | +|----|-----------|--------|-------------|----------------------| +| PV-1 | HTTP GET does not mutate logical state | enforced | all mutations are POST and gated (`src/catalog/web/mod.rs`, `src/catalog/web/job_handlers.rs`); experiment: DB hash unchanged after GET browsing | survived (with WAL sidecar caveat, see PV-2) | +| PV-2 | Read-only serving does not modify the demo database | partially enforced | row content unchanged (experiment); catalog opens read-write WAL so `-wal`/`-shm` sidecars appear | narrowed | +| PV-3 | Answer-key generation is deterministic | enforced | generator has no randomness/timestamps; CI drift check; experiment byte-identical | survived | +| PV-4 | The fabric diagram contains only reviewed attached networks | enforced | `FabricView` built only from `interconnection_context.attachments` (`src/catalog/web/view.rs`); tests `fabric_diagram_contains_no_fabric_asn`, `taxonomy_tests` | survived | +| PV-5 | Test equipment and other non-attached classes cannot enter the fabric or AS-path diagrams | enforced | separate reviewed lists; single production call site | survived | +| PV-6 | Diagrams are presentation projections, not canonical evidence | enforced | diagrams link to evidence refs; comment `src/catalog/web/path_diagram.rs` | survived | +| PV-7 | Source mention does not imply reviewed attachment | enforced | attachments only from the reviewed `attachments` array; no promotion path | survived | + +## Suppressed claims (did not survive falsification) + +- "Artifact listing and access use the same resolver" — **contradicted**, + four resolvers exist (AR-1). +- "Gap/unknown continuity always suppresses strong verdicts" — + **contradicted** for the empty-transitions path (TC-5). +- "Read-only browsing leaves the database byte-identical" — **narrowed**: + logical rows unchanged, WAL sidecars created (PV-2). + +## Invariant counts (at the pinned commit) + +- Enforced: 33 +- Partially enforced: 5 +- Assumed: 1 +- Claimed: 0 +- Unknown: 0 +- Contradicted/suppressed: 2 diff --git a/docs/design/state-machines.md b/docs/design/state-machines.md new file mode 100644 index 0000000..99bad7f --- /dev/null +++ b/docs/design/state-machines.md @@ -0,0 +1,169 @@ +# inim — state machines (as-built) + +Status: current normative documentation, recovered at commit `92f83d8`. +Each state machine is described separately; distinct axes are never +merged (analysis status `Complete` ≠ source lifecycle `Open` ≠ observed +result `InsufficientVisibility`). + +--- + +## SM1. Source event lifecycle (derived catalog status) + +Derived, never stored as a single truth field (`src/catalog/status.rs`). +States are `CatalogStatus`: `Discovered`, `NeedsReview`, `Ready`, +`Blocked`, `Running`, `Complete`, `Failed`, `Stale`. + +| From | Event | Guard | To | Effect | Enforcement | +|------|-------|-------|----|--------|-------------| +| (initial) | source record imported | — | `Discovered` | event row | import | +| `Discovered` | reviewed manifest exists | manifest present | `NeedsReview` | — | derivation | +| `NeedsReview` | manifest reviewed; plan ready | ready plan | `Ready` | — | derivation | +| `NeedsReview` | manifest reviewed; plan blocked | blocked plan | `Blocked` | blocker reason | derivation | +| `Ready` | job claimed/executing | active run | `Running` | — | derivation | +| `Ready`/`Running` | run completes | completed run for latest inputs | `Complete` | — | derivation | +| any | run fails | latest run failed | `Failed` | — | derivation | +| `Complete` | snapshot/manifest changed after run | newer inputs | `Stale` | — | derivation | + +Deterministic precedence (highest wins): Running → Failed → Stale → +Blocked → Complete → Ready → NeedsReview → Discovered. A completed +historical run stays completed; `Stale` never invalidates it. + +## SM2. Plan readiness (analysis-plan and readiness vocabulary) + +`AnalysisPlanStatus` (`src/plan.rs`): `Ready` | `Blocked { reason }` +with five `AnalysisBlockReason` variants. The catalog-level readiness +vocabulary has 14 states (`src/catalog/analyzability.rs`): +`NotReviewed`, `NeedsEntityMapping`, `NeedsTransitPredicate`, +`NeedsAnalysisWindow`, `NotApplicableToPublicBgp`, +`ReadyForArchivePlanning`, `ArchivePlanReady`, +`InsufficientBaselineVisibility`, `NotDirectlyObservableInPublicBgp`, +`NotOriginAttributable`, `AnalysisComplete`, `AnalysisStale`, +`AnalysisFailed`, `AnalysisRunning`. + +| From | Event | Guard | To | Effect | Enforcement | +|------|-------|-------|----|--------|-------------| +| (initial) | no manifest | — | `NotReviewed` | — | derivation | +| `NotReviewed` | manifest exists, no origin mapping | — | `NeedsEntityMapping` | — | derivation | +| `NeedsEntityMapping` | origin mapping reviewed, predicate unresolved | — | `NeedsTransitPredicate` | — | derivation | +| `NeedsTransitPredicate` | predicate reviewed, no window | — | `NeedsAnalysisWindow` | — | derivation | +| `NeedsAnalysisWindow` | ready plan, no archive plan | — | `ReadyForArchivePlanning` | — | derivation | +| `ReadyForArchivePlanning` | case-study archive plan stored | — | `ArchivePlanReady` | — | derivation | +| any (ready) | blocked plan | — | `Blocked`-family states | reason | derivation | +| any | reviewed applicability = not observable | — | `NotDirectlyObservableInPublicBgp` | — | derivation (authoritative over derived readiness) | +| any | active run | — | `AnalysisRunning` | — | derivation | +| any | completed run for latest inputs | — | `AnalysisComplete` | — | derivation | + +Readiness is a **projection over stored state**, not one algorithm +(distributed across `derive_status`, `derive_analyzability`, and plan +checks). + +## SM3. Analysis job lifecycle + +States (`src/catalog/jobs/mod.rs`): `Queued`, `Claimed`, +`DiscoveringArchives`, `AcquiringArchives`, `ParsingBaseline`, +`FreezingCohort`, `ParsingUpdates`, `ReconstructingRoutes`, +`DerivingEvidence`, `RenderingArtifacts`, `ValidatingArtifacts`, +`PublishingRun`, `Completed`, `CancelRequested`, `Cancelled`, `Failed`. + +| From | Event | Guard | To | Effect | Enforcement | +|------|-------|-------|----|--------|-------------| +| (initial) | queue | plan ready + in scope + no active duplicate | `Queued` | job row + event | `service::queue` | +| `Queued` | claim | lease acquired | `Claimed` | lease, worker id | `claim_next` (BEGIN IMMEDIATE) | +| `Queued` | cancel | — | `Cancelled` | terminal | `request_cancel` | +| `Queued` | scope exclusion | recheck | `Cancelled` | terminal | `cancel_scope_excluded` | +| executing stages | stage advance | forward in fixed order | next stage | progress | `transition`/`stage_advance` | +| executing stages | cancel observed | — | `CancelRequested` | cooperative stop | `observe_cancel` | +| executing stages | failure | — | `Failed` | error code + summary | `fail` | +| `CancelRequested` | cancel confirmed | — | `Cancelled` | terminal | service | +| `CancelRequested` | failure | — | `Failed` | terminal | service | +| `PublishingRun` | publication complete | validation passed | `Completed` | run linkage | `complete_job` | +| `PublishingRun` | publication failure | — | `Failed` | staging preserved | `fail_job` | + +Legal transitions are explicit: `legal_transition(from, to)` returns +false for every other pair (test `illegal_state_transition_is_rejected`). +Regression is never legal; advancement may skip intermediate stages. +Terminal states (`Completed`, `Cancelled`, `Failed`) are immutable; +retry creates a new job linked via `original_job_id`. + +Job status ≠ execution stage ≠ analysis outcome ≠ observed result ≠ +expectation assessment: a completed job may carry +`InsufficientVisibility` outcome (test +`completed_insufficient_visibility_is_not_failed_job`). + +## SM4. Worker lease + +| State | Transition | Guard | Enforcement | +|-------|-----------|-------|-------------| +| active (lease unexpired) | heartbeat renew | `heartbeat_at` within window | `renew_lease` | +| active | expiry | `lease_expires_at < now` | `mark_stale_leases` | +| stale (expired) | detected | — | audit marks stale; never auto-resumed (test) | + +Defaults: lease 90 s, heartbeat 15 s (`src/catalog/jobs/service.rs`). +Claim uses `BEGIN IMMEDIATE` so two workers cannot claim the same job +(test `two_workers_cannot_claim_same_job`). + +## SM5. Run publication + +| State | Transition | Guard | Enforcement | +|-------|-----------|-------|-------------| +| staging | write artifacts + execution metadata | job claimed | worker | +| staging | validate | required artifacts + plan hash match + schema versions | `validate_staged` | +| validated | rename staging → final immutable dir | same filesystem | `publish_staged_run` | +| final dir | import rows + complete job | report readable | `import_finalized_run` + `complete_job` | +| crash between rename and import | orphan final directory | — | detected by `reconcile_orphans`, not auto-repaired | + +A staged run is not visible as a run (test +`incomplete_stage_is_not_visible_as_run`); publication is idempotent for +the same job (test `publication_is_idempotent_for_same_job`). + +## SM6. Observer-prefix lifecycle classification + +Per-stream classification (`src/lifecycle.rs`): `StreamCategory` +(`Unchanged`, `PrependOnly`, `PathChangedStillViaTransit`, +`DepartedTransitPath`, `Withdrawn`) plus `StreamFlags` (`restored`, +`not_restored`, `multiple_cycles`, `add_path_ambiguous`). + +| Condition | Category | +|-----------|----------| +| no transitions | `Unchanged` | +| only collapsed-equivalent prepend changes | `PrependOnly` | +| material path change, still via required transit ASN | `PathChangedStillViaTransit` | +| path departed required transit ASN | `DepartedTransitPath` | +| route became absent (final instance lost) | `Withdrawn` | + +Restoration classification is per-`StreamRestoration` +(`src/lifecycle.rs`), with `add_path_ambiguous` suppressing strong +stream-level conclusions when keyed/unkeyed encoding is mixed. + +## SM7. Provisional open-event analysis + +The reviewed cutoff (`analysis_end_utc` in the manifest) defines the +analysis window for an open event. Plan lifecycle carries +`open`; the verdict carries provisionality separately +(`Verdict::ProvisionalImpactObserved` / `ProvisionalNoImpactSoFar`, +`is_provisional()`), and the run outcome's `assessment.provisional` flag +is a distinct verdict-posture flag. A stored Ready plan for an open +event with no cutoff is possible via the import path but rejected at +queue/worker time (see [invariants PR-6](invariants.md)). + +## SM8. Project-scope recheck + +| Point | Check | Enforcement | +|-------|-------|-------------| +| queue time | plan hash in scope | `src/catalog/jobs/plan.rs` | +| worker after claim, before source access | event/target excluded? | `src/worker.rs` (cancel_scope_excluded) | +| retry | excluded? | `src/catalog/jobs/service.rs` | +| standalone `analyze` | none | absent (falsification finding) | + +Scope is not access control; it is an exact-normalized reviewed policy +overlay (see [invariants SC-1..SC-5](invariants.md)). + +## Explicitly not one state machine + +- `analysis status Complete` (SM3) — job terminality. +- `source lifecycle Open` (manifest field) — ticket state at snapshot. +- `observed result InsufficientVisibility` (verdict projection) — + analysis outcome. + +These three axes are orthogonal and are kept in separate structures +(`JobState`, manifest `open`, `ObservedResultKind`).