Skip to content

feat(cli,portability): attestix export — produce M6-compatible bundles (closes symmetric portability)#95

Merged
ascender1729 merged 1 commit into
mainfrom
feature/cli-bundle-export
May 28, 2026
Merged

feat(cli,portability): attestix export — produce M6-compatible bundles (closes symmetric portability)#95
ascender1729 merged 1 commit into
mainfrom
feature/cli-bundle-export

Conversation

@ascender1729

Copy link
Copy Markdown
Member

What

OSS attestix export <output.tar.gz> — the symmetric counterpart to attestix import (PR #93) and the cloud M6 P1 worker. Closes the constitutional "portability is a right" principle end-to-end: OSS → cloud and cloud → OSS, byte-interchangeable bundles.

This is the persona-review's #1 critical fix — the Bedrock cross-family adversary (GLM-5) caught that the previous tier matrix accidentally gave OSS users import-only portability, violating the zero-lock-in promise.

CLI

attestix export <output.tar.gz> [--workspace <name>] [--no-include-anchors] [--force]
  • One progress line per table: [✓] audit_events N rows sha256:abc12345….
  • Final line: wrote <path> <bytes> manifest_sha256=<…>.
  • Refuses to overwrite without --force.

Wire-format parity (https://attestix.io/spec/bundle/v1)

Mirrors the cloud worker apps/workers/ts/src/exports.ts field-for-field so cloud↔OSS bundles are byte-interchangeable. Member order matches the cloud's EXPORT_TABLE_SPECS. USTAR mtime=0 + gzip header mtime=0 + alphabetical member order → byte-identical re-exports (verified by test_deterministic_re_export_is_byte_identical).

EXPORT_PLAN (in attestix/portability/bundle_writer.py)

bundle table OSS collection OSS→cloud projector
identities identities.agents _row_identity_to_cloud
credentials credentials.credentials _row_credential_to_cloud
compliance_profiles compliance.profiles _row_compliance_profile_to_cloud
conformity_assessments compliance.assessments _row_conformity_assessment_to_cloud
audit_events audit.events _row_audit_event_to_cloud
anchors anchors.anchors _row_anchor_to_cloud

Cloud-only tables (key_references, credential_schemas, memberships, team_invites, subscriptions, agent_dependencies, webhook_endpoints) are emitted as empty JSONL so the manifest count stays cloud-symmetric.

Audit chain integrity

The exporter inspects audit.events and uses each event's tenant_id as the bundle's workspace.slug. If multiple tenants exist without --workspace filter, errors. This guarantees the importer's _project_audit_event re-tags rows with the same tenant the chain was originally hashed under — so verify_chain round-trips cleanly.

Round-trip metadata preservation

Rows that carry _cloud_id / _cloud_workspace_id (stamped by a prior import) reuse those values rather than synthesising new ones — so a cloud → OSS → export cycle preserves the same cloud UUIDs. OSS-only rows that never round-tripped get UUID5 over (table, row_id) via a frozen namespace for stable hashes.

No second canonicaliser

Reuses attestix.auth.crypto.canonicalize_json everywhere (manifest sha-256, JSONL row sha-256, audit-chain integrity check).

Test plan

  • 519 tests pass (507 → 519, +12 new). 2 skipped unchanged. Full suite 35–38 s.
  • New tests:
    • tests/portability/test_export_import_roundtrip.py (6) — seed → export → import → row-count and chain-integrity match; empty store; partial workspace export; --no-include-anchors; deterministic byte-identical re-export.
    • tests/cli/test_export_command.py (6) — CLI writes file + exit 0; refuses on existing target; --force proceeds; round-trip through attestix import --verify-only.
  • Bundle size from a representative seeded store (5 agents + 5 credentials + 5 compliance profiles + 20 audit events): 6,654 bytes total (5.4KB credentials + 4.6KB compliance + 14KB audit, gzip ~3–5x).

Deferred to next slice

  • .tar.zst output (cloud worker is also .tar.gz only today — P2 parity).
  • Streaming export for very large stores (current matches importer's 256 MiB cap).
  • website/content/docs/portability/export.mdx doc page.

…s (closes symmetric portability)

Adds the OSS-side exporter symmetric to the importer that landed in PR #93.
Closes the persona-review critical: OSS users could import but not export,
violating the constitutional zero-lock-in promise.

- New attestix export <output.tar.gz> CLI subcommand with --workspace,
  --include-anchors/--include-audit, --force, --no-pretty options.
- New attestix/portability/bundle_writer.py implements write_bundle() as the
  inverse of importer.py - reads through Repository, projects each OSS row to
  the cloud wire-format shape, JCS-canonicalises via the shared
  auth.crypto.canonicalize_json (no second canonicaliser), and emits a USTAR
  + gzip tarball that bundle_reader + Importer accept byte-cleanly.
- Deterministic output: alphabetical member order, USTAR mtime=0, gzip
  header mtime=0; synthetic cloud UUIDs derived via UUID5 over row content
  so two consecutive exports of the same OSS state are byte-identical.
- Audit-chain integrity: workspace.slug is inferred from the audit_events
  collection's tenant (or --workspace override) so the importer's chain
  re-verification round-trips cleanly.
- EXPORT_PLAN mirrors the cloud worker's EXPORT_TABLE_SPECS order so the
  manifest tables array and on-disk member order are byte-comparable across
  cloud and OSS implementations.
- 12 new tests (6 round-trip + 6 CLI smoke). Suite goes 507 -> 519 passing.
@coderabbitai

coderabbitai Bot commented May 28, 2026

Copy link
Copy Markdown

Warning

Review limit reached

@ascender1729, we couldn't start this review because you've reached your PR review rate limit.

More reviews will be available in 39 minutes and 4 seconds. Learn how PR review limits work.

Your organization has run out of usage credits. Purchase more in the billing tab.

⌛ How to resolve this issue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans include higher PR review limits than trial, open-source, and free plans. In all cases, reviews become available again over time. During sustained high-volume PR review activity, CodeRabbit may temporarily slow when the next review becomes available.

Please see our Fair Usage Limits Policy for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 13409ee2-1057-47a4-a26b-f0205a81937d

📥 Commits

Reviewing files that changed from the base of the PR and between 653d61f and da2bda4.

📒 Files selected for processing (5)
  • attestix/cli.py
  • attestix/portability/__init__.py
  • attestix/portability/bundle_writer.py
  • tests/cli/test_export_command.py
  • tests/portability/test_export_import_roundtrip.py

Note

.coderabbit.yaml has unrecognized properties

CodeRabbit is using all valid settings from your configuration. Unrecognized properties (listed below) have been ignored and may indicate typos or deprecated fields that can be removed.

⚠️ Parsing warnings (1)
Validation error: Unrecognized key: "ignore"
⚙️ Configuration instructions
  • Please see the configuration documentation for more information.
  • You can also validate your configuration using the online YAML validator.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feature/cli-bundle-export

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.

❤️ Share

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

@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented May 28, 2026

Copy link
Copy Markdown

Deploying attestix with  Cloudflare Pages  Cloudflare Pages

Latest commit: da2bda4
Status: ✅  Deploy successful!
Preview URL: https://be60bd7c.attestix.pages.dev
Branch Preview URL: https://feature-cli-bundle-export.attestix.pages.dev

View logs

import gzip
import hashlib
import io
import json

import hashlib

import pytest

from __future__ import annotations

import pytest
Comment on lines +32 to +37
from attestix.portability import (
Bundle,
Importer,
read_bundle,
write_bundle,
)
the FileRepository state by clearing the documents (the fixture's tmp_path
persists for the duration of the test), then import and re-verify.
"""
seeded = _seed_oss_data()
audit_count_pre = _count_audit_rows()

out = tmp_path / "seeded.tar.gz"
result = write_bundle(out)
@ascender1729 ascender1729 merged commit cf55a3d into main May 28, 2026
19 checks passed
@ascender1729 ascender1729 deleted the feature/cli-bundle-export branch May 28, 2026 08:31
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.

1 participant