Audit source-derived KB rebuild coverage
This commit is contained in:
parent
279962fe8a
commit
ed6b4b8b69
4 changed files with 1396 additions and 0 deletions
|
|
@ -65,6 +65,18 @@ The simple retained-row joins currently reproduce:
|
|||
- 4,878 of 4,916 canonical edge rows can be accounted for by a staged relation;
|
||||
historical duplicate multiplicity still needs an explicit replay rule.
|
||||
|
||||
Re-run this aggregate, read-only coverage audit against any restored local
|
||||
clone:
|
||||
|
||||
```bash
|
||||
.venv/bin/python ops/audit_kb_rebuild_coverage.py \
|
||||
--container <restored-local-postgres-container> \
|
||||
--database teleo
|
||||
```
|
||||
|
||||
The auditor emits no claim bodies or source excerpts and separates snapshot
|
||||
recovery from source-derived recompilation readiness.
|
||||
|
||||
The remaining gaps are concrete rather than mysterious:
|
||||
|
||||
- 30 claims were created after or outside the mapped import;
|
||||
|
|
|
|||
|
|
@ -0,0 +1,71 @@
|
|||
# KB Source Recompile Coverage
|
||||
|
||||
Generated UTC: `2026-07-13T08:51:50.309130+00:00`
|
||||
|
||||
Status: **partial**
|
||||
|
||||
This audit ran inside a repeatable-read, read-only transaction against a fresh
|
||||
restored canonical database. It emitted aggregate counts only: no claim bodies,
|
||||
source excerpts, credentials, or database writes. The disposable container was
|
||||
removed and a later Docker inspection confirmed it was absent.
|
||||
|
||||
## What Can Be Rebuilt From Retained Import State
|
||||
|
||||
| Surface | Covered | Canonical | Coverage |
|
||||
|---|---:|---:|---:|
|
||||
| Claims with canonical mappings | 1,807 | 1,837 | 98.37% |
|
||||
| Sources with canonical mappings | 4,145 | 4,145 | 100.00% |
|
||||
| Eligible mapped claim field projection | 1,807 | 1,807 | 100.00% |
|
||||
| Eligible staged source field projection | 3,861 | 3,861 | 100.00% |
|
||||
| Evidence links from staged claim-source rows | 4,254 | 4,670 | 91.09% |
|
||||
| Edge rows accounted for by resolved staged relations | 4,878 | 4,916 | 99.23% |
|
||||
| Applied proposals with replayable apply payload | 1 | 2 | 50.00% |
|
||||
|
||||
The claim projection compares type, title-to-text, status, confidence, tags,
|
||||
creator, and applicable legacy creation time. The source projection compares
|
||||
type, URL, storage path, excerpt, hash, capture time, and creator. Relation
|
||||
coverage does not by itself reconstruct row IDs, timestamps, creators, or
|
||||
historical duplicate multiplicity.
|
||||
|
||||
## Retained Import State
|
||||
|
||||
- Import runs: `2`
|
||||
- Inventory items: `11,624` (`5,812` per run)
|
||||
- Staged claims: `2,277`
|
||||
- Staged sources: `4,715`
|
||||
- Staged claim-source rows: `5,195`
|
||||
- Staged claim-edge rows: `6,593`
|
||||
- Canonical mappings: `5,952`
|
||||
|
||||
Both imports point to the same retained 5,812-file Forgejo-era workspace
|
||||
inventory. The second run reflects the corrected classifier mapping.
|
||||
|
||||
## Exact Remaining Gaps
|
||||
|
||||
- `30` canonical claims have no canonical mapping.
|
||||
- `284` source mappings do not resolve to a staged source in their recorded
|
||||
first import run.
|
||||
- `66` distinct staged claim keys and `85` staged source keys have no canonical
|
||||
mapping.
|
||||
- `155` staged claim-source rows cannot resolve both mapped endpoints.
|
||||
- `416` canonical evidence links have no exact staged relation projection.
|
||||
- `700` staged edges are not resolved; another `152` resolved staged edges
|
||||
cannot resolve both mapped endpoints.
|
||||
- `38` canonical edge rows have no matching resolved staged relation.
|
||||
- `1` applied proposal has no non-empty replayable `apply_payload`.
|
||||
|
||||
## Command
|
||||
|
||||
```bash
|
||||
.venv/bin/python ops/audit_kb_rebuild_coverage.py \
|
||||
--container <restored-local-postgres-container> \
|
||||
--database teleo
|
||||
```
|
||||
|
||||
## Interpretation
|
||||
|
||||
Fast exact snapshot recovery is complete and separately proven. Source-derived
|
||||
recompilation is close for the original mapped claim/source graph, but it is not
|
||||
yet a complete blank-database rebuild. The practical next step is to preserve
|
||||
the current snapshot as genesis, backfill the listed gaps, and require every
|
||||
future accepted change to carry a replayable apply payload and row receipt.
|
||||
1069
ops/audit_kb_rebuild_coverage.py
Normal file
1069
ops/audit_kb_rebuild_coverage.py
Normal file
File diff suppressed because it is too large
Load diff
244
tests/test_audit_kb_rebuild_coverage.py
Normal file
244
tests/test_audit_kb_rebuild_coverage.py
Normal file
|
|
@ -0,0 +1,244 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import re
|
||||
from collections.abc import Sequence
|
||||
from typing import Any
|
||||
|
||||
import pytest
|
||||
|
||||
from ops.audit_kb_rebuild_coverage import (
|
||||
CommandResult,
|
||||
audit_database,
|
||||
build_audit_sql,
|
||||
build_report,
|
||||
)
|
||||
|
||||
|
||||
def aggregate_fixture() -> dict[str, Any]:
|
||||
return {
|
||||
"identity": {"database": "teleo", "transaction_read_only": "on", "server_version_num": "160004"},
|
||||
"canonical_counts": {"claims": 10, "sources": 8, "claim_evidence": 12, "claim_edges": 9},
|
||||
"staging_counts": {
|
||||
"canonical_mappings": 17,
|
||||
"import_runs": 1,
|
||||
"inventory_items": 20,
|
||||
"staged_claims": 11,
|
||||
"staged_sources": 8,
|
||||
"staged_claim_sources": 13,
|
||||
"staged_claim_edges": 10,
|
||||
"kb_proposals": 4,
|
||||
},
|
||||
"mappings": {
|
||||
"claims": {
|
||||
"rows": 9,
|
||||
"legacy_keys": 9,
|
||||
"target_ids": 9,
|
||||
"missing_canonical_targets": 0,
|
||||
"with_first_run_staged_row": 9,
|
||||
"canonical_target_rows_covered": 9,
|
||||
"canonical_target_rows_unmapped": 1,
|
||||
"staged_distinct_keys": 10,
|
||||
"staged_distinct_keys_unmapped": 1,
|
||||
},
|
||||
"sources": {
|
||||
"rows": 8,
|
||||
"legacy_keys": 8,
|
||||
"target_ids": 8,
|
||||
"missing_canonical_targets": 0,
|
||||
"with_first_run_staged_row": 7,
|
||||
"canonical_target_rows_covered": 8,
|
||||
"canonical_target_rows_unmapped": 0,
|
||||
"staged_distinct_keys": 8,
|
||||
"staged_distinct_keys_unmapped": 0,
|
||||
},
|
||||
"unsupported_target_rows": 0,
|
||||
},
|
||||
"claim_parity": {
|
||||
"compared_fields": ["type", "text_from_title"],
|
||||
"eligible_rows": 9,
|
||||
"exact_projection_rows": 9,
|
||||
"field_matches": {"type": 9, "text_from_title": 9},
|
||||
"projection_limits": {"staged_body_nonempty": 9},
|
||||
},
|
||||
"source_parity": {
|
||||
"compared_fields": ["source_type", "hash"],
|
||||
"eligible_rows": 7,
|
||||
"exact_projection_rows": 6,
|
||||
"field_matches": {"source_type": 7, "hash": 6},
|
||||
"projection_limits": {"staged_title_nonempty": 7},
|
||||
},
|
||||
"claim_evidence_reconstruction": {
|
||||
"compared_fields": ["claim_id", "source_id", "role", "weight"],
|
||||
"not_replayed_fields": ["created_by", "created_at"],
|
||||
"staged_rows": 13,
|
||||
"rows_with_mapped_claim": 12,
|
||||
"rows_with_mapped_source": 13,
|
||||
"rows_with_both_mapped_endpoints": 12,
|
||||
"distinct_projected_rows": 11,
|
||||
"projected_rows_present_in_canonical": 11,
|
||||
"canonical_rows_covered": 11,
|
||||
"canonical_rows_total": 12,
|
||||
},
|
||||
"claim_edge_reconstruction": {
|
||||
"compared_fields": ["from_claim", "to_claim", "edge_type"],
|
||||
"not_replayed_fields": ["id", "weight", "created_by", "created_at", "duplicate_multiplicity"],
|
||||
"staged_rows": 10,
|
||||
"resolution_state_resolved_rows": 9,
|
||||
"resolved_rows_with_from_mapping": 9,
|
||||
"resolved_rows_with_to_mapping": 8,
|
||||
"resolved_rows_with_both_mapped_endpoints": 8,
|
||||
"distinct_projected_rows": 8,
|
||||
"projected_rows_present_in_canonical": 8,
|
||||
"canonical_rows_covered": 8,
|
||||
"canonical_rows_total": 9,
|
||||
},
|
||||
"later_and_unmapped": {
|
||||
"latest_import_completed_at": "2026-01-01T00:00:00+00:00",
|
||||
"canonical_created_after_latest_import": {
|
||||
"claims": 1,
|
||||
"sources": 0,
|
||||
"claim_evidence": 1,
|
||||
"claim_edges": 1,
|
||||
},
|
||||
"canonical_without_mapping": {"claims": 1, "sources": 0},
|
||||
"canonical_relations_not_reconstructible": {"claim_evidence": 1, "claim_edges": 1},
|
||||
"staged_distinct_keys_without_mapping": {"claims": 1, "sources": 0},
|
||||
},
|
||||
"proposal_apply_payload": {
|
||||
"total_rows": 4,
|
||||
"status_counts": {"applied": 2, "pending_review": 2},
|
||||
"with_apply_payload_key": 2,
|
||||
"with_valid_apply_payload": 2,
|
||||
"applied_rows": 2,
|
||||
"applied_with_valid_apply_payload": 1,
|
||||
},
|
||||
"import_runs": [
|
||||
{
|
||||
"id": "run-id",
|
||||
"label": "fixture-run",
|
||||
"repo_path": "/workspace",
|
||||
"inventory_path": "/inventory/fixture.jsonl",
|
||||
"started_at": "2026-01-01T00:00:00+00:00",
|
||||
"completed_at": "2026-01-01T00:01:00+00:00",
|
||||
"inventory_items": 20,
|
||||
"staged_claims": 11,
|
||||
"staged_sources": 8,
|
||||
"staged_claim_sources": 13,
|
||||
"staged_claim_edges": 10,
|
||||
}
|
||||
],
|
||||
}
|
||||
|
||||
|
||||
def test_sql_is_guarded_and_read_only() -> None:
|
||||
sql = build_audit_sql()
|
||||
|
||||
assert "BEGIN TRANSACTION ISOLATION LEVEL REPEATABLE READ READ ONLY" in sql
|
||||
assert "current_setting('transaction_read_only')" in sql
|
||||
assert "expected_schema_absent" in sql
|
||||
assert "\\if :schema_ok" in sql
|
||||
assert sql.count("ROLLBACK;") >= 2
|
||||
assert not re.search(r"\b(INSERT|UPDATE|DELETE|CREATE|ALTER|DROP|TRUNCATE|COPY)\b", sql, re.IGNORECASE)
|
||||
|
||||
|
||||
def test_report_separates_snapshot_recovery_from_source_recompile_and_calculates_gaps() -> None:
|
||||
report = build_report(aggregate_fixture(), container="teleo-test", database="teleo")
|
||||
semantic = report["source_derived_recompile_readiness"]
|
||||
|
||||
assert report["audit_status"] == "complete"
|
||||
assert report["exact_snapshot_recovery"]["verified_by_this_database_only_auditor"] is False
|
||||
assert report["exact_snapshot_recovery"]["current_state_counts"]["canonical"]["claims"] == 10
|
||||
assert semantic["status"] == "partial"
|
||||
assert semantic["canonical_mappings"]["claims"]["canonical_coverage"] == {
|
||||
"covered": 9,
|
||||
"total": 10,
|
||||
"gap": 1,
|
||||
"percent": 90.0,
|
||||
}
|
||||
assert semantic["canonical_mappings"]["sources"]["first_run_stage_origin_coverage"]["percent"] == 87.5
|
||||
assert semantic["claim_evidence_from_staged_claim_sources"]["canonical_reconstruction_coverage"]["percent"] == 91.67
|
||||
assert semantic["claim_evidence_from_staged_claim_sources"]["not_replayed_fields"] == [
|
||||
"created_by",
|
||||
"created_at",
|
||||
]
|
||||
assert "duplicate_multiplicity" in semantic["claim_edges_from_resolved_staged_claim_edges"]["not_replayed_fields"]
|
||||
assert semantic["proposal_apply_payload_coverage"]["applied_proposal_payload_coverage"]["percent"] == 50.0
|
||||
gap_codes = {gap["code"] for gap in semantic["explicit_gaps"]}
|
||||
assert "canonical_claims_without_mapping" in gap_codes
|
||||
assert "source_mappings_without_first_run_stage" in gap_codes
|
||||
assert "mapped_source_projection_mismatch" in gap_codes
|
||||
assert "canonical_claim_evidence_not_reconstructible" in gap_codes
|
||||
assert "canonical_claim_edges_not_reconstructible" in gap_codes
|
||||
assert "applied_proposals_without_valid_apply_payload" in gap_codes
|
||||
assert semantic["import_run_metadata"][0]["inventory_path"] == "/inventory/fixture.jsonl"
|
||||
assert report["safety"]["contains_private_bodies_or_source_excerpts"] is False
|
||||
|
||||
|
||||
def test_audit_uses_named_container_database_and_injected_runner() -> None:
|
||||
captured: dict[str, Any] = {}
|
||||
|
||||
def fake_runner(command: Sequence[str], stdin: str, timeout: int) -> CommandResult:
|
||||
captured.update(command=list(command), stdin=stdin, timeout=timeout)
|
||||
return CommandResult(0, json.dumps(aggregate_fixture()) + "\n", "")
|
||||
|
||||
report = audit_database(
|
||||
container="teleo-local",
|
||||
database="teleo",
|
||||
db_user="postgres",
|
||||
timeout=45,
|
||||
runner=fake_runner,
|
||||
)
|
||||
|
||||
assert captured["command"] == [
|
||||
"docker",
|
||||
"exec",
|
||||
"-i",
|
||||
"teleo-local",
|
||||
"psql",
|
||||
"-X",
|
||||
"-U",
|
||||
"postgres",
|
||||
"-d",
|
||||
"teleo",
|
||||
"-Atq",
|
||||
"-v",
|
||||
"ON_ERROR_STOP=1",
|
||||
]
|
||||
assert captured["timeout"] == 45
|
||||
assert "READ ONLY" in captured["stdin"]
|
||||
assert report["target"]["container"] == "teleo-local"
|
||||
assert report["safety"]["transaction_read_only_readback"] == "on"
|
||||
|
||||
|
||||
def test_missing_schema_fails_closed_with_structured_json() -> None:
|
||||
def fake_runner(_command: Sequence[str], _stdin: str, _timeout: int) -> CommandResult:
|
||||
payload = {
|
||||
"status": "error",
|
||||
"error": "expected_schema_absent",
|
||||
"missing_relations": ["kb_stage.canonical_mappings"],
|
||||
"missing_columns": ["kb_stage.canonical_mappings.legacy_key"],
|
||||
}
|
||||
return CommandResult(3, json.dumps(payload) + "\n", "")
|
||||
|
||||
report = audit_database(container="empty-postgres", database="teleo", runner=fake_runner)
|
||||
|
||||
assert report["audit_status"] == "error"
|
||||
assert report["error"] == "expected_schema_absent"
|
||||
assert report["details"]["missing_relations"] == ["kb_stage.canonical_mappings"]
|
||||
assert report["details"]["missing_columns"] == ["kb_stage.canonical_mappings.legacy_key"]
|
||||
assert "source_derived_recompile_readiness" not in report
|
||||
|
||||
|
||||
def test_invalid_docker_identifier_is_rejected_before_runner() -> None:
|
||||
called = False
|
||||
|
||||
def fake_runner(_command: Sequence[str], _stdin: str, _timeout: int) -> CommandResult:
|
||||
nonlocal called
|
||||
called = True
|
||||
return CommandResult(0, "{}", "")
|
||||
|
||||
with pytest.raises(ValueError, match="container must match"):
|
||||
audit_database(container="--privileged", database="teleo", runner=fake_runner)
|
||||
|
||||
assert called is False
|
||||
Loading…
Reference in a new issue