Skip to content

fix(bundle): use POSIX separators in manifest paths and tar arcnames#73

Merged
plind-junior merged 1 commit into
vouchdev:mainfrom
galuis116:fix/bundle-windows-path-separator
May 25, 2026
Merged

fix(bundle): use POSIX separators in manifest paths and tar arcnames#73
plind-junior merged 1 commit into
vouchdev:mainfrom
galuis116:fix/bundle-windows-path-separator

Conversation

@galuis116

@galuis116 galuis116 commented May 25, 2026

Copy link
Copy Markdown
Contributor

Problem

On Windows, vouch export writes bundle paths into manifest.json
using the native \ separator while tarfile records member
names with /. Every bundle vouch produces on Windows is therefore
self-invalid:

  • manifest.counts is {claims: 0, pages: 0, sources: 0, ...} for
    every KB regardless of content (the per-subdir counter at
    src/vouch/bundle.py:90 uses
    f["path"].startswith(f"{sub}/"), which never matches
    sources\...).
  • vouch export-check on the bundle vouch just produced returns
    ok: false with file in bundle but not in manifest for every
    tar member and manifest lists missing file for every manifest
    entry.
  • vouch import-apply is a silent no-op for the same reason: the
    manifest-paths set lookup at src/vouch/bundle.py:264 is keyed
    on the \-flavoured strings, so every tar member is treated
    as "not in manifest" and skipped.

Portable bundles are a load-bearing feature (README: "Portable
bundles"), so this corrupts the only mechanism for moving a KB
between repos on any Windows host. The bug is invisible to CI
because .github/workflows/ci.yml only runs on ubuntu-latest,
where Path already produces POSIX separators.

Fix

Normalise paths to POSIX in two places in src/vouch/bundle.py:

@@ build_manifest
     for rel, abs_path in _iter_export_files(kb_dir):
         data = abs_path.read_bytes()
         files.append({
-            "path": str(rel),
+            "path": rel.as_posix(),
             "size": len(data),
             "sha256": sha256_hex(data),
         })

@@ export
     with tarfile.open(dest, "w:gz") as tar:
         for rel, abs_path in _iter_export_files(kb_dir):
-            tar.add(abs_path, arcname=str(rel))
+            tar.add(abs_path, arcname=rel.as_posix())

POSIX separators are the on-the-wire format anyway: tarfile uses
/, _unsafe_name_reason already checks startswith("/"), and
the startswith(f"{sub}/") counter at line 90 assumes the same.
Existing Linux/macOS bundles are byte-identical after this change;
Windows bundles become valid. No on-disk-layout, schema, or
bundle-format change — the spec stays as Linux already
emitted it.

Tests

Added tests/test_bundle.py::test_manifest_paths_match_tar_member_names
as a regression contract: asserts no backslash in any manifest
path, asserts the manifest path set equals the tar member set, and
asserts manifest["counts"] is non-zero for claims, pages,
and sources. This guards against str(rel) slipping back in.

Local run on Windows (Python 3.12.13):

  • ruff check src tests — passes.
  • pytest tests/test_bundle.py — 6/7 pass (the new contract
    test is among them; test_export_import_round_trip now reaches
    a separate, pre-existing Windows bug in put_page CRLF
    handling that is masked on Linux — out of scope for this PR,
    reportable separately).
  • End-to-end CLI: vouch initvouch source add
    vouch exportvouch export-check now returns
    {"ok": true, "issues": []} and manifest.counts.sources is
    2 (was 0).

On Linux/macOS, behaviour and bundle bytes are unchanged.

Fixes #72

Summary by CodeRabbit

  • Bug Fixes
    • Fixed Windows bundle export path format inconsistency that made exported bundles incompatible with validation and import operations. Bundles now export consistently across all platforms. Previously exported Windows bundles should be re-exported.

Review Change Stack

…ouchdev#72)

build_manifest stored Windows-style 'sources\<sha>\meta.yaml' in
manifest.json while tarfile members used 'sources/<sha>/meta.yaml',
so on Windows every bundle vouch produced was self-invalid:
export_check returned ok=false, manifest.counts was always zero,
and import_apply was a silent no-op.

Normalise via rel.as_posix() in both build_manifest and
export(). Add a regression test that asserts the manifest path set
equals the tar member set and that no path contains a backslash.

Existing Linux/macOS bundles are unchanged (their paths were
already POSIX). Windows bundles produced before this fix should be
re-exported.
@coderabbitai

coderabbitai Bot commented May 25, 2026

Copy link
Copy Markdown

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: cf457e71-6455-414a-8b17-9d4d4c46b121

📥 Commits

Reviewing files that changed from the base of the PR and between c3accb6 and e1917a5.

📒 Files selected for processing (3)
  • CHANGELOG.md
  • src/vouch/bundle.py
  • tests/test_bundle.py

📝 Walkthrough

Walkthrough

This PR fixes a critical Windows bug where vouch export produced self-invalid bundles due to path separator mismatch: manifest.json recorded paths with \ while tar members used /. The fix normalizes all paths to POSIX separators, and includes a regression test and changelog documentation.

Changes

Cross-platform bundle path normalization

Layer / File(s) Summary
Path normalization in bundle export and import
src/vouch/bundle.py, tests/test_bundle.py, CHANGELOG.md
build_manifest and export now normalize file paths to POSIX / separators via Path.as_posix() instead of platform-dependent str(rel). New regression test test_manifest_paths_match_tar_member_names validates that manifest paths contain no backslashes and tar member names exactly match manifest path values. Changelog documents the fix, impact on prior Windows bundles, and unchanged behavior for Linux/macOS bundles.

Estimated code review effort

🎯 1 (Trivial) | ⏱️ ~5 minutes

Possibly related issues

  • \#72: Direct fix implementation for the reported Windows bundle export bug where manifest paths used backslash separators while tar members used forward slashes, breaking export-check and import-apply workflows.

Poem

🐰 A rabbit hops through paths of old,
Found Windows breaks what Linux holds,
Slashes forward, forward still,
Now bundles work on every hill!
POSIX peace across the land! 🌍

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title directly and specifically describes the main changes: normalizing POSIX separators in manifest paths and tar member arcnames to fix the Windows bundle export bug.
Linked Issues check ✅ Passed The PR fully addresses all coding requirements from issue #72: path normalization to POSIX in manifest and tar arcnames, comprehensive test coverage validating path consistency, and preservation of existing bundle compatibility.
Out of Scope Changes check ✅ Passed All changes are directly aligned with issue #72 requirements: bundle.py fixes, CHANGELOG documentation, and new regression test. No unrelated modifications detected.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Comment @coderabbitai help to get the list of available commands and usage tips.

@plind-junior plind-junior merged commit e57825e into vouchdev:main May 25, 2026
1 check passed
galuis116 added a commit to galuis116/vouch that referenced this pull request May 25, 2026
Resolves conflicts in CHANGELOG.md, tests/test_sessions.py, and
auto-merges src/vouch/sessions.py with the recently-merged:

  - vouchdev#61 (FTS5 indexing of crystallize summary page) — adds
    index_db.index_page(...) right after store.put_page(...) inside
    crystallize. Composes cleanly with this PR's strict-derivation
    _build_summary_body and the summary_page_id audit fix.
  - vouchdev#62 (test_crystallize_collects_approval_failures) — preserved as
    a sibling test.
  - vouchdev#75 (bundle import sha256 verification) — CHANGELOG entry only.
  - vouchdev#73 (bundle POSIX separators) — CHANGELOG entry only.

Both regression tests added by this PR
(test_crystallize_summary_page_does_not_leak_agent_controlled_fields
and test_crystallize_audit_event_records_summary_page_id) still pass
against the merged sessions.crystallize.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

bug: vouch export produces self-invalid bundles on Windows (manifest uses backslash separator, tar uses forward slash)

2 participants