Wave 47 Phase 2 tick#6 — Dim F fact_signature storage extension (mig 285)¶
Date: 2026-05-12
Branch: feat/jpcite_2026_05_12_wave47_dim_f_migration
Worktree: /tmp/jpcite-w47-dim-f-mig
Lane: /tmp/jpcite-w47-dim-f-mig.lane
Base: origin/main @ cd5b7bbfb (HEAD: Dim T mig 280 landed)
Author: Wave 47 Phase 2 永遠ループ tick#6
What this PR does¶
Adds migration 285 as a purely additive extension to mig 262
(am_fact_signature) per feedback_explainable_fact_design.md. Lands
two new tables + one helper view, an Ed25519-verifying bridge ETL,
and a 14-case integration test.
| Artefact | Role |
|---|---|
scripts/migrations/285_fact_signature_v2.sql |
TWO new tables on top of mig 262: am_fact_signature_v2_attestation (signature_id PK, fact_id, signer_pubkey, signature_bytes, signed_at) and am_fact_signature_v2_revocation_log (revoke_id PK, signature_id FK, reason_class CHECK enum, revoked_at, revoked_by). Plus helper view v_am_fact_sig_v2_attestation_active. Indexes for (fact_id, signed_at DESC), (signer_pubkey, signed_at DESC), (key_id), unique triplet, revocation lookup. |
scripts/migrations/285_fact_signature_v2_rollback.sql |
Drops only the v2 tables/indexes/view. Mig 262 (am_fact_signature) is NEVER touched (confirmed by test_mig_285_rollback_drops_only_v2). |
scripts/etl/build_fact_signatures_v2.py |
One-shot bridge ETL. LEFT JOIN mig 262 -> mig 285 to find unbridged rows, Ed25519-verify each row against payload_sha256 BEFORE inserting (refuse-on-fail, exit 3). Pure cryptography stdlib (Ed25519PrivateKey / Ed25519PublicKey). Cursor-paged CHUNK_SIZE=5000 walk per feedback_no_quick_check_on_huge_sqlite. --dry-run / --max-rows N / --skip-verify flags. LLM-0 by construction. |
tests/test_dim_f_signature_v2.py |
14 cases: migration apply + idempotent re-apply, rollback safety for mig 262, 4 CHECK constraints (pubkey 64-hex, sha256 64-hex, sig 64..96 bytes), unique triplet dedup, revocation reason_class enum + one-per-signature, active view excludes revoked rows, boot manifest dual registration, full Ed25519 round-trip sign → INSERT → SELECT → verify, LLM-0 grep guard, legacy-brand grep guard. |
scripts/migrations/jpcite_boot_manifest.txt |
Appended 285_fact_signature_v2.sql with explanatory header. |
scripts/migrations/autonomath_boot_manifest.txt |
Mirror append of the same entry. |
Relationship to PR #118 (round 1) and prior dim F work¶
PR #118 (round 1) landed the REST file MISSING axis:
src/jpintel_mcp/api/fact_signature_v2.py (277 LOC) + 7-case test.
That gave the verify endpoint a code path but the storage layer
remained the single-row-per-fact mig 262.
This PR (Wave 47 Phase 2 tick#6) closes the storage extension axis
that round 1 deliberately deferred — the multi-attestation +
revocation backing tables that
feedback_explainable_fact_design.md requires for "all fact = source +
extracted_at + verified_by + confidence 4-axis metadata + Ed25519 sign".
The two tables answer two needs round 1 couldn't:
-
Multi-party attestation enumeration. A single fact may be co-signed by operator + customer-auditor + 3rd-party notary under the same
payload_sha256. mig 262 only stores ONE pointer; mig 285 stores all rows under a unique(fact_id, signer_pubkey, corpus_snapshot_id)triplet. -
Explicit revocation trail. Key rotation / key compromise / payload amendment now leaves an append-only row in
am_fact_signature_v2_revocation_logwith areason_classCHECK enum (key_rotated|key_compromised|payload_amended|operator_request|auditor_request|other). The active viewv_am_fact_sig_v2_attestation_activejoins LEFT and filtersWHERE r.revoke_id IS NULL, so verify endpoints naturally exclude revoked sigs without a WHERE clause in the handler.
Verification done before this PR¶
sqlite3syntax + idempotent re-apply + rollback verify (mig 262 table set intact:am_fact_signature+v_am_fact_signature_latest).pytest tests/test_dim_f_signature_v2.py— 14 passed in 0.99s. Includes a real Ed25519 sign → INSERT → SELECT → verify round-trip usingcryptography.hazmat.primitives.asymmetric.ed25519— the same stdlib used byscripts/cron/refresh_fact_signatures_weekly.pyin mig 262.ruff checkon ETL + test — clean (no diagnostics).- LLM-0 grep across all 4 new files — only matches are the test's own guard assertions / docstring (verified by inspection).
- Legacy-brand grep (
税務会計AI/zeimu-kaikei) — only matches are the test's own guard assertions / docstring.
Constraints honoured (per task brief)¶
- No overwrite of mig 262 (verified by
test_mig_285_rollback_drops_only_v2). - No mutation of the main worktree (lane is
/tmp/jpcite-w47-dim-f-mig). - No
rm/mv(purely additive: 4 new files + 2 manifest appends). - No legacy brand markers in any new file.
- No LLM API import in any new file (LLM-0 invariant preserved).
- Boot manifest registered in both
jpcite_boot_manifest.txtandautonomath_boot_manifest.txt.
Dim F state vector¶
| Axis | Round 0 (pre #118) | Round 1 (#118) | This PR (tick#6) |
|---|---|---|---|
| REST handler | MISSING | LANDED (277 LOC, 7 tests) | unchanged |
| Storage schema | mig 262 only | mig 262 only | mig 285 added (2 tables + 1 view, idempotent, rollback-safe) |
| Ed25519 verify | sig present | sig present | round-trip exercised (sign + INSERT + SELECT + Ed25519PublicKey.verify) |
| Revocation log | absent | absent | landed (am_fact_signature_v2_revocation_log with reason_class 6-value enum) |
| Bridge ETL | absent | absent | landed (build_fact_signatures_v2.py, verify-or-refuse semantics) |
| Test coverage | 7 cases (round 1) | 7 cases (round 1) | +14 cases here (round 1 cases unchanged) |
| Boot manifest | mig 262 only | mig 262 only | mig 285 appended to both jpcite + autonomath manifests |
Next steps after merge¶
- CI verify on the PR (CodeQL / lane-enforcer / acceptance / ruff / pytest). Lane-enforcer should accept the new mig number 285 since it is strictly above the current max 280.
- Production boot — entrypoint reads the manifest and self-heals
mig 285 onto the existing 9.7 GB autonomath.db. Pure additive so
no impact to existing reads. No quick_check is needed (per
feedback_no_quick_check_on_huge_sqlite) and none is issued. - One-shot bridge ETL run (out of band, on a separate tick): the
weekly cron
refresh_fact_signatures_weekly.pywill from this point forward write directly into both mig 262 and mig 285. The bridge ETLbuild_fact_signatures_v2.pyis for the historical backfill of pre-tick rows already in mig 262 that have no mig 285 companion attestation. It is intentionally a one-shot tool and converges to zero writes once the backfill is done. - Wave 47 Phase 2 tick#7+ continues with the next Dim from
feedback_explainable_fact_design-adjacent dimensions (Dim G realtime signal storage, Dim P composable tool storage adjacency).
Files changed (count)¶
scripts/migrations/285_fact_signature_v2.sql (new, ~156 LOC)
scripts/migrations/285_fact_signature_v2_rollback.sql (new, ~19 LOC)
scripts/etl/build_fact_signatures_v2.py (new, ~225 LOC)
tests/test_dim_f_signature_v2.py (new, ~360 LOC)
scripts/migrations/jpcite_boot_manifest.txt (+18 LOC append)
scripts/migrations/autonomath_boot_manifest.txt (+18 LOC append)
docs/research/wave46/STATE_w47_dim_f_pr.md (this file, ~150 LOC)
Total: ~946 LOC across 7 files (4 new + 3 amended).