fix(evidence): resolve final review findings

This commit is contained in:
2026-08-25 03:03:11 +02:00
parent d4818c8cc3
commit e9c65ef2db
10 changed files with 686 additions and 20 deletions
@@ -0,0 +1,175 @@
# Final-review fix report — Evidence #43–#46
Date: 2026-08-25
Base ThothII revision: `d4818c8cc33b8b11204377ff3cc65c6c9ee1425e`
Binding inputs:
- requirements: `docs/plans/2026-08-24-evidence-restructuring.md`;
- approved design: `docs/plans/2026-08-24-evidence-restructuring-design.md`;
- final review: `.superpowers/sdd/2026-08-24-evidence-restructuring/final-review.md`.
No PSD path was read or mutated during this fix wave. No issue was closed.
## Verdict
All three Important findings are fixed. Candidate evaluation remains mandatory for schema-v2
filesystem curated corpora and is absent for legacy v1, HTTP-only, and S3-only acquisition.
Reviewed formula migration now fails closed unless the caller supplies the exact original source,
the source parses back to the same formula, and every supporting excerpt is present after the
canonical mechanical normalization. Current owner-gate summaries explicitly distinguish the
immutable 225-test owner-gate observation from the expanded 230-test final-review verification and
place stale 222/224-era statements under an explicitly superseded historical section.
## RED/GREEN record
### Finding 1 — evaluator compatibility boundary
RED:
```text
cd harness
.venv/bin/pytest -q tests/test_preprocess_cli.py tests/test_evidence_formula_migration.py \
tests/test_evidence_restructuring_fixture.py \
-k 'candidate_evaluation_is_not_required or runtime_identity'
6 failed, 18 deselected, 1 warning
```
The v1 filesystem run still received a callable evaluator; v1 HTTP/S3 and v2 HTTP/S3 attempted to
resolve a filesystem evaluation root before the pipeline could run.
GREEN:
```text
6 passed, 9 deselected, 1 warning
```
`_requires_candidate_evaluation()` is now the single boundary used by both curated-corpus
validation and evaluator construction. It returns true only for `schema_version: 2` with a
filesystem source (including an explicit filesystem `source_root`). The existing integrated v2
candidate test remains the positive proof: it validates the corpus, performs all nine
candidate-generation searches while inactive, publishes only after PASS, and compensates a failed
candidate.
### Finding 2 — formula provenance
RED:
```text
cd harness
.venv/bin/pytest -q tests/test_evidence_formula_migration.py \
tests/test_evidence_restructuring_fixture.py
5 failed, 4 passed, 1 warning
```
The converter rejected the new `source_content` argument, returned clean Curated Evidence when no
original source was supplied, and the documentation consistency assertion still failed.
GREEN:
```text
cd harness
.venv/bin/pytest -q tests/test_evidence_formula_migration.py tests/test_formula.py \
tests/test_formula_wiring.py tests/test_preprocess_cli.py \
tests/test_evidence_candidate_publication.py
39 passed, 1 warning
```
The migration now:
1. returns `None` unchanged for `auto` and `draft` formulas;
2. requires original source content for a reviewed formula;
3. normalizes it with the authoring validator's `normalize_source_text()`;
4. parses it and requires equality with the supplied `ConceptFormula`;
5. requires one or more legacy provenance notes and verifies each note in normalized source text;
6. hashes the verified normalized original source, never `formula.dump()`;
7. returns `LegacyFormulaMigrationFailure(code="legacy_formula_requires_manual_review")` with
bounded problem codes for missing, invalid, mismatched, or excerpt-incomplete provenance.
The regression matrix covers exact original formatting, deterministic digest, absent original
source, absent excerpts, a YAML-folded excerpt that cannot validate literally, mismatched original
formula content, invalid full-query Formula Evidence, stable path-derived IDs, and unreviewed
session-only behavior.
### Finding 3 — current owner-gate record
RED:
```text
cd harness
.venv/bin/pytest -q tests/test_evidence_restructuring_fixture.py \
-k current_summaries
1 failed, 3 deselected, 1 warning
```
The current report did not contain the fresh expanded-suite result. Earlier RED also showed that
the leading report still lacked an authoritative read-only PSD summary and `PROJECT_STATE.md`
still said 222.
GREEN:
```text
4 passed, 1 warning
```
The task report now begins with one authoritative current record and moves all earlier scope/count
statements below `Historical record — explicitly superseded`. The manual and `PROJECT_STATE.md`
record both immutable observations without conflating them: 225 passed at the reviewed `d4818c8`
owner gate; 230 passed after five final-review regressions entered the selected acceptance files.
Both summaries describe the authorized PSD package as read-only, with 35 moveable sources plus the
retained README, narrow no-payload/no-vector observations, no secrets, and no mutation. Issue #47
still owns migration and manual acceptance.
## Requirement mapping
| Requirement | Implementation evidence | Verification |
| --- | --- | --- |
| v1 filesystem remains viable without `evaluation.yaml` or a new curated tree | `_requires_candidate_evaluation()` returns false for schema v1; `run_from_config()` passes `None` | v1 runtime identity test and parameterized filesystem case |
| HTTP/S3 preserve existing behavior | evaluator construction returns `None` for HTTP-only/S3-only configs in schema v1 and v2 | four parameterized HTTP/S3 cases |
| v2 filesystem evaluation remains mandatory | shared predicate drives validation and evaluator; no permissive missing-fixture path was added | integrated inactive-candidate publication/failure-compensation test |
| formula digest represents verified normalized original | caller supplies `source_content`; authoring normalization computes digest | literal SHA-256 assertion from independently normalized original fixture |
| formula and source cannot silently mismatch | original source is parsed and compared with the supplied model | `original_source_mismatch` regression |
| supporting excerpts are real and nonempty | notes are required and each normalized note must occur in normalized source | missing and folded/unverifiable excerpt regressions |
| unverifiable migration requires human resolution | every provenance failure returns the existing bounded manual-review result | exact code/problem assertions preserve original path and formula |
| current acceptance record is unambiguous | authoritative current sections plus explicit historical supersession | owner-gate document consistency test |
| PSD scope remains read-only | no PSD tool/path used in fix; current docs retain issue #47 authorization gate | diff review and acceptance isolation probe |
## Verification evidence
```text
focused Evidence compatibility/formula/candidate: 39 passed, 1 warning
owner-gate document contract: 4 passed, 1 warning
harness full: 1093 passed, 4 deselected, 53 warnings
harness Ruff: All checks passed!
acceptance runner: 230 passed, 1 warning; PASS
backend TypeScript: PASS
native tht build: PASS
git diff --check: PASS
```
The full backend Vitest gate remains outside this patch's modified files and reported 13 failures:
the ten previously recorded auth-runtime projection failures, the recorded Node 25 versus Node 24
contract failure, the recorded Argon2 401/429 timing assertion, plus one Windows timeout-helper
marker failure that is timing-sensitive and was not part of the prior stable 12-failure baseline.
The native Go suite with `-timeout 20s` reproduced the recorded authconfig timeout and backup/setup
failures. These unrelated failures were not repaired or hidden; backend TypeScript and native build
both pass.
## SHA-256 artifact hashes
```text
193f9e83634c17160dc5f589ee392f6cefbca2e24efa0770181500e7dce008a7 PROJECT_STATE.md
4f9cfb213fdfbab481fcad9eeb0002ab0bd69d83d7ef0c459c5fccd5f5b44048 docs/testing/evidence-restructuring-manual.md
e47862d4bf9b846f059ef133b77a6311fbeb297671843bfeda0d0445eb2e4933 harness/tht/cli/preprocess_cmd.py
4017f66dc6d3aeaa4f0294bbca581b8d447e3b60bd8daff4efff08048dc43d4b harness/tht/evidence/formula_store.py
35bd73f6d0759cf0c8cb15a9125cc97b5dda64eb02417eda9dd1a9c82ed8a558 harness/tests/test_preprocess_cli.py
a0affbf9213c41c95d5fea8e596ef37ae21836e0df9bdfa8f6adda95d9238fb6 harness/tests/test_evidence_formula_migration.py
6ed7d7679c860c6edc54cbcfc2a04db89c7d32ecab80c7daa72be96cdc14f164 harness/tests/test_formula.py
26d886074244be7cf6ca084f859dcbc0c0ade74c302077a506198a2e96bfc07e harness/tests/test_evidence_restructuring_fixture.py
62743e885230823f8942d14feadae511b6d09b5d391807a04d10a164f71dd919 .superpowers/sdd/2026-08-24-evidence-restructuring/task-12-report.md
e753674bb55d4880448a51e7fbdb3ccf506800ecc5fd3b73f59ed655cf9b63c7 tracked working-tree diff before this report
```
The final commit hash is reported with the completed handoff because a commit cannot include its
own hash without changing itself.
@@ -0,0 +1,276 @@
# Task 12 report — owner migration gate (#46)
## Current owner-gate record
This section supersedes every historical section below. The owner-gate run at `d4818c8` observed
**225 passed, 1 warning**. The final-review fix added five regressions to the selected files; its
fresh run of `bash scripts/evidence-restructuring-acceptance.sh` observed **230 passed, 1 warning**.
Both runs remained hermetic: they used only an isolated temporary authoring workspace and did not
receive or mutate a PSD path.
The authorized read-only PSD owner-gate package inspected the clean repository at immutable
commit `47516f85b4db4a67cfa8a86cea4cb2e7b98c5813`. It recorded 35 moveable source documents plus
the retained `psd-clinical/evidence/README.md` (36 current Evidence files total), and the narrow
legacy Qdrant baseline: 163 `schema_table`, 2,275 `schema_column`, 2 `memory`, and 1
`solved_question`. The replacement observations used exact filtered counts and at most three IDs
per kind with `with_payload:false` and `with_vector:false`. PSD Git status remained clean; no
secret was read and no external mutation, branch, migration, activation, commit, or push occurred.
The real Pi call, PSD migration, human Git review, authoritative pre/post vector inspection,
activation, and complete manual walkthrough remain pending in issue #47 until the owner
authorizes the migration window and exact target branch. The current package is read-only
preparation, not migration or manual acceptance.
## Historical record — explicitly superseded
Everything below preserves the sequence of earlier Task 12 runs for audit history only. Counts,
scope statements, and pending-inventory wording below are not current; the current owner-gate
record above and `docs/testing/evidence-restructuring-manual.md` are authoritative.
### Initial scope and boundary
Implemented only ThothII-local artifacts:
- `harness/tests/fixtures/evidence_authoring/poorly_structured.md` supplies prose, a
rough list, enum values, URL, ambiguity, and a PostgreSQL-expression candidate.
- `scripts/evidence-restructuring-acceptance.sh` uses a fake restructurer and an
isolated `mktemp` authoring workspace. It never receives, discovers, or accesses a
PSD path; it also asserts that the ThothII worktree status is unchanged.
- `docs/testing/evidence-restructuring-manual.md` is the owner-gate package and manual
procedure. It intentionally leaves the PSD 36-path inventory, PSD commit, and
before-counts pending authorization.
- `PROJECT_STATE.md` records only the observed hermetic result and says that issue #47
is pending.
No external PSD authoring repository was accessed, modified, staged, migrated, or
inspected. No GitHub issue was closed.
### Initial TDD record
RED:
```text
cd harness && .venv/bin/pytest -q tests/test_evidence_restructuring_fixture.py
FAILED: FileNotFoundError for fixtures/evidence_authoring/poorly_structured.md
```
GREEN:
```text
cd harness && .venv/bin/pytest -q tests/test_evidence_restructuring_fixture.py
1 passed
cd harness && .venv/bin/ruff check .
All checks passed!
```
### Initial hermetic acceptance evidence
```text
bash scripts/evidence-restructuring-acceptance.sh
PASS hermetic fake-restructurer: split, review, Git recovery/diff, no-op, dirty, upgrade, orphan
PASS isolation: only a temporary workspace was supplied; no external PSD path was read or written
222 passed, 1 warning
PASS evidence restructuring automated acceptance
```
The runner directly proves typed splitting; visible unresolved-review and orphan
validation failures; one-source manifest membership; recovery of the committed curated
baseline and a visible Git diff; unchanged no-op; dirty-state refusal; no-write
pipeline mismatch refusal and explicit all-source upgrade. Its selected suites cover
all eight typed kinds, no-tool/no-session Pi invocation, formula acceptance/rejection,
atomic semantic chunking and the 4,000-character policy, candidate evaluation and
inactive-generation failure behavior, deterministic query rendering, hybrid branch and
fused ranks, fail-closed search, and the pinned Qdrant Italian-BM25 L0 contract.
### Initial required local gates
| Gate | Result |
| --- | --- |
| `harness/.venv/bin/pytest -q` | PASS — 1080 passed, 4 deselected, 53 warnings |
| `harness/.venv/bin/ruff check .` | PASS |
| `backend/npx tsc --noEmit -p .` | PASS |
| `backend/npx vitest run` | FAIL — pre-existing failures listed below |
| `tools/tht/go build ./cmd/tht` | PASS |
| `tools/tht/go test ./...` | FAIL / timeout — pre-existing failures listed below |
| `bash scripts/evidence-restructuring-acceptance.sh` | PASS — 222 passed, 1 warning |
#### Baseline proof for unrelated failures
The immutable pre-task source `0caa747` was checked out to a temporary detached
worktree. Its targeted backend run reproduced the same 12 stable failures:
- all ten failures in `test/auth-runtime-projection.test.ts` (`authentication runtime
projection is invalid`);
- `test/health.test.ts` expects Node 24 but the host runs Node 25;
- `test/auth-routes-local.test.ts` expects the third Argon2 request to receive 429 but
receives 401.
The native host suite was run first unbounded and remained silent for more than seven
minutes, then was rerun with `go test ./... -timeout 20s`. Both the task worktree and
the detached `0caa747` baseline reproduce the same failures:
- `internal/authconfig: TestRunProjectedMutationHoldsOuterLockAcrossCanonicalAndProjection`
times out;
- `internal/backup` restore/auth publication assertions fail;
- `internal/setup` projected-server/root assertions fail.
These packages and backend files were not changed by Task 12. The suite failures are
therefore recorded as pre-existing concerns and were not repaired or hidden.
### Initial pending owner decision
Focused ThothII commit: `fc83d29b5836b4db173e0874689426ecf2da526f`
(`test(evidence): record restructuring acceptance`). Issue #46 is ready for the owner
migration authorization gate. Issue #47 remains pending: the owner must authorize the
migration window and exact PSD branch before any external repository inspection,
36-file inventory, migration, validation, activation, or manual PSD acceptance work
begins.
### Fix round 1 evidence (Task 12 review)
#### TDD
RED:
```text
cd harness && .venv/bin/pytest -q tests/test_evidence_candidate_publication.py tests/test_evidence_restructuring_fixture.py
FAILED: acceptance runner did not include test_evidence_candidate_publication.py
```
The first integrated-test draft also exposed that the real configuration refuses a
non-1024 embedding dimension; the fake was corrected to model the production contract
instead of bypassing configuration loading.
GREEN:
```text
cd harness && .venv/bin/pytest -q tests/test_evidence_candidate_publication.py tests/test_evidence_restructuring_fixture.py
3 passed, 1 warning
cd harness && .venv/bin/ruff check tests/test_evidence_candidate_publication.py tests/test_evidence_restructuring_fixture.py
All checks passed!
bash scripts/evidence-restructuring-acceptance.sh
224 passed, 1 warning
```
The new integrated test invokes the real v2 canonical-corpus validator, the real
`preprocess_cmd._candidate_evaluator`, and `CorpusPipeline`, with an observable store,
vector writer, and searcher. It records nine exact-generation branch searches (dense,
BM25, fused for lexical, semantic, and mixed queries), asserts that the candidate is
not ACTIVE during any search, publishes only after the PASS report, then proves a
failed candidate remains inactive and is compensated. It also asserts the 4,000 default,
rendered fragment bound, and rejection/absence of parallel vector-size aliases.
The acceptance probe now creates a real temporary Git repository, commits its initial
and proposed corpus, makes `evidence/curated` genuinely dirty, and calls
`prepare_workspace_evidence` with the default Git-status detection path. It restores
the temporary file before continuing; no injected `git_status` is used by the dirty
case, no-op, pipeline mismatch, or explicit upgrade checks.
#### Authorized read-only PSD preparation
The PSD repository was inspected read-only at immutable commit
`47516f85b4db4a67cfa8a86cea4cb2e7b98c5813`. `git status --porcelain` was empty before
and after, `git diff --quiet` succeeded after, and no PSD secret, worktree content
beyond the listed source names, or mutation command was used. The exact 36 paths,
concrete rollback command, and Qdrant baseline are now in
`docs/testing/evidence-restructuring-manual.md`.
Read-only Qdrant observation for `127.0.0.1:6333`, collection `psd-clinical`:
`schema_table=163`, `schema_column=2275`, `memory=2`, `solved_question=1`; the manual
records representative IDs. The standard `tht workspace vector inspect --json` command
was attempted, but its temporary maintenance container stopped before Qdrant access
because production auth configuration was unavailable. No secret was read to bypass
that guard; the owner-gate package explicitly requires repeating the contract command
immediately before authorized preprocessing.
Fresh no-write verification after the inspection:
```text
status_lines=0 diff_quiet_exit=0
head=47516f85b4db4a67cfa8a86cea4cb2e7b98c5813
```
Fresh required-gate evidence for this fix round:
```text
harness pytest: 1082 passed, 4 deselected, 53 warnings
harness ruff: PASS
acceptance runner: 224 passed, 1 warning
backend tsc: PASS
backend vitest: 12 failures, exactly reproduced at baseline 0caa747
Go build: PASS
Go test -timeout 20s: authconfig timeout plus backup/setup failures, exactly reproduced at baseline 0caa747
```
Fix-round commit: `8542124f2737354b7ae35973933fc38ca85a31c1`
(`test(evidence): harden owner gate acceptance`).
### Fix round 2 evidence (Task 12 re-review)
#### TDD
RED:
```text
cd harness && .venv/bin/pytest -q tests/test_evidence_restructuring_fixture.py
1 failed, 2 passed, 1 warning
```
The new manual-contract test failed because the package still described 36 source
documents and retained the old broad Qdrant request.
GREEN: the package now distinguishes exactly 35 moveable source documents from the
retained `psd-clinical/evidence/README.md` (36 current evidence files total). The
authorized migration instruction moves exactly the 35 listed documents to
`evidence/source/` and retains the README at its current path; the owner-only rollback
restores the immutable pre-migration SHA.
#### Narrow replacement Qdrant baseline
The prior `with_payload:true` full-collection scroll was out-of-scope and is withdrawn
as evidence. It is not a permitted fallback, and this report makes no claim that it
did not materialize payloads.
On 2026-08-25, the replacement baseline sent, for each of `schema_table`,
`schema_column`, `memory`, and `solved_question`:
```text
POST /collections/psd-clinical/points/count
{"filter":{"must":[{"key":"record_kind","match":{"value":"<kind>"}}]},"exact":true}
POST /collections/psd-clinical/points/scroll
{"filter":{"must":[{"key":"record_kind","match":{"value":"<kind>"}}]},"limit":3,"with_payload":false,"with_vector":false}
```
The resulting no-payload/no-vector observations were:
```text
schema_table=163: 01bc2535-24d6-5722-a58b-64a122b90b36,
024ddf60-c80d-5223-ac06-9247e7de7027, 086e00c8-b4a9-5063-b82d-e5a67add29cd
schema_column=2275: 00126cc1-7564-521a-a084-c2d670263258,
00365200-2c55-5bb4-86bb-87dd2d1bb529, 004304bc-b543-5b2d-8b40-f18da8e82af7
memory=2: 8d5cd772-563a-5e22-b764-2ca76cf6efca,
db74457a-3de8-5b95-9a31-d28a1ecf8141
solved_question=1: 2b6bb7d2-1a35-5f49-bd8d-b0cdcb98459a
```
No secret was read, no PSD mutation/stage/branch/commit/migration/activation/push command
was invoked, and the PSD repository remained at
`47516f85b4db4a67cfa8a86cea4cb2e7b98c5813` with empty porcelain status and a successful
`git diff --quiet` after the replacement queries. The standard `tht workspace vector
inspect --json` attempt remains blocked by missing production auth and must be repeated
successfully immediately before any owner-authorized preprocessing.
Focused correction gates:
```text
cd harness && .venv/bin/pytest -q tests/test_evidence_restructuring_fixture.py
3 passed, 1 warning
cd harness && .venv/bin/ruff check tests/test_evidence_restructuring_fixture.py
All checks passed!
bash scripts/evidence-restructuring-acceptance.sh
225 passed, 1 warning
```
Fix-round commit: `d4818c8cc33b8b11204377ff3cc65c6c9ee1425e`
(`docs(evidence): narrow owner gate baseline`).
+6 -3
View File
@@ -3,9 +3,12 @@
## Evidence restructuring owner gate (#46) — automated acceptance observed; PSD migration pending (#47) (2026-08-25) ## Evidence restructuring owner gate (#46) — automated acceptance observed; PSD migration pending (#47) (2026-08-25)
- **Observed local automation:** `bash scripts/evidence-restructuring-acceptance.sh` completed its - **Observed local automation:** `bash scripts/evidence-restructuring-acceptance.sh` completed its
isolated fake-restructurer probe and the selected Evidence/L0 contract suite: **222 passed, 1 isolated fake-restructurer probe and the selected Evidence/L0 contract suite. The owner-gate
known pytest deprecation warning**. The probe used only a `mktemp` workspace, confirmed the run at `d4818c8` observed **225 passed, 1 known pytest deprecation warning**. The final-review
ThothII worktree status was unchanged, and did not read or write an external PSD authoring path. fix added five regressions to the selected files and its fresh run observed
**230 passed, 1 known pytest deprecation warning**. Both probes used only a `mktemp` workspace,
confirmed the ThothII worktree status was unchanged, and did not receive or mutate an external
PSD authoring path. The separately authorized PSD preparation was read-only as recorded below.
- **Read-only owner-gate package:** authorized inspection of the clean PSD repository at - **Read-only owner-gate package:** authorized inspection of the clean PSD repository at
`47516f85b4db4a67cfa8a86cea4cb2e7b98c5813` recorded 35 moveable source documents plus the `47516f85b4db4a67cfa8a86cea4cb2e7b98c5813` recorded 35 moveable source documents plus the
retained `evidence/README.md` (36 current evidence files total), and the legacy `psd-clinical` retained `evidence/README.md` (36 current evidence files total), and the legacy `psd-clinical`
@@ -5,6 +5,11 @@ occur only after the owner authorizes a migration window and exact PSD target br
The automated runner is hermetic: it uses a fake restructurer and temporary inputs. The automated runner is hermetic: it uses a fake restructurer and temporary inputs.
The owner-gate inventory below is a separately authorized, read-only PSD snapshot; The owner-gate inventory below is a separately authorized, read-only PSD snapshot;
it did not write, stage, branch, commit, migrate, activate, push, or read secrets. it did not write, stage, branch, commit, migrate, activate, push, or read secrets.
The owner-gate run at `d4818c8` observed **225 passed, 1 known pytest deprecation warning**.
The final-review fix added five regressions to the selected files and its fresh hermetic run
observed **230 passed, 1 known pytest deprecation warning**. This read-only package and those
immutable results are the current issue #46 record; the authorized migration and manual
acceptance remain pending in issue #47.
## Recorded automated boundary ## Recorded automated boundary
@@ -22,6 +27,9 @@ formula, chunking, candidate-evaluation, hybrid-query/fail-closed, and pinned-Qd
L0 suites. The runner supplies only a `mktemp` workspace and asserts the ThothII L0 suites. The runner supplies only a `mktemp` workspace and asserts the ThothII
worktree is unchanged; consequently it performs no external PSD write. worktree is unchanged; consequently it performs no external PSD write.
Recorded owner-gate output: `225 passed, 1 warning`. Fresh final-review fix output:
`230 passed, 1 warning`. Both were followed by `PASS evidence restructuring automated acceptance`.
The real Pi invocation is deliberately not automated here. A reviewer must run it once The real Pi invocation is deliberately not automated here. A reviewer must run it once
per changed source after the authorization gate and examine every proposed curated file. per changed source after the authorization gate and examine every proposed curated file.
@@ -1,6 +1,10 @@
"""Migration boundary: legacy formulas become curated evidence or session proposals.""" """Migration boundary: legacy formulas become curated evidence or session proposals."""
import hashlib
from tht.evidence import formula_store from tht.evidence import formula_store
from tht.evidence.authoring import normalize_source_text
from tht.evidence.canonical import CuratedEvidence
from tht.evidence.formula_store import ConceptFormula from tht.evidence.formula_store import ConceptFormula
@@ -13,17 +17,124 @@ def test_reviewed_formula_migration_has_deterministic_provenance_hash():
sources=["Regola clinica approvata dal gruppo pediatrico."], sources=["Regola clinica approvata dal gruppo pediatrico."],
) )
original_source = """---
concept: fascia pediatrica
columns: [clinical.patient.birth_date]
status: reviewed
sources:
- Regola clinica approvata dal gruppo pediatrico.
---
CASE WHEN age < 18 THEN 'pediatrica' ELSE 'adulta' END
"""
first = formula_store.legacy_formula_to_curated( first = formula_store.legacy_formula_to_curated(
formula, legacy_path="formulas/fascia-pediatrica-1.sql.md", formula,
legacy_path="formulas/fascia-pediatrica-1.sql.md",
source_content=original_source,
) )
second = formula_store.legacy_formula_to_curated( second = formula_store.legacy_formula_to_curated(
formula,
legacy_path="formulas/fascia-pediatrica-1.sql.md",
source_content=original_source,
)
assert isinstance(first, CuratedEvidence)
assert isinstance(second, CuratedEvidence)
assert first.provenance.source_sha256 == second.provenance.source_sha256
expected = hashlib.sha256(normalize_source_text(original_source).encode("utf-8")).hexdigest()
assert first.provenance.source_sha256 == f"sha256:{expected}"
def test_reviewed_formula_without_original_source_fails_closed():
formula = ConceptFormula(
concept="fascia pediatrica",
columns=["clinical.patient.birth_date"],
sql="CASE WHEN age < 18 THEN 'pediatrica' ELSE 'adulta' END",
status="reviewed",
sources=["Regola clinica approvata dal gruppo pediatrico."],
)
outcome = formula_store.legacy_formula_to_curated(
formula, legacy_path="formulas/fascia-pediatrica-1.sql.md", formula, legacy_path="formulas/fascia-pediatrica-1.sql.md",
) )
assert first is not None assert outcome.code == "legacy_formula_requires_manual_review"
assert second is not None assert outcome.problems == ("original_source_required",)
assert first.provenance.source_sha256 == second.provenance.source_sha256
assert first.provenance.source_sha256.startswith("sha256:")
def test_reviewed_formula_without_verified_supporting_excerpts_fails_closed():
formula = ConceptFormula(
concept="fascia pediatrica",
columns=["clinical.patient.birth_date"],
sql="CASE WHEN age < 18 THEN 'pediatrica' ELSE 'adulta' END",
status="reviewed",
sources=["Nota non presente nel sorgente originale."],
)
original_source = """---
concept: fascia pediatrica
columns: [clinical.patient.birth_date]
status: reviewed
sources:
- >-
Nota non presente nel
sorgente originale.
---
CASE WHEN age < 18 THEN 'pediatrica' ELSE 'adulta' END
"""
outcome = formula_store.legacy_formula_to_curated(
formula,
legacy_path="formulas/fascia-pediatrica-1.sql.md",
source_content=original_source,
)
assert outcome.code == "legacy_formula_requires_manual_review"
assert outcome.problems == ("supporting_excerpt_unverified",)
def test_reviewed_formula_without_provenance_notes_fails_closed():
formula = ConceptFormula(
concept="fascia pediatrica",
columns=["clinical.patient.birth_date"],
sql="CASE WHEN age < 18 THEN 'pediatrica' ELSE 'adulta' END",
status="reviewed",
sources=[],
)
outcome = formula_store.legacy_formula_to_curated(
formula,
legacy_path="formulas/fascia-pediatrica-1.sql.md",
source_content=formula.dump(),
)
assert outcome.code == "legacy_formula_requires_manual_review"
assert outcome.problems == ("supporting_excerpts_required",)
def test_reviewed_formula_must_match_the_original_source_record():
formula = ConceptFormula(
concept="fascia pediatrica",
columns=["clinical.patient.birth_date"],
sql="CASE WHEN age < 18 THEN 'pediatrica' ELSE 'adulta' END",
status="reviewed",
sources=["Regola clinica approvata dal gruppo pediatrico."],
)
different_source = ConceptFormula(
concept=formula.concept,
columns=formula.columns,
sql="CASE WHEN age < 16 THEN 'pediatrica' ELSE 'adulta' END",
status=formula.status,
sources=formula.sources,
).dump()
outcome = formula_store.legacy_formula_to_curated(
formula,
legacy_path="formulas/fascia-pediatrica-1.sql.md",
source_content=different_source,
)
assert outcome.code == "legacy_formula_requires_manual_review"
assert outcome.problems == ("original_source_mismatch",)
def test_incompatible_reviewed_legacy_formula_fails_closed_with_the_original_record(): def test_incompatible_reviewed_legacy_formula_fails_closed_with_the_original_record():
@@ -38,3 +38,22 @@ def test_owner_gate_manual_records_the_narrow_inventory_and_qdrant_baseline_prot
assert "with_payload:false" in text assert "with_payload:false" in text
assert "with_payload:true" not in text assert "with_payload:true" not in text
assert "must be repeated through the successful `vector inspect` command" in normalized assert "must be repeated through the successful `vector inspect` command" in normalized
def test_owner_gate_current_summaries_supersede_stale_pre_inspection_history():
root = Path(__file__).parents[2]
report = (
root / ".superpowers" / "sdd" / "2026-08-24-evidence-restructuring"
/ "task-12-report.md"
).read_text(encoding="utf-8")
state = (root / "PROJECT_STATE.md").read_text(encoding="utf-8")
current_report = report.split("## Historical record", 1)[0]
current_state = state.split("## Modular workflow refactor candidate", 1)[0]
assert "225 passed, 1 warning" in current_report
assert "230 passed, 1 warning" in current_report
assert "authorized read-only PSD" in current_report
assert "35 moveable source documents" in current_report
assert "**225 passed, 1 known pytest deprecation warning**" in current_state
assert "**230 passed, 1 known pytest deprecation warning**" in current_state
+14 -4
View File
@@ -100,7 +100,9 @@ def test_reviewed_legacy_formula_becomes_curated_formula_with_stable_provenance(
) )
migrated = formula_store.legacy_formula_to_curated( migrated = formula_store.legacy_formula_to_curated(
formula, legacy_path="formulas/fascia-pediatrica-1.sql.md", formula,
legacy_path="formulas/fascia-pediatrica-1.sql.md",
source_content=formula.dump(),
) )
assert migrated is not None assert migrated is not None
@@ -114,7 +116,9 @@ def test_reviewed_legacy_formula_becomes_curated_formula_with_stable_provenance(
assert migrated.provenance.supporting_excerpts == tuple(formula.sources) assert migrated.provenance.supporting_excerpts == tuple(formula.sources)
assert migrated.review_items == () assert migrated.review_items == ()
assert formula_store.legacy_formula_to_curated( assert formula_store.legacy_formula_to_curated(
formula, legacy_path="formulas/fascia-pediatrica-1.sql.md", formula,
legacy_path="formulas/fascia-pediatrica-1.sql.md",
source_content=formula.dump(),
).id == migrated.id ).id == migrated.id
@@ -124,19 +128,25 @@ def test_reviewed_legacy_formulas_with_the_same_concept_keep_distinct_path_ident
columns=["clinical.patient.birth_date"], columns=["clinical.patient.birth_date"],
sql="CASE WHEN age < 18 THEN 'pediatrica' ELSE 'adulta' END", sql="CASE WHEN age < 18 THEN 'pediatrica' ELSE 'adulta' END",
status="reviewed", status="reviewed",
sources=["Regola legacy revisionata."],
) )
second = ConceptFormula( second = ConceptFormula(
concept="fascia pediatrica", concept="fascia pediatrica",
columns=["clinical.patient.birth_date"], columns=["clinical.patient.birth_date"],
sql="CASE WHEN age < 16 THEN 'pediatrica' ELSE 'adulta' END", sql="CASE WHEN age < 16 THEN 'pediatrica' ELSE 'adulta' END",
status="reviewed", status="reviewed",
sources=["Regola legacy revisionata."],
) )
first_migration = formula_store.legacy_formula_to_curated( first_migration = formula_store.legacy_formula_to_curated(
first, legacy_path="formulas/fascia-pediatrica-1.sql.md", first,
legacy_path="formulas/fascia-pediatrica-1.sql.md",
source_content=first.dump(),
) )
second_migration = formula_store.legacy_formula_to_curated( second_migration = formula_store.legacy_formula_to_curated(
second, legacy_path="formulas/fascia-pediatrica-2.sql.md", second,
legacy_path="formulas/fascia-pediatrica-2.sql.md",
source_content=second.dump(),
) )
assert first_migration is not None assert first_migration is not None
+25 -1
View File
@@ -315,11 +315,35 @@ def test_run_from_config_uses_runtime_identity_workspace_id(monkeypatch, tmp_pat
command.run_from_config(config) command.run_from_config(config)
assert calls["init"]["sparse_language"] == "english" assert calls["init"]["sparse_language"] == "english"
assert callable(calls["init"]["candidate_evaluator"]) assert calls["init"]["candidate_evaluator"] is None
assert calls["run_as_job"]["workspace_id"] == "psd-clinical" assert calls["run_as_job"]["workspace_id"] == "psd-clinical"
assert calls["run_as_job"]["input_fingerprint"] != calls["run_as_job"]["config_fingerprint"] assert calls["run_as_job"]["input_fingerprint"] != calls["run_as_job"]["config_fingerprint"]
@pytest.mark.parametrize("schema_version, source_type", [
(1, "filesystem"),
(1, "http"),
(1, "s3"),
(2, "http"),
(2, "s3"),
])
def test_candidate_evaluation_is_not_required_outside_v2_filesystem_corpora(
schema_version, source_type,
):
import tht.cli.preprocess_cmd as command
cfg = SimpleNamespace(
evidence=SimpleNamespace(
schema_version=schema_version,
source_root=None,
sources=[SimpleNamespace(type=source_type)],
),
language="it",
)
assert command._candidate_evaluator(cfg, vector_store=object(), embedder=object()) is None
def test_preprocess_evidence_gc_json_is_pristine(monkeypatch, tmp_path): def test_preprocess_evidence_gc_json_is_pristine(monkeypatch, tmp_path):
import tht.cli.preprocess_cmd as command import tht.cli.preprocess_cmd as command
+12 -3
View File
@@ -45,8 +45,19 @@ def _evaluation_workspace_root(cfg) -> Path:
return root return root
def _requires_candidate_evaluation(cfg) -> bool:
evidence = cfg.evidence
if evidence is None or evidence.schema_version != 2:
return False
return evidence.source_root is not None or any(
source.type == "filesystem" for source in evidence.sources
)
def _candidate_evaluator(cfg, *, vector_store, embedder): def _candidate_evaluator(cfg, *, vector_store, embedder):
"""Bind candidate publication to the same read-only retrieval evaluator as the CLI.""" """Bind candidate publication to the same read-only retrieval evaluator as the CLI."""
if not _requires_candidate_evaluation(cfg):
return None
from tht.evidence.canonical import load_curated_tree from tht.evidence.canonical import load_curated_tree
from tht.evidence.evaluation import evaluate_retrieval, load_evaluation_fixture from tht.evidence.evaluation import evaluate_retrieval, load_evaluation_fixture
@@ -77,9 +88,7 @@ def _candidate_evaluator(cfg, *, vector_store, embedder):
def _validate_materialized_curated_corpus(cfg) -> None: def _validate_materialized_curated_corpus(cfg) -> None:
"""Fail closed on a v2 pinned filesystem corpus before any vector write is possible.""" """Fail closed on a v2 pinned filesystem corpus before any vector write is possible."""
if cfg.evidence is None or cfg.evidence.schema_version != 2: if not _requires_candidate_evaluation(cfg):
return
if not any(source.type == "filesystem" for source in cfg.evidence.sources):
return return
from tht.evidence import validate_workspace_evidence from tht.evidence import validate_workspace_evidence
+35 -4
View File
@@ -8,6 +8,7 @@ from __future__ import annotations
import hashlib import hashlib
import re import re
import unicodedata
from dataclasses import dataclass from dataclasses import dataclass
from pathlib import Path from pathlib import Path
from typing import Literal from typing import Literal
@@ -121,6 +122,7 @@ def legacy_formula_to_curated(
formula: ConceptFormula, formula: ConceptFormula,
*, *,
legacy_path: str, legacy_path: str,
source_content: str | None = None,
) -> CuratedEvidence | LegacyFormulaMigrationFailure | None: ) -> CuratedEvidence | LegacyFormulaMigrationFailure | None:
"""Convert one reviewed legacy formula into its deterministic curated counterpart. """Convert one reviewed legacy formula into its deterministic curated counterpart.
@@ -129,10 +131,39 @@ def legacy_formula_to_curated(
""" """
if formula.status != "reviewed": if formula.status != "reviewed":
return None return None
source_notes = tuple(formula.sources) or ( problems: list[str] = []
"Legacy formula migrated without a recorded provenance note.", normalized_source: str | None = None
) if source_content is None:
source_sha256 = hashlib.sha256(formula.dump().encode("utf-8")).hexdigest() problems.append("original_source_required")
else:
from tht.evidence.authoring import normalize_source_text
normalized_source = normalize_source_text(source_content)
try:
original_formula = ConceptFormula.parse(source_content)
except (TypeError, ValidationError, ValueError, yaml.YAMLError):
problems.append("original_source_invalid")
else:
if original_formula != formula:
problems.append("original_source_mismatch")
source_notes = tuple(formula.sources)
if not source_notes:
problems.append("supporting_excerpts_required")
elif normalized_source is not None and any(
unicodedata.normalize("NFC", note.replace("\r\n", "\n").replace("\r", "\n"))
not in normalized_source
for note in source_notes
):
problems.append("supporting_excerpt_unverified")
if problems:
return LegacyFormulaMigrationFailure(
code="legacy_formula_requires_manual_review",
legacy_path=legacy_path,
formula=formula,
problems=tuple(sorted(problems)),
)
assert normalized_source is not None
source_sha256 = hashlib.sha256(normalized_source.encode("utf-8")).hexdigest()
source_file = legacy_path if legacy_path.startswith("source/") else f"source/{legacy_path}" source_file = legacy_path if legacy_path.startswith("source/") else f"source/{legacy_path}"
# The legacy path is the immutable identity of this unit during migration. Keeping # The legacy path is the immutable identity of this unit during migration. Keeping
# its full digest avoids a duplicate public ID when the same concept has reviewed # its full digest avoids a duplicate public ID when the same concept has reviewed