Skip to content

refactor: make command.Command valid-by-construction#21

Merged
frrist merged 1 commit into
mainfrom
frrist/command-struct
May 21, 2026
Merged

refactor: make command.Command valid-by-construction#21
frrist merged 1 commit into
mainfrom
frrist/command-struct

Conversation

@frrist

@frrist frrist commented May 21, 2026

Copy link
Copy Markdown
Member

Summary

command.Command was type Command string, so its validity invariant — leading slash, lowercase, no trailing slash — was not enforced by the type. Three ways to hold an invalid command existed:

  1. Conversioncommand.Command("FOO/") is legal Go.
  2. Zero valuevar c command.Command is "", which Parse itself rejects.
  3. Unvalidated decode — the generated wire path did a raw command.Command(sval) with no check.

This PR models Command on the existing did.DID precedent: a struct with an unexported field, obtainable only through validating constructors. Invalid commands become unrepresentable.

What changed

  • Command is now struct{ str string } (was type Command string). Added MustParse, Undef, and Defined(), mirroring did.DID.
  • Serialization methods (MarshalCBOR/UnmarshalCBOR/MarshalDagJSON/UnmarshalDagJSON/MarshalJSON/UnmarshalJSON) copied from did.DID. cbor-gen now delegates to them (the (struct) codegen path), and decode validates — a non-conforming command from the wire is rejected instead of silently accepted.
  • Wire format unchanged. A command still serializes as the same CBOR text string; fixtures and test vectors round-trip byte-identically (delegation/invocation/container/receipt tests stay green).
  • Regenerated the invocation/delegation datamodels.
  • receipt.Command: constvar command.MustParse(...) (can't const a struct).
  • invocation.go/delegation.go: string(command)command.String() at the re-parse boundary.
  • Updated call sites to build commands via MustParse. The "bad command" test now feeds command.Undef — the only error path still reachable now that invalid commands can't be constructed.
  • Added command_test.go (the package previously had no tests): parse table, MustParse, CBOR/DAG-JSON round-trips, and the decode-validation guarantees.

Notes

  • New(segments...) still trusts its caller's segments (like cid/did builders) rather than validating, to avoid a breaking signature change rippling into bindcom. The headline guarantee comes from the struct itself.
  • Independent of the in-flight binding work; this also makes a future binding.Of(cmd command.Command) a safe, error-free primitive.

🤖 Generated with Claude Code

@frrist frrist force-pushed the frrist/command-struct branch from fe3e758 to bcc87fd Compare May 21, 2026 17:50
@frrist frrist self-assigned this May 21, 2026
@frrist frrist requested review from Peeja and alanshaw May 21, 2026 17:52
Command was `type Command string`, so its validity invariant (leading
slash, lowercase, no trailing slash) was not enforced by the type:
arbitrary strings could be converted in, the zero value was an invalid
empty string, and the wire-decode path did an unchecked
`command.Command(sval)`.

Model Command on did.DID instead: a struct with an unexported field,
obtainable only through validating constructors. Add MarshalCBOR/
UnmarshalCBOR/DagJSON methods (cbor-gen delegates to them, like did.DID),
so decode now validates and rejects non-conforming commands. The wire
format is unchanged — a command still serializes as the same CBOR text
string (fixtures and test vectors round-trip identically).

- Command is now struct{ str string }; add MustParse, Undef, Defined.
- Regenerate invocation/delegation datamodels to delegate (struct path).
- receipt.Command: const -> var command.MustParse(...).
- Invoke/Delegate no longer re-parse the command (it is valid by
  construction); a cheap Defined() guard preserves rejection of the
  undefined zero value.
- Add command_test.go: parse table, MustParse, CBOR/DAG-JSON round-trips
  and decode-validation guarantees.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@frrist frrist force-pushed the frrist/command-struct branch from bcc87fd to 27db4cf Compare May 21, 2026 17:54
@frrist frrist merged commit 5900b0f into main May 21, 2026
1 check passed
@frrist frrist deleted the frrist/command-struct branch May 21, 2026 19:09
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