Skip to content

feat(cli): emit the command surface as data via batten spec - #45

Merged
wenzowski merged 1 commit into
mainfrom
claude/ci-watch-handoff-3abw65
Aug 6, 2026
Merged

wenzowski merged 1 commit into
mainfrom
claude/ci-watch-handoff-3abw65

Conversation

@wenzowski

Copy link
Copy Markdown
Contributor

Lands the CLI-as-data spine validated by the CLOUD-20 spike — the first build work off the now-cleared Phase-0 blockers (CLOUD-6).

What

The command surface is defined once as a clap tree and emitted at runtime as byte-stable JSON by introspecting the live clap::Command (house-style §11), with the §5 effect model merged in from a single table keyed by full command path.

  • effect.rs — the Effect enum + one path-keyed table. Fail-safe by construction: a path absent from the table resolves to ask, never read.
  • spec.rs — runtime introspection into a byte-stable CommandSpec, plus the derived read-only allowlist (CLOUD-28): filter(effect == read) over the same walk, never a second hand-kept list.
  • cli.rs — the spec [--format json] verb. The tree grows one verb at a time; each addition is kept honest by a completeness test that fails if any command ships without an effect.
  • run() now writes data output to an injected writer, so the library stays print!-free and byte-stable (the workspace lints deny print_stdout/print_stderr); the binary passes stdout.

Tests

  • batten spec emits parseable JSON on stdout, describes itself with effect: "read", is byte-stable across runs, and --format json matches the default.
  • Unit: fail-safe (effect_for(unknown) == ask), completeness (no command resolves to ask), and the derived allowlist is exactly the read commands.
  • 7 lib + 6 integration tests pass; mise run ci green locally.

Scope

Establishes the spine and the spec verb; subsequent verbs from §2 land in their own issues, each adding its effect-table entry (guarded by the completeness test). --format kdl (§2) and the completions/man/markdown generation + drift test (CLOUD-69) are follow-ups.

Refs CLOUD-26, CLOUD-27, CLOUD-28.

🤖 Generated with Claude Code


Generated by Claude Code

@linear-code

linear-code Bot commented Aug 6, 2026 •

Copy link
Copy Markdown
CLOUD-26 Create the `tools/batten/` crate and keep it repo-agnostic from line one

Why
This is the extraction target and must not embed repo-specific names or concepts.

Definition of done

  • Add a new crate under tools/batten/
  • Ensure cargo build -p batten succeeds
  • Start with an empty command tree

Acceptance

  • Source grep finds no repo-specific identifiers such as button, compliance, etaxbc, or entities/ in tools/batten/src

CLOUD-27 Define the CLI as data with a single usage spec as source of truth

Why
The command surface, docs, completions, and safety annotations must come from one spec rather than hand-written copies.

Definition of done

  • Define the command tree once
  • Give every subcommand an effect and human summary
  • Preserve the fail-safe rules: effect is not inherited, missing effect means ask rather than read, and flags may only raise effect rather than lower it

Acceptance

  • A test regenerates completions from the spec and diffs against the committed copy byte-for-byte
  • A test proves no subcommand with missing effect is silently treated as read-only

CLOUD-28 Derive the agent read-only allowlist from effect annotations

Why
The read-only allowlist must be derived from command effects rather than maintained as a second hand-written list.

Definition of done

  • Build the adapter allowlist by filtering the command spec for read-only effects

Acceptance

  • Source grep finds only the derivation and no second literal read-only list

Review in Linear

@wenzowski
wenzowski marked this pull request as ready for review August 6, 2026 08:29

Copy link
Copy Markdown
Contributor Author

/fast-forward


Generated by Claude Code

@wenzowski

Copy link
Copy Markdown
Contributor Author

Triggered from #45 (comment) by @​wenzowski.

Trying to fast forward main (104ad62) to claude/ci-watch-handoff-3abw65 (22fd17e).

Target branch (main):

commit 104ad62586946d157b08052c33e2324c967686dd (HEAD -> main, origin/main)
Author: Claude <noreply@anthropic.com>
Date:   Thu Aug 6 08:27:55 2026 +0000

    fix(ci): authenticate git so release-plz can rebase onto main
    
    release-plz clones the repo into a temp dir and runs `git fetch origin
    main` there to rebase the release branch onto the current main. That
    clone has no credentials — actions/checkout's auth lives only on the main
    checkout — so the fetch failed ("could not read Username for
    github.com"), release-plz gave up rebasing, and re-opened the release PR
    on a stale base (the first commit after the last release). The branch
    then diverged from main and could never fast-forward, so auto-release-land
    kept posting /fast-forward against a non-linear branch and failing.
    
    Add a global `url.insteadOf` credential for github.com (from the release
    PAT / job token) so release-plz's internal clone can fetch main, rebase
    the release branch to `main + release commit`, and land by fast-forward.
    
    Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
    Claude-Session: https://claude.ai/code/session_01TdVURXVaz6yYmrnRW35g3B

Pull request (claude/ci-watch-handoff-3abw65):

commit 22fd17e4e16fde8f2e2bdbf736869e9914f60f45 (pull_request/claude/ci-watch-handoff-3abw65)
Author: Claude <noreply@anthropic.com>
Date:   Thu Aug 6 08:28:52 2026 +0000

    feat(cli): emit the command surface as data via `batten spec`
    
    Land the CLI-as-data spine validated by the CLOUD-20 spike. The command
    surface is defined once as a clap tree and emitted at runtime as byte-stable
    JSON by introspecting the live `clap::Command` (house-style §11), with the
    §5 effect model merged in from a single table keyed by full command path.
    
    - effect.rs: the Effect enum + one path-keyed table, fail-safe by
      construction (a path absent from the table resolves to `ask`, never `read`).
    - spec.rs: runtime introspection into a byte-stable spec, plus the derived
      read-only allowlist (CLOUD-28) — filter(effect == read) over the same walk,
      never a second hand-kept list.
    - cli.rs: the `spec [--format json]` verb; the tree grows one verb at a time,
      each addition kept honest by a completeness test that fails if any command
      ships without an effect.
    - run() now writes data output to an injected writer so the library stays
      print-free and byte-stable; the binary passes stdout.
    
    Refs CLOUD-26, CLOUD-27, CLOUD-28.

Can't fast forward main (104ad62) to claude/ci-watch-handoff-3abw65 (22fd17e). main (104ad62) is not a direct ancestor of claude/ci-watch-handoff-3abw65 (22fd17e). Branches appear to have diverged at a4d8e04:

* 22fd17e4e16fde8f2e2bdbf736869e9914f60f45 feat(cli): emit the command surface as data via `batten spec`
| * 104ad62586946d157b08052c33e2324c967686dd fix(ci): authenticate git so release-plz can rebase onto main
|/  
* a4d8e04daec2beec627ba93e12e144cc85ec2275 fix(ci): read checks with GITHUB_TOKEN in auto-release-land

commit a4d8e04daec2beec627ba93e12e144cc85ec2275
Author: Claude <noreply@anthropic.com>
Date:   Thu Aug 6 08:10:39 2026 +0000

    fix(ci): read checks with GITHUB_TOKEN in auto-release-land
    
    The green-check verification called the check-runs API with
    RELEASE_PLZ_TOKEN, a fine-grained PAT that lacks checks:read, so it 403'd
    and the job failed. Split the tokens: read check status with the Actions
    GITHUB_TOKEN (grant it checks:read), and post `/fast-forward` with
    RELEASE_PLZ_TOKEN — the owner identity that actually triggers
    fast-forward.yml.
    
    Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
    Claude-Session: https://claude.ai/code/session_01TdVURXVaz6yYmrnRW35g3B

Rebase locally, and then force push to claude/ci-watch-handoff-3abw65.

Land the CLI-as-data spine validated by the CLOUD-20 spike. The command
surface is defined once as a clap tree and emitted at runtime as byte-stable
JSON by introspecting the live `clap::Command` (house-style §11), with the
§5 effect model merged in from a single table keyed by full command path.

- effect.rs: the Effect enum + one path-keyed table, fail-safe by
  construction (a path absent from the table resolves to `ask`, never `read`).
- spec.rs: runtime introspection into a byte-stable spec, plus the derived
  read-only allowlist (CLOUD-28) — filter(effect == read) over the same walk,
  never a second hand-kept list.
- cli.rs: the `spec [--format json]` verb; the tree grows one verb at a time,
  each addition kept honest by a completeness test that fails if any command
  ships without an effect.
- run() now writes data output to an injected writer so the library stays
  print-free and byte-stable; the binary passes stdout.

Refs CLOUD-26, CLOUD-27, CLOUD-28.
@wenzowski

Copy link
Copy Markdown
Contributor Author

Triggered from #45 (comment) by @​wenzowski.

Trying to fast forward main (1f814e7) to claude/ci-watch-handoff-3abw65 (22fd17e).

Target branch (main):

commit 1f814e78b08f28546333e86c8f4c13fba6a2d30e (HEAD -> main, origin/main)
Author: Alec Wenzowski <alec@button.is>
Date:   Thu Aug 6 01:30:17 2026 -0700

    chore: release v0.0.2

Pull request (claude/ci-watch-handoff-3abw65):

commit 22fd17e4e16fde8f2e2bdbf736869e9914f60f45 (pull_request/claude/ci-watch-handoff-3abw65)
Author: Claude <noreply@anthropic.com>
Date:   Thu Aug 6 08:28:52 2026 +0000

    feat(cli): emit the command surface as data via `batten spec`
    
    Land the CLI-as-data spine validated by the CLOUD-20 spike. The command
    surface is defined once as a clap tree and emitted at runtime as byte-stable
    JSON by introspecting the live `clap::Command` (house-style §11), with the
    §5 effect model merged in from a single table keyed by full command path.
    
    - effect.rs: the Effect enum + one path-keyed table, fail-safe by
      construction (a path absent from the table resolves to `ask`, never `read`).
    - spec.rs: runtime introspection into a byte-stable spec, plus the derived
      read-only allowlist (CLOUD-28) — filter(effect == read) over the same walk,
      never a second hand-kept list.
    - cli.rs: the `spec [--format json]` verb; the tree grows one verb at a time,
      each addition kept honest by a completeness test that fails if any command
      ships without an effect.
    - run() now writes data output to an injected writer so the library stays
      print-free and byte-stable; the binary passes stdout.
    
    Refs CLOUD-26, CLOUD-27, CLOUD-28.

Can't fast forward main (1f814e7) to claude/ci-watch-handoff-3abw65 (22fd17e). main (1f814e7) is not a direct ancestor of claude/ci-watch-handoff-3abw65 (22fd17e). Branches appear to have diverged at a4d8e04:

* 1f814e78b08f28546333e86c8f4c13fba6a2d30e chore: release v0.0.2
* 104ad62586946d157b08052c33e2324c967686dd fix(ci): authenticate git so release-plz can rebase onto main
| * 22fd17e4e16fde8f2e2bdbf736869e9914f60f45 feat(cli): emit the command surface as data via `batten spec`
|/  
* a4d8e04daec2beec627ba93e12e144cc85ec2275 fix(ci): read checks with GITHUB_TOKEN in auto-release-land

commit a4d8e04daec2beec627ba93e12e144cc85ec2275
Author: Claude <noreply@anthropic.com>
Date:   Thu Aug 6 08:10:39 2026 +0000

    fix(ci): read checks with GITHUB_TOKEN in auto-release-land
    
    The green-check verification called the check-runs API with
    RELEASE_PLZ_TOKEN, a fine-grained PAT that lacks checks:read, so it 403'd
    and the job failed. Split the tokens: read check status with the Actions
    GITHUB_TOKEN (grant it checks:read), and post `/fast-forward` with
    RELEASE_PLZ_TOKEN — the owner identity that actually triggers
    fast-forward.yml.
    
    Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
    Claude-Session: https://claude.ai/code/session_01TdVURXVaz6yYmrnRW35g3B

Rebase locally, and then force push to claude/ci-watch-handoff-3abw65.

@wenzowski

Copy link
Copy Markdown
Contributor Author

Triggered from #45 (comment) by @​wenzowski.

Trying to fast forward main (1f814e7) to claude/ci-watch-handoff-3abw65 (22fd17e).

Target branch (main):

commit 1f814e78b08f28546333e86c8f4c13fba6a2d30e (HEAD -> main, origin/main)
Author: Alec Wenzowski <alec@button.is>
Date:   Thu Aug 6 01:30:17 2026 -0700

    chore: release v0.0.2

Pull request (claude/ci-watch-handoff-3abw65):

commit 22fd17e4e16fde8f2e2bdbf736869e9914f60f45 (pull_request/claude/ci-watch-handoff-3abw65)
Author: Claude <noreply@anthropic.com>
Date:   Thu Aug 6 08:28:52 2026 +0000

    feat(cli): emit the command surface as data via `batten spec`
    
    Land the CLI-as-data spine validated by the CLOUD-20 spike. The command
    surface is defined once as a clap tree and emitted at runtime as byte-stable
    JSON by introspecting the live `clap::Command` (house-style §11), with the
    §5 effect model merged in from a single table keyed by full command path.
    
    - effect.rs: the Effect enum + one path-keyed table, fail-safe by
      construction (a path absent from the table resolves to `ask`, never `read`).
    - spec.rs: runtime introspection into a byte-stable spec, plus the derived
      read-only allowlist (CLOUD-28) — filter(effect == read) over the same walk,
      never a second hand-kept list.
    - cli.rs: the `spec [--format json]` verb; the tree grows one verb at a time,
      each addition kept honest by a completeness test that fails if any command
      ships without an effect.
    - run() now writes data output to an injected writer so the library stays
      print-free and byte-stable; the binary passes stdout.
    
    Refs CLOUD-26, CLOUD-27, CLOUD-28.

Can't fast forward main (1f814e7) to claude/ci-watch-handoff-3abw65 (22fd17e). main (1f814e7) is not a direct ancestor of claude/ci-watch-handoff-3abw65 (22fd17e). Branches appear to have diverged at a4d8e04:

* 1f814e78b08f28546333e86c8f4c13fba6a2d30e chore: release v0.0.2
* 104ad62586946d157b08052c33e2324c967686dd fix(ci): authenticate git so release-plz can rebase onto main
| * 22fd17e4e16fde8f2e2bdbf736869e9914f60f45 feat(cli): emit the command surface as data via `batten spec`
|/  
* a4d8e04daec2beec627ba93e12e144cc85ec2275 fix(ci): read checks with GITHUB_TOKEN in auto-release-land

commit a4d8e04daec2beec627ba93e12e144cc85ec2275
Author: Claude <noreply@anthropic.com>
Date:   Thu Aug 6 08:10:39 2026 +0000

    fix(ci): read checks with GITHUB_TOKEN in auto-release-land
    
    The green-check verification called the check-runs API with
    RELEASE_PLZ_TOKEN, a fine-grained PAT that lacks checks:read, so it 403'd
    and the job failed. Split the tokens: read check status with the Actions
    GITHUB_TOKEN (grant it checks:read), and post `/fast-forward` with
    RELEASE_PLZ_TOKEN — the owner identity that actually triggers
    fast-forward.yml.
    
    Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
    Claude-Session: https://claude.ai/code/session_01TdVURXVaz6yYmrnRW35g3B

Rebase locally, and then force push to claude/ci-watch-handoff-3abw65.

@wenzowski

Copy link
Copy Markdown
Contributor Author

Triggered from #45 (comment) by @​wenzowski.

Trying to fast forward main (1f814e7) to claude/ci-watch-handoff-3abw65 (22fd17e).

Target branch (main):

commit 1f814e78b08f28546333e86c8f4c13fba6a2d30e (HEAD -> main, tag: v0.0.2, origin/main)
Author: Alec Wenzowski <alec@button.is>
Date:   Thu Aug 6 01:30:17 2026 -0700

    chore: release v0.0.2

Pull request (claude/ci-watch-handoff-3abw65):

commit 22fd17e4e16fde8f2e2bdbf736869e9914f60f45 (pull_request/claude/ci-watch-handoff-3abw65)
Author: Claude <noreply@anthropic.com>
Date:   Thu Aug 6 08:28:52 2026 +0000

    feat(cli): emit the command surface as data via `batten spec`
    
    Land the CLI-as-data spine validated by the CLOUD-20 spike. The command
    surface is defined once as a clap tree and emitted at runtime as byte-stable
    JSON by introspecting the live `clap::Command` (house-style §11), with the
    §5 effect model merged in from a single table keyed by full command path.
    
    - effect.rs: the Effect enum + one path-keyed table, fail-safe by
      construction (a path absent from the table resolves to `ask`, never `read`).
    - spec.rs: runtime introspection into a byte-stable spec, plus the derived
      read-only allowlist (CLOUD-28) — filter(effect == read) over the same walk,
      never a second hand-kept list.
    - cli.rs: the `spec [--format json]` verb; the tree grows one verb at a time,
      each addition kept honest by a completeness test that fails if any command
      ships without an effect.
    - run() now writes data output to an injected writer so the library stays
      print-free and byte-stable; the binary passes stdout.
    
    Refs CLOUD-26, CLOUD-27, CLOUD-28.

Can't fast forward main (1f814e7) to claude/ci-watch-handoff-3abw65 (22fd17e). main (1f814e7) is not a direct ancestor of claude/ci-watch-handoff-3abw65 (22fd17e). Branches appear to have diverged at a4d8e04:

* 1f814e78b08f28546333e86c8f4c13fba6a2d30e chore: release v0.0.2
* 104ad62586946d157b08052c33e2324c967686dd fix(ci): authenticate git so release-plz can rebase onto main
| * 22fd17e4e16fde8f2e2bdbf736869e9914f60f45 feat(cli): emit the command surface as data via `batten spec`
|/  
* a4d8e04daec2beec627ba93e12e144cc85ec2275 fix(ci): read checks with GITHUB_TOKEN in auto-release-land

commit a4d8e04daec2beec627ba93e12e144cc85ec2275
Author: Claude <noreply@anthropic.com>
Date:   Thu Aug 6 08:10:39 2026 +0000

    fix(ci): read checks with GITHUB_TOKEN in auto-release-land
    
    The green-check verification called the check-runs API with
    RELEASE_PLZ_TOKEN, a fine-grained PAT that lacks checks:read, so it 403'd
    and the job failed. Split the tokens: read check status with the Actions
    GITHUB_TOKEN (grant it checks:read), and post `/fast-forward` with
    RELEASE_PLZ_TOKEN — the owner identity that actually triggers
    fast-forward.yml.
    
    Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
    Claude-Session: https://claude.ai/code/session_01TdVURXVaz6yYmrnRW35g3B

Rebase locally, and then force push to claude/ci-watch-handoff-3abw65.

@wenzowski
wenzowski force-pushed the claude/ci-watch-handoff-3abw65 branch from 22fd17e to 681e42e Compare August 6, 2026 08:32
@wenzowski

Copy link
Copy Markdown
Contributor Author

Triggered from #45 (comment) by @​wenzowski.

Trying to fast forward main (1f814e7) to claude/ci-watch-handoff-3abw65 (681e42e).

Target branch (main):

commit 1f814e78b08f28546333e86c8f4c13fba6a2d30e (HEAD -> main, tag: v0.0.2, origin/main)
Author: Alec Wenzowski <alec@button.is>
Date:   Thu Aug 6 01:30:17 2026 -0700

    chore: release v0.0.2

Pull request (claude/ci-watch-handoff-3abw65):

commit 681e42e45250eea0930c14aaac5b93c26bbce6d0 (pull_request/claude/ci-watch-handoff-3abw65)
Author: Claude <noreply@anthropic.com>
Date:   Thu Aug 6 08:28:52 2026 +0000

    feat(cli): emit the command surface as data via `batten spec`
    
    Land the CLI-as-data spine validated by the CLOUD-20 spike. The command
    surface is defined once as a clap tree and emitted at runtime as byte-stable
    JSON by introspecting the live `clap::Command` (house-style §11), with the
    §5 effect model merged in from a single table keyed by full command path.
    
    - effect.rs: the Effect enum + one path-keyed table, fail-safe by
      construction (a path absent from the table resolves to `ask`, never `read`).
    - spec.rs: runtime introspection into a byte-stable spec, plus the derived
      read-only allowlist (CLOUD-28) — filter(effect == read) over the same walk,
      never a second hand-kept list.
    - cli.rs: the `spec [--format json]` verb; the tree grows one verb at a time,
      each addition kept honest by a completeness test that fails if any command
      ships without an effect.
    - run() now writes data output to an injected writer so the library stays
      print-free and byte-stable; the binary passes stdout.
    
    Refs CLOUD-26, CLOUD-27, CLOUD-28.

Fast forwarding main (1f814e7) to claude/ci-watch-handoff-3abw65 (681e42e).

$ git push origin 681e42e45250eea0930c14aaac5b93c26bbce6d0:main
remote: error: GH013: Repository rule violations found for refs/heads/main.        
remote: Review all repository rules at https://github.com/button-inc/batten/rules?ref=refs%2Fheads%2Fmain        
remote: 
remote: - 3 of 3 required status checks have not succeeded: .        
remote: 
To https://github.com/button-inc/batten.git
 ! [remote rejected] 681e42e45250eea0930c14aaac5b93c26bbce6d0 -> main (push declined due to repository rule violations)
error: failed to push some refs to 'https://github.com/button-inc/batten.git'

Copy link
Copy Markdown
Contributor Author

/fast-forward


Generated by Claude Code

@wenzowski
wenzowski merged commit 681e42e into main Aug 6, 2026
4 checks passed
@wenzowski
wenzowski deleted the claude/ci-watch-handoff-3abw65 branch August 6, 2026 08:33
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.

2 participants