feat: add ocas export / import — CAS closure bundling #91

Merged
xiaomo merged 2 commits from fix/83-export-import into main 2026-06-07 03:48:38 +00:00
Owner

What

Adds self-contained CAS closure bundling for ocas:

  • ocas export <root> [<root> ...] -o <bundle.tar> — package a closure (nodes + vars + tags) into a tar bundle
  • ocas import <bundle.tar> [--scope @newscope] — restore a bundle into any store, with optional scope remap
  • Global --store <bundle.tar> flag — read-only inspection of a bundle as if it were a regular store
  • New public @ocas/core exports: computeClosure, exportBundle, importBundle, loadBundleStore
  • New builtin output schemas: @ocas/output/export, @ocas/output/import

Why

Closes #83. Enables shipping reproducible CAS slices (workflows, schemas, configs) between machines / users / environments without requiring a shared store. Uses content-addressed dedup on import so re-importing is a safe no-op.

Changes

  • @ocas/core
    • closure.tscomputeClosure(store, roots) walks refs + schema chains, gathers vars/tags
    • bundle.tsexportBundle() / importBundle() / loadBundleStore() with a custom POSIX/ustar tar implementation (no external deps)
    • bootstrap.ts — registers @ocas/output/export / @ocas/output/import
  • @ocas/cli
    • export and import commands; --store and --scope flags
    • Write commands rejected with a clear error when --store is set
  • Tests: closure.test.ts, bundle.test.ts, export-import.test.ts covering all spec scenarios — 683/683 tests pass
  • Docs: root README.md, packages/cli/README.md, packages/core/README.md, packages/cli/prompts/usage.md, .cards/cli.md updated
  • Changeset: .changeset/export-import-bundle.md (minor for @ocas/core and @ocas/cli)

Ref

Fixes #83

## What Adds self-contained CAS closure bundling for `ocas`: - `ocas export <root> [<root> ...] -o <bundle.tar>` — package a closure (nodes + vars + tags) into a tar bundle - `ocas import <bundle.tar> [--scope @newscope]` — restore a bundle into any store, with optional scope remap - Global `--store <bundle.tar>` flag — read-only inspection of a bundle as if it were a regular store - New public `@ocas/core` exports: `computeClosure`, `exportBundle`, `importBundle`, `loadBundleStore` - New builtin output schemas: `@ocas/output/export`, `@ocas/output/import` ## Why Closes #83. Enables shipping reproducible CAS slices (workflows, schemas, configs) between machines / users / environments without requiring a shared store. Uses content-addressed dedup on import so re-importing is a safe no-op. ## Changes - `@ocas/core` - `closure.ts` — `computeClosure(store, roots)` walks refs + schema chains, gathers vars/tags - `bundle.ts` — `exportBundle()` / `importBundle()` / `loadBundleStore()` with a custom POSIX/ustar tar implementation (no external deps) - `bootstrap.ts` — registers `@ocas/output/export` / `@ocas/output/import` - `@ocas/cli` - `export` and `import` commands; `--store` and `--scope` flags - Write commands rejected with a clear error when `--store` is set - Tests: `closure.test.ts`, `bundle.test.ts`, `export-import.test.ts` covering all spec scenarios — **683/683 tests pass** - Docs: root `README.md`, `packages/cli/README.md`, `packages/core/README.md`, `packages/cli/prompts/usage.md`, `.cards/cli.md` updated - Changeset: `.changeset/export-import-bundle.md` (minor for `@ocas/core` and `@ocas/cli`) ## Ref Fixes #83
xiaoju added 2 commits 2026-06-07 03:37:09 +00:00
Implements `ocas export` / `ocas import` for shipping a self-contained
closure of CAS nodes, variables and tags between stores, plus a
read-only `--store <bundle.tar>` flag for inspecting bundles without
extracting them.

- core: computeClosure walks refs + schema chains and gathers vars/tags
- core: exportBundle / importBundle / loadBundleStore use a custom
  POSIX/ustar tar (no external deps); content-addressed dedup on import,
  optional --scope remap of non-@ocas variable names
- core: new @ocas/output/export and @ocas/output/import builtin schemas
- cli: new export and import commands, --store read-only mode, write
  commands rejected with a clear error when --store is set

Closes #83
Address reviewer feedback on #83:

- Replace dynamic `await import("./bootstrap-capable.js")` in
  bundle.ts with a static top-of-file import (no real cycle exists,
  bootstrap.ts is already statically imported).
- Remove unused `bootstrapSym` Symbol.for() / `void bootstrapSym`
  dead code in importBundle.
- Move the misplaced `import { decode } from "cborg"` to the top of
  bundle.ts with the other imports.
- Document the new `export` / `import` commands and `--store`,
  `--scope`, `-o` flags in:
  - root README.md (commands + global flags)
  - packages/cli/README.md (command table + bundles section + global flags)
  - packages/cli/prompts/usage.md (`ocas prompt usage` output)
  - packages/core/README.md (Closure & Bundles API surface)
  - .cards/cli.md (bundle commands + --store architecture note)
xiaomo approved these changes 2026-06-07 03:46:27 +00:00
xiaomo left a comment
Owner

LGTM 仔细过了一遍,2100 行 diff 质量很高。

核心逻辑

closure.ts — 5 阶段遍历设计清晰:refs walk → schema chain → template vars → variable collection → tag collection。用现有 walk() BFS(自带 visited set 防环),meta-schema 自引用终止条件正确。

bundle.ts — POSIX tar 实现干净,512 字节对齐 + checksum + end-of-archive markers。导出确定性排序(hash/name/target),导入用收敛循环解决类型依赖顺序。remapVarName() 正确保护 @ocas/* builtins。BOOTSTRAP_STORE symbol 用对了。

CLI

WRITE_COMMANDS set 覆盖完整,ensureWritable() 在 dispatch 前 fail-fast,--store 只允许读命令。export 正确不在写命令里(读 store 写文件)。

测试

40 个测试覆盖很扎实:

  • Test 2.8 验证幂等重导入(0 imported, N skipped)
  • Test 2.11 验证 scope remap 不动 @ocas/* builtins
  • Test 2.2 验证 CBOR binary identity
  • Test 3.14 验证 --store 下写命令被拒

Non-blocking observations

  1. Import 丢失原始 timestampput()Date.now(),bundle 里的 node.timestamp 被丢弃。不影响 hash 正确性,但 import 后的 created 时间会变。如果在意审计场景可以考虑 putWithTimestamp 路径,不过作为 v1 完全可以接受
  2. Bundle decode 无运行时验证as CasNode / as Variable 直接 cast。自产自消场景没问题,后续可以加 schema validation 做 hardening
  3. async 函数内部全是同步 I/O — 猜测是 future-proofing,可以加个注释说明
  4. 建议补一个 invalid tar import 测试 — 用户迟早会喂错文件进来

这个 PR 从 #83 issue 到 merge 经历了不少波折(frontmatter 失败 → #139 修复 → session resume → reviewer reject 补文档),最终产出非常扎实。可以合 🍊

LGTM ✅ 仔细过了一遍,2100 行 diff 质量很高。 ## 核心逻辑 **closure.ts** — 5 阶段遍历设计清晰:refs walk → schema chain → template vars → variable collection → tag collection。用现有 `walk()` BFS(自带 visited set 防环),meta-schema 自引用终止条件正确。 **bundle.ts** — POSIX tar 实现干净,512 字节对齐 + checksum + end-of-archive markers。导出确定性排序(hash/name/target),导入用收敛循环解决类型依赖顺序。`remapVarName()` 正确保护 `@ocas/*` builtins。`BOOTSTRAP_STORE` symbol 用对了。 ## CLI `WRITE_COMMANDS` set 覆盖完整,`ensureWritable()` 在 dispatch 前 fail-fast,`--store` 只允许读命令。export 正确不在写命令里(读 store 写文件)。 ## 测试 40 个测试覆盖很扎实: - Test 2.8 验证幂等重导入(0 imported, N skipped)✅ - Test 2.11 验证 scope remap 不动 `@ocas/*` builtins ✅ - Test 2.2 验证 CBOR binary identity ✅ - Test 3.14 验证 `--store` 下写命令被拒 ✅ ## Non-blocking observations 1. **Import 丢失原始 timestamp** — `put()` 用 `Date.now()`,bundle 里的 `node.timestamp` 被丢弃。不影响 hash 正确性,但 import 后的 `created` 时间会变。如果在意审计场景可以考虑 `putWithTimestamp` 路径,不过作为 v1 完全可以接受 2. **Bundle decode 无运行时验证** — `as CasNode` / `as Variable` 直接 cast。自产自消场景没问题,后续可以加 schema validation 做 hardening 3. **`async` 函数内部全是同步 I/O** — 猜测是 future-proofing,可以加个注释说明 4. **建议补一个 invalid tar import 测试** — 用户迟早会喂错文件进来 这个 PR 从 #83 issue 到 merge 经历了不少波折(frontmatter 失败 → #139 修复 → session resume → reviewer reject 补文档),最终产出非常扎实。可以合 🍊
xiaomo merged commit 544226041e into main 2026-06-07 03:48:38 +00:00
xiaomo deleted branch fix/83-export-import 2026-06-07 03:48:38 +00:00
Sign in to join this conversation.
No Reviewers
No Label
2 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: shazhou/ocas#91