soft3/zheng/.claude/plans/minimal-zheng-structure-and-cli.md

finishing minimal zheng: canonical structure + CLI

status: DONE (both parts implemented, all tests green). part 1 — canonical rs/ + cli/ workspace, 5 downstream path deps updated, nox Reduction API adaptation. part 2 — zheng CLI (run/demo/eval/pack/prove/help) emitting a tape chunk stream; demo hash and eval prove+verify green; general run reports precise opening-data needs (axis/hash/look) since real formulas touch the axis pattern — that aux derivation stays deferred (see specs/cli.md).

two tasks to close out "minimal zheng": (1) refactor the repo to the canonical rs/ + cli/ workspace layout used by every sibling; (2) design and build a minimal CLI. the library itself is already the minimal release — release-plan phases 0–5 are implemented and the two soundness gaps are closed. what remains is packaging + a command-line face.


part 1 — canonical structure refactor

current (outlier)

zheng is the only stack repo with a package crate at the top and bare src/:

zheng/
  Cargo.toml        # [package] name = "zheng"
  Cargo.lock
  src/              # lib.rs + modules
  specs/ docs/ roadmap/ .claude/ CLAUDE.md README.md .github/

nox and hemera both use a [workspace] at the top with rs/ (library) and cli/ (binary) members. nox is the closest sibling (proof-native, minimal ["rs","cli"] workspace, license = "Cyber", package name kept — not renamed to cyber-*). we mirror nox, but keep the src/ directory (zheng already has one) rather than nox's bare rs/*.rs — least churn, still canonical.

target

zheng/
  Cargo.toml        # [workspace] members = ["rs", "cli"], resolver = "3"
  rs/
    Cargo.toml      # [package] name = "zheng"  (moved from top, paths fixed)
    src/            # lib.rs + modules  (moved from top-level src/)
  cli/
    Cargo.toml      # [package] name = "zheng-cli", bin name = "zheng"
    src/main.rs
  specs/ docs/ roadmap/ .claude/ CLAUDE.md README.md LICENSE.md CHANGELOG.md

steps

  1. create top-level workspace Cargo.toml (preserve YAML frontmatter):
    [workspace]
    members = ["rs", "cli"]
    resolver = "3"
    
  2. move library: git mv src rs/src, git mv Cargo.toml rs/Cargo.toml. keep package name zheng, version 0.1.0, license = "Cyber".
  3. fix path deps in rs/Cargo.toml — one level deeper (../../../):
    • nebupath = "../../strata/nebu/rs" (package strata-nebu)
    • hemerapath = "../../hemera/rs" (package cyber-hemera)
    • noxpath = "../../nox/rs" (features ["brakedown"])
    • lenspath = "../../lens/src" (package cyber-lens)
  4. create cli/ crate — see part 2 for contents. package zheng-cli, bin name = "zheng", path = "src/main.rs"; depends on zheng = { path = "../rs" }, nox = { path = "../../nox/rs", features = ["brakedown"] }, nebu = { package = "strata-nebu", path = "../../strata/nebu/rs" }.
  5. add LICENSE.md — link to canonical Cyber license source (per CLAUDE.md repo-layout spec; manifests keep the short license = "Cyber" field, matching nox).
  6. add CHANGELOG.md — seed with 0.1.0 (SuperSpartan + Brakedown + sumcheck
    • HyperNova folding; soundness gaps closed; canonical layout; CLI).
  7. Cargo.lock — regenerate at workspace root (cargo build); delete stale top-level lock if it moved.
  8. downstream sync: bbg sits above zheng in the stack (hemera→lens→nox→zheng→bbg). grep bbg for a zheng path dep; if it points at ../zheng, update to ../zheng/rs. crate name is unchanged, so only the path moves. flag in commit.
  9. verify: cargo test green at workspace root; cargo build -p zheng-cli.

verification

  • cargo test — all 87 existing tests still pass after the move.
  • cargo build --workspace — clean.
  • confirm target/ stays at workspace root (already gitignored).

part 2 — CLI design

the serialization constraint (why the CLI is in-process)

nothing in the stack serializes: zheng Proof/TraceProof, nox VecTrace, lens Commitment/Opening all lack serde and any codec. a full offline prove → proof.bin → verify would need a canonical binary codec across the whole proof (incl. lens's opaque Opening), touching lens's frozen interface. that is a separate milestone, not minimal.

what IS cheap to serialize: the inputs — a trace is Vec<[u64;16]>, a statement is 3×32 bytes + u64. and nox's own CLI runs in-process (no trace files). so the minimal CLI mirrors nox: drive formula → trace → proof → verify in one process, persist only the cheap inputs, report stats. proof-file serialization is deferred (see below).

spec first

per CLAUDE.md ("spec before code"), write specs/cli.md (canonical CLI reference: commands, flags, exit codes, trace file format) BEFORE implementing.

commands (minimal set)

command does serialization
zheng run formula (-e inline / file / stdin) → nox::reducecommitverify → report (proof groups, proof size est., verify ok/fail, timing) none — in-process
zheng trace formula → VecTrace → canonical .trace file (16×u64 LE per row, row count header) writes trace (new codec, trivial)
zheng prove .trace file → commitverify → report stats reads trace; proof stays in-process
zheng eval commit a polynomial, open at a point, verify_eval — PCS demo none
zheng help / --help usage

zheng run is the headline: proves the whole pipeline works from the shell with zero new codec. modeled on e2e_hash_accumulator_roundtrip (rs/src/lib.rs), the most complete end-to-end example.

formula input

reuse nox CLI's formula parser (nox/cli/main.rs parses -e '<formula>', file, or stdin into an Order + object/formula nouns). zheng run calls the same path, then feeds the trace to zheng::commit. statement built from real trace rows via hash_row (input = first row, output = last row); focus_bound from --budget or 0.

trace file format (.trace, canonical)

deterministic, single valid encoding (quality pass 1):

magic "ZTRC" (4B) | version u8 | row_count u32 LE | rows...
row = 16 × u64 LE  (128 bytes)

write in zheng trace, read in zheng prove. lives in a small cli/src/trace_io.rs. keeps main.rs under the 500-line limit.

arg parsing

hand-rolled (matches nox 294-line and hemera 1081-line CLIs — no clap in the stack). main.rs dispatches on argv[1]; each command a small fn. split into cli/src/{main.rs, trace_io.rs, report.rs} if main.rs approaches 500 lines.

output / reporting

report.rs prints: number of CCS groups, per-group step count, estimated proof size (bytes), verify result, wall-clock for commit + verify. honest numbers only — no fabricated metrics (CLAUDE.md honesty). if proof-size estimation isn't exact yet, print what's measurable (group/step counts) and mark size as "est." explicitly.

verification

  • zheng run -e '<add formula>' exits 0, reports verify ok.
  • zheng trace -e '<f>' -o t.trace && zheng prove t.trace round-trips.
  • tampered .trace (flip a byte) → zheng prove reports verify fail, exits nonzero.
  • zheng eval commits/opens/verifies a small poly.

deferred (explicitly NOT in minimal)

  • offline proof serialization (prove → .proof, standalone verify <file>): needs a canonical codec for TraceProof incl. lens Commitment/Opening. requires lens to expose canonical encoding (cross-repo, frozen interface). its own milestone; specs/cli.md notes the reserved .proof format.
  • clap / rich arg parsing — hand-rolled matches the stack.
  • proof-size hitting the ~2 KiB spec target — that's release-plan phase 7 (benchmarks + optimization), separate from packaging.

sequencing

  1. part 1 structure refactor (mechanical, one commit) → cargo test green.
  2. specs/cli.md (spec before code).
  3. cli/ crate: zheng run first (no codec), then trace/prove, then eval.
  4. CHANGELOG + LICENSE.md + README update (document zheng binary usage).

estimate: structure refactor ~1 pomodoro; specs/cli.md ~1; CLI impl ~2–3. ~1 session total.

housekeeping

close-soundness-gaps.md is DONE (this session — Gap 1 closed, Gap 2 partial). delete it when this plan is signed off, to stay within the .claude/ 1000-line budget.

Graph