Make the delegation logic importable - #13
Merged
Merged
Conversation
The delegate and container pack commands each hand-rolled the mapping from a codec name to a container codec byte, and each declared the "base64+gzip" default separately. Add ParseCodec next to the FormatCodec call it inverts, and express the binary-versus-text distinction as IsTextualCodec so every caller shares one rule for deciding whether output ends with a newline. Convert the pack command; the delegate command follows once its delegation logic moves out of the cobra layer. Assisted-by: Claude:claude-opus-5 Signed-off-by: Miroslav Bajtoš <oss@bajtos.net>
EncodeSignerToPEM interpolated the signer itself with %s. ed25519.Signer is a []byte with no String method, so the raw private key bytes rendered into the error text and from there into anything that logged it. Format the key DID instead. Add LoadSignerFromPEMFile alongside DecodeSignerFromPEM. The delegate command carries its own copy today and switches to this one once its delegation logic moves out of the cobra layer; callers holding a key in memory keep using DecodeSignerFromPEM and never touch the filesystem. Assisted-by: Claude:claude-opus-5 Signed-off-by: Miroslav Bajtoš <oss@bajtos.net>
Issuing a delegation required the ucantool binary, and the CLI takes the issuer key as a file path, so an importing module had to write private keys to disk purely to satisfy an API shape. Lift the orchestration out of the delegate command's RunE into a package that takes a signer or PEM bytes in memory. Request replaces the cobra flag globals. Two behaviours are worth naming: a nil Expiration passes WithNoExpiration explicitly, because omitting the option entirely makes ucantone expire the delegation 30 seconds from now; and Result.WriteTo carries the rule that textual container codecs end with a newline while binary output is written bare, so consumers match the CLI byte for byte without reimplementing it. Assisted-by: Claude:claude-opus-5 Signed-off-by: Miroslav Bajtoš <oss@bajtos.net>
The delegate command is now a flag binder over ucandelegate.Issue, which is the check that the extraction is faithful: nothing but reading flags and writing bytes is left behind. Add a test for the trailing-newline rule, since the delegation payload itself cannot be compared against a golden file. ucantone generates a random nonce for every delegation, so the encoded bytes differ on every run. Assisted-by: Claude:claude-opus-5 Signed-off-by: Miroslav Bajtoš <oss@bajtos.net>
There was a problem hiding this comment.
Pull request overview
This PR extracts the delegation-issuing orchestration out of the CLI into an importable Go package (pkg/ucandelegate), enabling callers to issue and encode delegations directly from in-memory keys (no subprocess, no writing private keys to disk).
Changes:
- Introduces
pkg/ucandelegatewithRequest/Resultand helpers (Issue,Delegate,Encode,IssueFromPEM,ExpiresAt/ExpiresIn) to reproduce CLI delegation behavior as a library. - Adds
pkg/ucanfmthelpers for container codec defaults/parsing and textual-vs-binary detection, and reuses them from commands. - Moves PEM-file signer loading into
pkg/identityand refactorscmd/delegateto bind flags and call the library; adds tests for newline behavior and expiration semantics.
Reviewed changes
Copilot reviewed 10 out of 10 changed files in this pull request and generated 1 comment.
Show a summary per file
| File | Description |
|---|---|
| README.md | Documents the new library usage pattern for issuing delegations without the CLI. |
| pkg/ucanfmt/codec.go | Adds codec parsing/defaulting and textual codec detection shared by CLI and library. |
| pkg/ucanfmt/codec_test.go | Tests codec parsing round-trips and textual codec classification. |
| pkg/ucandelegate/delegate.go | New library API for issuing/encoding delegations (extracted from CLI orchestration). |
| pkg/ucandelegate/delegate_test.go | Validates delegation semantics (expiration, policy, issuer/subject) and encoding/output rules. |
| pkg/identity/pem.go | Fixes signer formatting in errors and adds LoadSignerFromPEMFile. |
| pkg/identity/pem_test.go | Tests PEM file loading and missing-file error behavior. |
| cmd/delegate.go | Refactors CLI to build a ucandelegate.Request from flags and call the library. |
| cmd/delegate_test.go | Adds CLI regression tests for newline rules and codec/missing-file errors. |
| cmd/container/pack.go | Reuses ucanfmt codec parsing/defaults and textual detection instead of local logic. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
frrist
approved these changes
Aug 11, 2026
Result.WriteTo copied the whole output into a string and ran it through fmt just to append a newline. Write the bytes directly and follow with a single '\n' for textual codecs. A failed or short first write now reports its partial count instead of fmt's. Signed-off-by: Miroslav Bajtoš <oss@bajtos.net> Assisted-by: Claude:claude-opus-5[1m]
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Generating a delegation requires the
ucantoolbinary today, and the CLI takes the issuer key as a file path. A caller that holds a key in memory — a Lambda, for instance — would have to spawn a subprocess and write the private key to disk purely to satisfy an API shape.Every cryptographic step already comes from ucantone's exported API. The only obstacle was that the orchestration lived in
mkDelegationin packagecmd, reading cobra flag globals. This is a move, not a rewrite.Example Use
Requestreplaces the flag globals, and takes aSigneror PEM bytes rather than a path.DelegateandEncodeare available separately for callers that want the delegations before they are encoded.Result.WriteTocarries the rule that textual container codecs end with a newline whilerawandraw+gzipare written bare, so a consumer reproduces the CLI's bytes without reimplementing the rule.ExpiresAtandExpiresInexist so that setting an expiration does not require importing ucantone.A private key leak, fixed
EncodeSignerToPEMinterpolated the signer with%s.ed25519.Signeris a[]bytewith noStringmethod, so the raw private key bytes rendered into the error text and from there into anything that logged it. It now formats the key DID.(Related: fil-forge/libforge#61)