diff --git a/.beads/issues.jsonl b/.beads/issues.jsonl index 7b663b81b4..9067a3f07a 100644 --- a/.beads/issues.jsonl +++ b/.beads/issues.jsonl @@ -1748,3 +1748,4 @@ {"_type":"issue","id":"polylogue-fs1.4","title":"Report: polylogue forensics for Hermes sessions","description":"Five-section per-session/per-corpus report, computed from the canonical archive (composition over existing primitives where possible): 1) session topology — parents, resumes, compactions, subagents, branches, long turns; 2) LLM/request economy — token lanes, cost, retry/fallback causes, model/provider shifts, cache-read amplification; 3) tool execution profile — durations, failures, approvals, repeated calls, parallel groups; 4) failure patterns — loops, stalls, empty-response retries, repeated shell failures, truncation, compaction-induced loss, reasoning burn; 5) local causal footprint — git diff/commits, commands, files, build/test runs. The 2-minute demo artifact (sanitized sessions, one command, README section) is the campaign-grade packaging of this report.","design":"Composition first: sections 1-3 and most of 4 should lower onto existing primitives — get_session_topology/logical session (topology), session_provider_usage_events + cost rollups (economy), actions/tool timing (tool profile), pathology detectors + structural outcomes (failure patterns), session_commits/git correlation (footprint). Only add new detectors where Hermes-specific (loop detection over repeated identical tool calls; stall = long gap between spans; reasoning burn = reasoning-token share per turn). Surface: a named read view/report profile (`polylogue forensics hermes --session ` or read --view forensics), rendered markdown + JSON. Demo packaging: sanitized fixture sessions, one command, <2min, README section — that packaging is a legitimate one-off; the five sections' facts must be query-composable (capabilities-not-silos rule).","acceptance_criteria":"A Hermes forensic report regenerates from imported Hermes sessions and emits citable findings with coverage/fidelity caveats, raw evidence refs, and a single documented regeneration command. The report includes at least one happy-path fixture, one missing-field/degraded fixture, and one fidelity limitation that renders visibly instead of silently disappearing.","notes":"Executable upgrade (2026-07-04 sidecar):\nClassification: blocked on polylogue-fs1.1 for real Hermes state.db ingestion, but the report contract can be made executable now against synthetic/fixture sessions and later rerun on real Hermes rows.\nProduct question: can Polylogue produce a cold-reader forensic report that explains one Hermes/agent session better than the runtime itself, using canonical archive facts rather than a silo export?\nLikely modules/surfaces: read/query surfaces under polylogue/cli/read or command inventory, session topology/logical-session APIs, session_provider_usage_events/cost rollups, actions/tool timing readers, git/local footprint helpers, demo fixture/scenario generators, docs/demo shelf. Prefer a named report/read view that composes existing primitives; avoid a Hermes-only data path.\nArtifact shape: markdown + JSON report with the five existing sections, each section carrying source_refs/query names and missing-data caveats. Demo package must include sanitized fixture/session id, one command, expected runtime under 2 minutes, and a README snippet.\nAcceptance detail: for fixture data, each section has at least one asserted fact and one source reference; for missing Hermes fields, the report emits explicit unavailable/caveat rows rather than prose guesses; once fs1.1 lands, rerun against a real Hermes session and record diff between fixture and real coverage.\nVerification commands: focused unit/visual/demo command for the report surface, plus devtools render all --check if docs/README/demo surfaces are touched. If the implementation adds a new CLI command or view, verify command inventory and generated docs through devtools render all.\n[Delivery upgrade 2026-07-07T00:05:00Z] Release=K-interop-origin-export; lane=origin-interop-export; readiness=A-implementation-ready; proof=OriginSpec detector/parser/fixture/fidelity suite and content-hash export/import roundtrip. Original readiness=C-needs-acceptance-criteria.\n[Prework packet 2026-07-07] Static execution packet (anchors, mechanism, plan, tests, verification): .agent/handoffs/polylogue-gpt-pro-2026-07-07/prework-v2/task_packets/105_polylogue_fs1_4.md (depth: bead-localized-from-export; urgency: T2-foundation-before-feature-proof). Generated from master @ 8a975a40 2026-07-06 — verify source anchors before coding; line numbers are snapshot-relative.\n2026-07-10 integration-demo refinement: fs1.12 consumes this report. Include a claim-vs-tool-evidence canary with supported, contradicted, and unknown states; every displayed conclusion must resolve to structured tool outcome evidence and a fidelity caveat, not agent prose.\n2026-07-10 Nous follow-up technical refinement: use an evidence-status taxonomy that distinguishes supported, contradicted, later repaired/reversed, externally uncheckable, and unverifiable because required evidence was not retained. Preserve temporal ordering and evidence-retention caveats so later success cannot launder an earlier contradicted claim. Reuse the general claim-vs-evidence/claims-ledger substrate; do not create Hermes-only verdict semantics.\nDeferred (no new code) -- investigated and found the design's own preferred shape (\"composition first... avoid a Hermes-only data path... only add new detectors where Hermes-specific\") is already substantially satisfied by existing generic primitives, verified by reading their source (not assumed):\n\n- Section 1 (session topology): get_session_topology / logical session APIs -- already exist, origin-agnostic.\n- Sections 2-4 (LLM/request economy, tool execution profile, failure patterns): polylogue/insights/postmortem.py's compile_postmortem_bundle. Verified it is 100% origin-agnostic (SessionProfile.origin is a plain string field, zero origin-conditional branches in the aggregator) and already produces cost/token-lane metrics, tool-category profiles, and pathology-detector failure_mode/wasted_loop fields with evidence refs and honest degraded-not-fabricated behavior for missing signal (test_compile_postmortem_bundle_degrades_without_signal already covers this generically).\n- Section 5 (local causal footprint): polylogue/insights/session_commit.py's detect_session_commits -- git-commit attribution via time-window + file-overlap scoring, already exists, origin-agnostic.\n\nWhat's genuinely missing, and why I did not build it this pass: (a) a NAMED regeneration surface (a `polylogue forensics hermes --session ` command or `read --view forensics`) unifying these existing primitives under one command -- mechanical but real work that triggers the CLI-inventory/devtools-render-docs cascade; (b) the claim-vs-tool-evidence canary / evidence-status taxonomy the bead's own 2026-07-10 refinement explicitly says to build on \"the general claim-vs-evidence/claims-ledger substrate\" -- that substrate does not exist yet, so building Hermes-only verdict semantics here would violate the refinement's own instruction not to invent parallel machinery.\n\nDid not add a redundant \"hermes-flavored\" test of compile_postmortem_bundle: since the aggregator has zero origin-conditional logic, a test asserting it also works with origin=\"hermes-session\" would be vacuous (guaranteed to pass, proves nothing a mutation could break that the existing origin-agnostic tests don't already cover).\n\nRecommend: a follow-up scoped narrowly to (a) the CLI/read-view wiring only, composing the primitives above with zero new detector logic -- and treat the claim-vs-evidence canary as blocked on its own substrate bead, not this one.\n2026-07-18 (Claude Sonnet, branch feature/fix/hermes-atof-remaining-gaps): landed the verification-coverage correlation primitive that Phase 3's verification-ledger import (wj25) unblocked -- polylogue/insights/hermes_verification_coverage.py, a pure aggregator (no I/O) summarizing one Hermes session's verification_evidence.db coverage: structural event outcomes, final status, changed_paths, honest available=False (not fabricated) when no verification evidence exists. Also added hermes_verification.hermes_verification_session_id_for mirroring hermes_spans's existing observer-correlation helper. Verified via real archive ingestion (LiveBatchProcessor), not hand-built fixtures. 3/3 tests, devtools verify --quick green.\n\nDid NOT attempt in this pass, per this bead's own prior 2026-07-14 finding that sections 1-4 are 'substantially satisfied by existing generic primitives' and the recommended narrow follow-up is 'the CLI/read-view wiring only': the named CLI regeneration surface (read --view forensics or similar), the per-corpus aggregate ('sessions ended with failing/absent verification'), the 2-minute demo package (sanitized fixtures, one command, README section), and MCP tool wiring. This was a deliberate scoping decision given session budget, not an oversight -- the correlation primitive was the one piece genuinely blocked on Phase 3 landing first; the rest is composition/wiring work that deserves its own focused pass (CLI read-view registration triggers the docs-render cascade per this repo's own gotchas list).\n2026-07-18 merge: PR #3120 squash-merged to master as b563083188926b2078965ea41c2028a0a305577e. Verification-coverage correlation primitive (hermes_verification_coverage.py) is now live on master. Bead stays open: named CLI/read-view surface, per-corpus aggregate, 2-minute demo package, and MCP wiring remain undone per this pass's explicit scoping decision (see prior note).\n[Verification sweep 2026-07-31, bead-landing-check group5] Verdict: LIVE. Substantial composition-based delivery (PR #3120 verification-coverage correlation) but explicit remaining scope: named CLI/read-view surface, per-corpus aggregate, demo package, MCP wiring - \"deliberate scoping decision... not an oversight.\"\nUNBLOCKED 2026-07-31 (polylogue-pbuh/cijx.1 residual pass, worktree agent-aaffe89902b670d4b): the session->PR producer+reader chain this bead depends on is now real. session_refs carries typed pull_request evidence (18,949 rows live), and PR #3425 (merged 5525446a2) wired `read --view correlation` / Polylogue.session_correlation_payload to consume it as authoritative over the old regex/time-window heuristics, with disagreements surfaced rather than silently guessed. Verified live against /realm/db/polylogue/index.db (read-only) that the CLI path resolves real typed PR refs end-to-end (also fixed a pre-existing NameError in that path's GitHub-enrichment branch that had never been exercised with real refs before this pass). Full detail: polylogue-cijx.1 and polylogue-pbuh notes, 2026-07-31.\n\nNOT closed by this alone: this bead's own AC still needs its specific deliverable (see this bead's own description) beyond \"the correlation data is now readable\" -- that implementation work was not attempted in this pass (out of its declared scope: read-surface residual verification for pbuh/cijx.1 only). Re-triage this bead's own AC against the now-working session_commit.py/correlation_view.py surface when picked up next.\n","status":"open","priority":4,"issue_type":"feature","owner":"ezo.dev@gmail.com","created_at":"2026-07-03T04:31:41Z","created_by":"Sinity","updated_at":"2026-07-31T22:35:46Z","labels":["area:ingest","area:query","area:substrate","delivery:K-interop-origin-export","delivery:ac-patched","lane:origin-interop-export"],"dependencies":[{"issue_id":"polylogue-fs1.4","depends_on_id":"polylogue-cijx.1","type":"blocks","created_at":"2026-07-29T06:52:02Z","created_by":"Sinity","metadata":"{}"},{"issue_id":"polylogue-fs1.4","depends_on_id":"polylogue-fs1","type":"parent-child","created_at":"2026-07-03T06:31:40Z","created_by":"Sinity","metadata":"{}"},{"issue_id":"polylogue-fs1.4","depends_on_id":"polylogue-fs1.1","type":"blocks","created_at":"2026-07-03T06:31:41Z","created_by":"Sinity","metadata":"{}"},{"issue_id":"polylogue-fs1.4","depends_on_id":"polylogue-fs1.3","type":"blocks","created_at":"2026-07-10T11:03:54Z","created_by":"Sinity","metadata":"{}"}],"dependency_count":3,"dependent_count":2,"comment_count":0} {"_type":"issue","id":"polylogue-fs1.2","title":"Importer: NeMo Relay ATOF/ATIF runtime spans","description":"Import Hermes observer-layer trace exports as runtime span evidence: pre/post_api_request -> LLM request spans; pre/post_tool_call -> tool execution spans with duration/status; approval hooks -> high-risk decision points; subagent hooks -> delegation graph; error hooks -> retry/fallback taxonomy. ATIF import + enrichment beats inventing another trajectory format — respect Hermes's actual extension seams and make Polylogue the normalizer.","design":"VERIFY first: current NeMo Relay plugin output shape in the Hermes repo (ATOF JSONL / ATIF JSON exported from observer hooks). Ingest route: new artifact kinds in the taxonomy (archive/artifact_taxonomy/) + a spans parser under sources/parsers/, landing as ObservedEvents/actions attached to the session (join key: Hermes session id from the trace envelope -> sessions.native_id). Map: pre/post_api_request pair -> LLM request span (duration, model, provider, token fields if present); pre/post_tool_call -> tool execution span with duration/status (structural outcome — feeds is_error/exit_code lanes where present); approval hooks -> decision-point events; subagent lifecycle -> topology_edges (subagent type); error hooks -> retry/fallback taxonomy events. Spans without a matching archived session become explicit acquisition debt rows, not silent drops.","acceptance_criteria":"`polylogue-fs1.2` adds or updates an origin contract with detector, parser, raw fixture, normalized fixture, parser fingerprint, and fidelity/completeness notes. Ambiguous inputs are handled deterministically. The regression suite proves idempotent replay and visible degraded/missing-field behavior. Verification artifact: OriginSpec detector/parser/fixture/fidelity suite and content-hash export/import roundtrip.","notes":"[Delivery upgrade 2026-07-07T00:05:00Z] Release=K-interop-origin-export; lane=origin-interop-export; readiness=D-horizon-ready; proof=OriginSpec detector/parser/fixture/fidelity suite and content-hash export/import roundtrip. Original readiness=E-spec-needed.\n2026-07-10 Hermes contract refinement: ingest context_injected with profile/session/turn/snapshot-revision correlation; unpaired spans remain explicit acquisition debt. fs1.7 owns atomic spool/export production; this bead owns normalization and reconciliation.\nImplemented and PR opened (not merged): #2876 (feature/hermes/lifecycle-spool-and-bridge).\n\nScope understood: import Hermes observer-layer (NeMo Relay) trace exports as runtime span evidence, normalized and reconciled per the design's mapping (pre/post_api_request -> LLM request spans, pre/post_tool_call -> tool spans, approvals -> decision points, subagent hooks -> delegation evidence, error hooks -> retry/fallback taxonomy).\n\nHonesty constraint documented explicitly in code + PR: the real ATOF/ATIF wire shape was not independently verifiable from this workspace -- no local checkout of the Hermes observer-plugin source was available. sources/parsers/hermes_spans.py implements a documented, testable, best-effort marker-based schema derived from this bead's own design notes and the shared lifecycle taxonomy (hermes_lifecycle.py, fs1.7). Every fidelity capability the parser declares tops out at \"inferred\", never \"exact\", for this reason -- filed as a concrete follow-up (fs1.2.1, not yet created as a bead by me -- flagging here so the orchestrator can file it) to re-verify against real Hermes source and tighten fidelity if it matches without changing the public contract.\n\nWhat changed: sources/parsers/hermes_spans.py (detector/parser/fidelity), wired into the real dispatch pipeline (sources/dispatch.py: detect_provider, lowering, parse_payload -- same path every other origin uses, not a bespoke test-only entrypoint); new artifact-taxonomy classification (archive/artifact_taxonomy/runtime.py).\n\nAC checklist: origin contract with detector/parser/raw fixture/normalized fixture/parser fingerprint/fidelity notes -- satisfied (marker_payload() is the raw-fixture generator used by every test; normalized output is the ParsedSession/session_events produced; fidelity via import_fidelity_declaration()). Ambiguous inputs handled deterministically -- satisfied: unrecognized hook_type -> generic hermes_observer_span event (never dropped, never misclassified as a known kind); malformed span entries (missing hook_type/span_id, non-dict entries) are skipped and counted, not crashing. Idempotent replay -- satisfied and tested (test_atif_parse_is_idempotent_and_deterministic: same document parsed twice -> byte-identical structural output). Visible degraded/missing-field behavior -- satisfied: unpaired spans (start without finish) are counted and surfaced as an explicit degraded fidelity capability with a caveat, never silently dropped.\n\nDesign gap explicitly NOT closed, documented not silently assumed: physical merge of observer spans into the state-db-ingested conversational session's message tree (the design's \"landing as ObservedEvents/actions attached to the session\"). This parser instead produces its own observer-evidence session (observer:) with a read-side correlation helper (hermes_observer_session_id_for) joining by the shared raw Hermes session id -- a physical content-tree merge across two independently-acquired artifacts is a session-identity/lineage design decision (topology_edges/session_links) I judged out of scope for this pass rather than improvising a schema-adjacent change.\n\nVerification: devtools test tests/unit/sources/parsers/test_hermes_spans.py -- 9/9 passed (subset of PR's 43-test combined run). devtools verify --quick exit 0.\n[gpt-5.6-terra integration refinement, 2026-07-14]\n\nReal producer evidence now exists: the bundled NousResearch Hermes observability/nemo_relay plugin emits ATIF v1.7 session documents and append-only ATOF JSONL through actual session, LLM, tool, approval, and subagent callbacks. ATIF import is live. The remaining producer-to-archive gap is ATOF materialization, not schema speculation.\n\nRefine this bead implementation order: retain byte-identified ATOF raw evidence first; incremental reader checkpoints file identity plus byte offset; tolerate partial final lines and rotation/truncation; validate/order/deduplicate events; materialize normalized lifecycle/action evidence idempotently; retain parent/child subagent links; surface unpaired/unmatched records as debt. Never synthesize ATIF from ATOF or duplicate transcript bodies into events. Update OriginSpec fidelity only where real exported fixtures prove a field mapping.","status":"closed","priority":4,"issue_type":"feature","owner":"ezo.dev@gmail.com","created_at":"2026-07-03T04:31:39Z","created_by":"Sinity","updated_at":"2026-07-31T22:35:43Z","closed_at":"2026-07-20T21:34:47Z","close_reason":"Complete in substance across the merged chain — every item of the 2026-07-14 refined implementation order shipped: byte-identified ATOF raw retention + incremental byte-offset reader with partial-line/rotation tolerance (pre-existing append-plan mechanism, verified fs1.2.1 notes); validate/order/dedup + idempotent lifecycle/action materialization (#3103); shared-file multi-session correctness (#3113/flxh); parent/child subagent links from producer-positive marks, fail-closed (#3231); unpaired/unmatched as explicit debt (#3103). Identity composed with profile+artifact-family qualification (#3224/#3225). OriginSpec detector/parser/fixture/fidelity satisfied against REAL producer fixtures with marker-only payloads as negative tests (#3231, fs1.2.1 closed). Force rationale: remaining blocker edge 2qx.1.1 (shared OriginSpec admission kernel/conformance law) is a lane-gate shared suite per the delivery-ac-template-interpretation adjudication (2026-07-07) — not a per-bead requirement; the Hermes origin will conform when that kernel lands, tracked there.","metadata":{"authored_by":"gpt-5.6-terra","authored_on":"2026-07-14"},"labels":["area:ingest","area:substrate","delivery:K-interop-origin-export","delivery:ac-patched","lane:origin-interop-export"],"dependencies":[{"issue_id":"polylogue-fs1.2","depends_on_id":"polylogue-2qx.1.1","type":"blocks","created_at":"2026-07-15T20:55:32Z","created_by":"Sinity","metadata":"{}"},{"issue_id":"polylogue-fs1.2","depends_on_id":"polylogue-fs1","type":"parent-child","created_at":"2026-07-03T06:31:39Z","created_by":"Sinity","metadata":"{}"},{"issue_id":"polylogue-fs1.2","depends_on_id":"polylogue-fs1.2.1","type":"blocks","created_at":"2026-07-14T11:39:19Z","created_by":"Sinity","metadata":"{}"}],"dependency_count":2,"dependent_count":2,"comment_count":0} {"_type":"issue","id":"polylogue-harness-clone-contracts","title":"test(infra): restore archive-clone and embedding-stat contracts","description":"A current-master comparison for polylogue-mn0si exposed two independent shared-harness regressions. Cloned empty archives retained a fresh-bootstrap receipt bound to the template root, and the embedding-stat contract mock ignored bound sqlite_master parameters. Repair both without weakening durable identity checks or changing production embedding semantics.","acceptance_criteria":"1. Cloning the shared empty archive rebinds the fresh durable-bootstrap receipt to the destination identity, opens through the real ArchiveStore route, and leaves the source receipt unchanged. 2. The embedding-stat contract fixture resolves sqlite_master probes from bound parameters and preserves the intended legacy fallback count behavior. 3. The seven focused regressions pass, formatting and lint pass, and a complete current-master full-suite ledger records unrelated residual failures without claiming them fixed. 4. No production durable-train safety check or embedding provider behavior is weakened.","status":"closed","priority":1,"issue_type":"bug","owner":"ezo.dev@gmail.com","created_at":"2026-08-08T06:54:56Z","created_by":"Sinity","updated_at":"2026-08-08T07:16:43Z","closed_at":"2026-08-08T07:16:43Z","close_reason":"Implemented archive-clone destination rebinding and embedding-stat bound-parameter fixtures in PR #3883. The exact seven-test focused batch and quick gate passed; the complete current-master full-suite ledger recorded unrelated residual failures without claiming them fixed.","dependencies":[{"issue_id":"polylogue-harness-clone-contracts","depends_on_id":"polylogue-mn0si","type":"discovered-from","created_at":"2026-08-08T08:54:56Z","created_by":"Sinity","metadata":"{}"}],"dependency_count":0,"dependent_count":0,"comment_count":0} +{"_type":"issue","id":"polylogue-seeded-clone-bootstrap-rebind","title":"test(infra): rebind durable bootstrap on seeded archive clones","description":"The immutable seeded-workload archive cache is copied by tests/infra/workload_artifacts.py::clone_seeded_archive. Unlike the empty-archive fixture path repaired in PR #3883, this clone path preserves the source artifact's fresh durable-bootstrap marker byte-for-byte. Its source/user durable identities no longer match the destination files, so the first real ArchiveStore open fails closed with fresh durable bootstrap marker durable identity mismatch. Candidate/canary tests that begin from seeded workload clones therefore cannot exercise the production route.","design":"Give all private seeded-archive clones destination-bound fresh-bootstrap evidence immediately after the copy or reflink completes, using the production receipt writer already used by the empty-archive clone path. Keep the immutable source artifact and its marker unchanged. Add a focused regression that records source marker bytes, clones, verifies marker divergence and destination identity, then opens the clone through the real ArchiveStore route.","acceptance_criteria":"1. clone_seeded_archive replaces only the copied fresh durable-bootstrap marker with a destination-bound production marker after making the clone writable. 2. The immutable cached artifact marker remains byte-identical. 3. Reflink and copy fallback clones both use the same rebinding boundary. 4. A real ArchiveStore writer open succeeds on the clone and durable continuity checks remain fail-closed for an actually foreign marker. 5. Focused workload-artifact, temp-policy, and durable-train regressions plus devtools verify --quick pass.","notes":"Implemented destination-bound bootstrap rebinding in tests/infra/workload_artifacts.py for both reflink and copy fallback clones. Added real ArchiveStore open, immutable-source-marker preservation, and foreign-marker fail-closed regressions. Verification: 26 focused tests passed; mypy strict passed on both touched files; devtools verify --quick run 20260808T083029Z-quick-3360251-3cef0cf0 passed all 24 steps.","status":"closed","priority":1,"issue_type":"bug","assignee":"Sinity","owner":"ezo.dev@gmail.com","created_at":"2026-08-08T08:20:27Z","created_by":"Sinity","updated_at":"2026-08-08T08:33:10Z","closed_at":"2026-08-08T08:33:10Z","close_reason":"Seeded archive clones now replace copied bootstrap evidence with a destination-bound production marker on both clone paths. Real opens pass, the immutable artifact is unchanged, a restored foreign marker still fails closed, 26 focused tests pass, and quick verification is green.","labels":["area:test-infra","lane:reindex"],"dependencies":[{"issue_id":"polylogue-seeded-clone-bootstrap-rebind","depends_on_id":"polylogue-harness-clone-contracts","type":"discovered-from","created_at":"2026-08-08T10:20:27Z","created_by":"Sinity","metadata":"{}"},{"issue_id":"polylogue-seeded-clone-bootstrap-rebind","depends_on_id":"polylogue-inactive-candidate-durable-barrier","type":"discovered-from","created_at":"2026-08-08T10:20:27Z","created_by":"Sinity","metadata":"{}"}],"dependency_count":0,"dependent_count":0,"comment_count":0} diff --git a/tests/infra/workload_artifacts.py b/tests/infra/workload_artifacts.py index 50395136be..dcd2192e8c 100644 --- a/tests/infra/workload_artifacts.py +++ b/tests/infra/workload_artifacts.py @@ -529,6 +529,12 @@ def clone_seeded_archive(artifact: SeededArchiveArtifact, destination: Path) -> for path in destination.rglob("*"): path.chmod(path.stat().st_mode | stat.S_IWUSR) destination.chmod(destination.stat().st_mode | stat.S_IWUSR) + bootstrap_marker = destination / ".maintenance-state" / "durable-change-trains" / ".bootstrap" + if bootstrap_marker.is_file(): + from polylogue.storage.sqlite.durable_change_train import _record_fresh_durable_bootstrap + + bootstrap_marker.unlink() + _record_fresh_durable_bootstrap(destination) return SeededArchiveClone( root=destination, source_manifest_id=artifact.manifest.manifest_id, diff --git a/tests/unit/infra/test_workload_artifacts.py b/tests/unit/infra/test_workload_artifacts.py index 81d47e69cd..6b6acdc56b 100644 --- a/tests/unit/infra/test_workload_artifacts.py +++ b/tests/unit/infra/test_workload_artifacts.py @@ -5,6 +5,7 @@ import gc import os import sqlite3 +import subprocess import time from pathlib import Path from typing import Any @@ -12,6 +13,8 @@ import pytest from polylogue.storage.archive_readiness import raw_materialization_readiness_snapshot, raw_materialization_ready +from polylogue.storage.sqlite.archive_tiers.archive import ArchiveStore +from polylogue.storage.sqlite.durable_change_train import DurableChangeTrainError from tests.infra.workload_artifacts import ( _journal_mode_delete_with_retry, build_seeded_archive, @@ -41,17 +44,44 @@ def test_seeded_archive_publishes_valid_immutable_real_pipeline_artifact(tmp_pat def test_seeded_archive_clone_is_private_full_root_and_preserves_base(tmp_path: Path) -> None: artifact = build_seeded_archive(cache_root=tmp_path / "cache") base_manifest = artifact.root.joinpath("manifest.json").read_bytes() + marker_relative = Path(".maintenance-state/durable-change-trains/.bootstrap") + base_marker = artifact.root.joinpath(marker_relative).read_bytes() clone = clone_seeded_archive(artifact, tmp_path / "clone") clone.root.joinpath("private-mutation.txt").write_text("private") + with ArchiveStore.open_existing(clone.root, read_only=False) as archive: + assert archive.count_sessions() == 64 assert clone.clone_method in {"reflink", "copy"} assert clone.source_manifest_id == artifact.manifest.manifest_id assert clone.root.joinpath("source.db").exists() assert clone.root.joinpath("index.db").exists() assert artifact.root.joinpath("manifest.json").read_bytes() == base_manifest + assert artifact.root.joinpath(marker_relative).read_bytes() == base_marker + assert clone.root.joinpath(marker_relative).read_bytes() != base_marker assert not artifact.root.joinpath("private-mutation.txt").exists() + clone.root.joinpath(marker_relative).write_bytes(base_marker) + with pytest.raises(DurableChangeTrainError, match="durable identity mismatch"): + ArchiveStore.open_existing(clone.root, read_only=False) + + +def test_seeded_archive_copy_fallback_rebinds_durable_bootstrap( + tmp_path: Path, + monkeypatch: pytest.MonkeyPatch, +) -> None: + artifact = build_seeded_archive(cache_root=tmp_path / "cache") + + def reject_reflink(*args: object, **kwargs: object) -> None: + raise subprocess.CalledProcessError(1, ["cp"]) + + monkeypatch.setattr(subprocess, "run", reject_reflink) + clone = clone_seeded_archive(artifact, tmp_path / "clone") + + assert clone.clone_method == "copy" + with ArchiveStore.open_existing(clone.root, read_only=False) as archive: + assert archive.count_sessions() == 64 + def test_seeded_archive_rejects_corrupt_published_cache_and_rebuilds(tmp_path: Path) -> None: cache_root = tmp_path / "cache"