From c170fc96a3fbfba91a45a7008a4c8856abe8b7aa Mon Sep 17 00:00:00 2001 From: Sinity Date: Sat, 8 Aug 2026 10:34:46 +0200 Subject: [PATCH] test(infra): rebind seeded archive clone bootstrap Problem: seeded workload clones retained a fresh durable bootstrap marker bound to the immutable artifact. The first real archive open then failed durable identity continuity before candidate tests could reach their production route. Recreate the copied marker through the production bootstrap writer after both reflink and copy fallback clones become writable. Cover destination-bound identity, immutable source preservation, real writer opens, and foreign-marker rejection. Ref polylogue-seeded-clone-bootstrap-rebind. Co-Authored-By: Codex --- .beads/issues.jsonl | 1 + tests/infra/workload_artifacts.py | 6 +++++ tests/unit/infra/test_workload_artifacts.py | 30 +++++++++++++++++++++ 3 files changed, 37 insertions(+) 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"