feat(cli): #252 Layer 4 — portable JSON-Lines ledger export/import#258
Conversation
|
Important Review skippedAuto reviews are disabled on base/target branches other than the default branch. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
|
Conflicting after #257 merged ahead of this PR (sibling stack — both touch the same files). Could you rebase against fresh dev so this can land in v0.14.1? After rebase the merge should be clean. |
0a10147 to
96498f4
Compare
CI status — held pending dev-baseline fixes (#272)CI red on this PR is dev-baseline regressions, not Layer 4 code. Full diagnosis: #272.
Holding merge until #272's three dev-baseline fixes land. Re-running CI on this PR after those fixes should produce green. #252 Layer 5 (plan-252-layer-5-diagnose-remediation.md, audit PASS round 2) is queued behind this PR. |
…pe substantiated Layer 5 closes the #252 strategy: `bicameral-mcp diagnose` now surfaces the export → reset → import recipe (Layer 4's portable vehicle) as the recommended remediation when drift_status ∈ {drift, unavailable} or when the ledger is >100 MiB. Recipe is operator-facing display text; diagnose never executes it. Per gating-is-observability discipline. Reality matches Promise: - 8 functional tests + 1 content-contract test all pass - _compute_suggestions at 30 LOC (≤40), file at 248 LOC (≤250) - _remediation_recipe helper at 12 LOC - Layer 3 regression: 5/5 _compute_suggestions tests still pass Logged deviations: - Per-branch single-line append compaction beyond plan's example shapes (lands function at 30 LOC vs plan's 42-LOC projection) - bv truthy-guard preserves Layer 3's invariant: version-derivative suggestions skip when bicameral_version is unknown/missing Substantively closes #221 substrate (GDPR Art. 17 right-to-erasure operator workflow) + #222 (GDPR Art. 15 DSAR). Plan: plan-252-layer-5-diagnose-remediation.md (round 2 PASS; gitignored per c206ad4). Audit: PASS round 2 (round 1 razor-overage + spec-drift remediated). Branch caveat: based on plan/252-layer-4-export-import; will rebase onto fresh dev once #258 unblocks via #273 + Jin's Flow 1 fix (#272).
…residual) Aligns preflight-eval.yml with the test-mcp-regression.yml pin landed in PR #273. Closes the last of #272's three CI-baseline regressions — preflight-eval was the only remaining consumer of the unpinned mutable @v2 tag whose published artifact silently broke between PR #257 and PR #258 (index.js missing from the action's bundled output). Same SHA (31493c76ec9e7aa675f1585d3ed6f1da69269a86, v2.4) used in test-mcp-regression.yml:213 so a future bump is one grep-and-replace. Per docs/policies/install-trust-model.md (OWASP A06 supply-chain discipline): no GitHub Action runs in our CI from a mutable tag. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The migration vehicle + GDPR Art. 15 DSAR + Art. 17 right-to-erasure surface in one shape per docs/research-brief-252-privacy-preserving-ledger-remediation.md. Three phases: - Phase 1: cli/ledger_io.py — canonical record shape (_table + _schema_version + _record_version per record); export_jsonl() async generator with (table, created_at, id) deterministic ordering; import_jsonl() with two-phase validate-then-write (Phase A: validate every record; Phase B: data records first, then edges via RELATE); ImportSummary dataclass + ImportError_ exception. - Phase 2: cli/ledger_export_cli.py + cli/ledger_import_cli.py thin shims (~30 + ~50 LOC); server.py subparser + dispatch wiring for bicameral-mcp ledger-export + ledger-import commands. - Phase 3: docs/policies/ledger-export.md operator policy doc covering canonical record format + GDPR workflow recipes (DSAR, erasure, migration vehicle) + two-pass import rationale + privacy posture + error-mode catalog. README compliance-posture row added (6 -> 7). 8 design decisions locked per /qor-plan dialogue: 1. cli/ledger_io.py shared + thin CLI shims 2. JSON-Lines format 3. Per-record _schema_version + _record_version dual stamp 4. Sort by (table, created_at, id) for diff-stable round-trips 5. Edges as standalone records with in/out preserved (two-pass import) 6. NO redaction at export-time (Layer 3 diagnose is the redacted surface) 7. Wipe-first via separate bicameral-mcp reset; no --force shortcut 8. Strict + summary on import errors; no partial-import state ~400 LOC + ~30 functional tests + 2 content-contract tests. No new pip deps. Identity_supersedes documented as data-shaped despite edge semantics. SCHEMA_VERSION fallback when schema_meta row absent. Plan: plan-252-layer-4-export-import.md Strategy: docs/research-brief-252-privacy-preserving-ledger-remediation.md Closes #252 Layer 4 substrate for #221 (GDPR right-to-erasure) + #222 (GDPR Art. 15 DSAR CLI) when implementation lands.
…re-import) (#252) Round 1 VETO: specification-drift (1 binding finding): bicameral_meta + schema_meta round-trip break — plan claimed at_first_write provenance preserved, but mechanism (init_schema + migrate + sentinel run at adapter.connect BEFORE import; then import CREATE produces duplicate rows with different ULID IDs) doesn't deliver. Layer 2's SELECT...LIMIT 1 invariant breaks under duplicate rows. Round 2 amendments (Path B + 2 Razor advisories baked in): 1. New _DELETE_BEFORE_IMPORT = frozenset({"bicameral_meta", "schema_meta"}) constant. Phase B's first step calls _delete_meta_tables(adapter) which DELETEs both tables before the data-records write pass. End state: each meta table has exactly the source's row, preserving at_first_write provenance + source schema version. Layer 2 SELECT...LIMIT 1 invariant survives by construction. 2. import_jsonl decomposed into 5 private helpers + ~15-LOC orchestrator (per-function Razor 40-LOC ceiling honored): _validate_records (Phase A accumulates ALL errors; no first-failure-only), _assert_ledger_empty (gate skipping _DELETE_BEFORE_IMPORT tables from the check), _delete_meta_tables (Phase B step 1), _write_data_records (Phase B step 2), _write_edge_records (Phase B step 3 via RELATE). 3. cli/ledger_io.py split mandated (not advisory): cli/ledger_io.py (constants + dataclass + canonical-record + sort key ≈ 150 LOC) + cli/_ledger_io_engine.py (gather/export/import + private helpers ≈ 130 LOC). Mirror of Layer 3's cli/_diagnose_gather.py precedent. 3 new functional tests added for the Path B contract: - test_import_jsonl_delete_before_import_preserves_source_at_first_write_provenance - test_import_jsonl_delete_before_import_resolves_schema_meta_to_source_version - test_import_jsonl_delete_before_import_yields_exactly_one_row_per_meta_table Policy doc gains explicit "Meta-table special case" section in docs/policies/ledger-export.md showing the DELETE-before-CREATE flow. Plan: plan-252-layer-4-export-import.md (round 2) Strategy: docs/research-brief-252-privacy-preserving-ledger-remediation.md
…r 4) Closes #252 Layer 4 per docs/research-brief-252-privacy-preserving-ledger-remediation.md. Provides the portable JSON-Lines export/import vehicle that doubles as GDPR Art. 15 DSAR + Art. 17 right-to-erasure surface + surrealdb-py wire-format migration vehicle. cli/ledger_io.py (132 LOC): _DATA_TABLES (15) + _EDGE_TABLES (9) + _DELETE_BEFORE_IMPORT (Path B) frozensets + ImportSummary dataclass + custom exceptions + _canonical_record (stamps _table/_schema_version/ _record_version per record) + _record_sort_key for diff-stable (table, created_at, id) ordering. cli/_ledger_io_engine.py (197 LOC; split per Razor mandate): - _gather_table_rows: tolerant SELECT * (returns [] on missing table) - export_jsonl: async generator, data tables before edges, deterministic sort - _validate_records: Phase A; accumulates ALL errors before raising - _assert_ledger_empty: skips _DELETE_BEFORE_IMPORT tables - _delete_meta_tables: Path B Phase B step 1 - _write_data_records: Phase B step 2 via CREATE - _write_edge_records: Phase B step 3 via RELATE - _maybe_parse_datetime + _rehydrate: ISO-string -> datetime cast for SurrealDB option<datetime> fields (round-trip JSON serialization preserves the type contract) - import_jsonl: ~15-LOC orchestrator over the 5 helpers cli/ledger_export_cli.py + cli/ledger_import_cli.py: thin shims; server.py registers ledger-export + ledger-import subparsers + dispatch. docs/policies/ledger-export.md: operator policy with canonical record shape table, 4 workflow recipes (backup, GDPR Art. 17 erasure, Art. 15 DSAR, migration vehicle), two-pass import rationale, meta-table special case explanation, error mode catalog. README.md: Compliance posture bumped 5 -> 6. Tests: 45 functional pass — 9 canonical-record + 7 export + 12 import (incl. 3 Path B contract tests + 1 _assert_ledger_empty skip-set test) + 3 export CLI + 4 import CLI + 2 doc/code drift content-contract. Path B fully verified: meta tables end with exactly the source's row post-import; Layer 2's SELECT...LIMIT 1 invariant survives. Plan: plan-252-layer-4-export-import.md (round 2) Audit: round 1 VETO specification-drift (bicameral_meta + schema_meta round-trip break) -> round 2 PASS via Path B (DELETE meta tables before data-records pass).
…antiated Path B (round-1 audit central finding) verified end-to-end: - _DELETE_BEFORE_IMPORT + _delete_meta_tables Phase B step 1 - Source's at_first_write survives round-trip - Layer 2's SELECT...LIMIT 1 invariant preserved (1 row per meta table) Reality matches Promise: 45 new functional tests pass + 2 content-contract. Logged deviations: helper-extraction split (Razor mandate), datetime rehydration helpers (impl-time discovery), test count expansion (30 -> 45). Substantively closes #222 (GDPR Art. 15 DSAR CLI) + #221 substrate (GDPR Art. 17 right-to-erasure operator workflow). Plan: plan-252-layer-4-export-import.md (round 2)
96498f4 to
0d55fed
Compare
|
@jinhongkuan — merged to dev. This PR delivers the Layer 4 ledger export/import CLI for #252, substantively addressing #222 (GDPR Art. 15 data-subject access) and BicameralAI/bicameral-daemon#23 (GDPR Art. 17 right-to-erasure). All automated CI gates green (7/7; manual demo recording pending but not a gate). Please review at your convenience. |
Summary
Closes the
#252 Layer 4substrate from the privacy-preserving remediation strategy: a portable JSON-Lines export/import vehicle for the SurrealDB ledger. Substantively closes #222 (GDPR Art. 15 DSAR CLI) and provides the operator escape hatch for BicameralAI/bicameral-daemon#23 (GDPR Art. 17 right-to-erasure).Verdict: PASS (round-2 audit). Round-1 VETO
specification-driftresolved via Path B (DELETE meta tables before data-records pass; preserves source-provenance + singleton invariant).What ships
cli/ledger_io.py(132 LOC) +cli/_ledger_io_engine.py(197 LOC) — split per Razor mandatecli/ledger_export_cli.py(22 LOC) +cli/ledger_import_cli.py(47 LOC) — thin shimsserver.py— newledger-export+ledger-importsubparsers + dispatch armsdocs/policies/ledger-export.md— operator-readable policy with 4 workflow recipes (backup, GDPR Art. 17 erasure, Art. 15 DSAR, migration vehicle)Path B contract (closed by construction)
bicameral-mcp resetwipes ledgerbicameral-mcp ledger-import --from-file <path>:adapter.connect()populates meta tables (destination-side)_validate_recordsvalidates every JSONL line_delete_meta_tablesclears destination-side rows_write_data_recordswrites source rows (incl. source's meta tables)_write_edge_recordswrites RELATE edgesLogged deviations
_diagnose_gather.pyprecedent)._maybe_parse_datetime+_rehydratehelpers added during impl: SurrealDB rejects ISO datetime strings foroption<datetime>fields after JSON round-trip. ~20 LOC. Doctrine-positive expansion.Test plan
memory://ledgertest_compliance_policy_docs.pypassbicameral_meta+schema_metaend with exactly 1 row eachat_first_writeprovenance survives the round-trip (verified end-to-end)test_import_jsonl_writes_edge_via_relateSubstantiation
META_LEDGER entry #48 sealed (commit
0a10147). Plan:plan-252-layer-4-export-import.md.🤖 Generated with Claude Code