From 762800ab0a118c69a5c9eb1eafaa0a6b6babf3d2 Mon Sep 17 00:00:00 2001 From: Carl Tashian Date: Thu, 10 Sep 2026 09:11:28 -0700 Subject: [PATCH 1/3] Add CLAUDE.md agent guidance Document the repo layout, Make targets, which files are generated from spec/linkedca/*.proto (and how to regenerate them with make bootstrap and make generate), which files are hand-written, and how downstream consumers pick up changes. Co-Authored-By: Claude Fable 5.1 Claude-Session: https://claude.ai/code/session_01JuSZFSx1cTEnqY55oquacV --- CLAUDE.md | 89 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 89 insertions(+) create mode 100644 CLAUDE.md diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..ef7a5ee --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,89 @@ +# CLAUDE.md + +This file provides guidance to Claude Code when working with code in this repository. + +## Overview + +`linkedca` is a small Go library (module `github.com/smallstep/linkedca`, package `linkedca`) holding the protocol buffer definitions and generated Go code for Linked CAs: the `Majordomo` gRPC service and the messages that describe a CA's configuration, authority, provisioners, admins, ACME accounts, EAB keys, and certificate issuance policy. It is imported by `step-ca` (`github.com/smallstep/certificates`) and `step` (`github.com/smallstep/cli`). There is no binary to run; `make build` is a no-op. + +## Commands + +```bash +make test # unit tests with coverage (gotestsum, via go tool) +make race # unit tests with the race detector +make lint # golangci-lint (config fetched from smallstep/workflows) + govulncheck +make fmt # goimports -local github.com/smallstep/linkedca +make generate # regenerate all *.pb.go from spec/ (see below) +make bootstrap # go install protoc-gen-go and protoc-gen-go-grpc +make all # lint + generate + test +``` + +```bash +go test -run TestAdminFromContext ./... # run a single test +go build ./... # compile check +``` + +`make test` and `go build ./...` need only a Go toolchain; they complete in a few seconds. `make lint` needs network access (it curls `.golangci.yml` from the `smallstep/workflows` repo). All Go tools (`gotestsum`, `goimports`, `golangci-lint`, `govulncheck`) are `tool` directives in `go.mod`, so nothing needs to be on `$PATH` except `go`, `protoc`, and the two protoc plugins. + +CI (`.github/workflows/ci.yml`) calls the shared `smallstep/workflows` `goCI.yml` on every pull request: build, test, lint, govulncheck, and CodeQL across the Go versions in the matrix (`only-latest-golang: false`). + +## Generated Code — Do Not Edit + +| Pattern | Generator | Source | +|---------|-----------|--------| +| `*.pb.go` (`acme`, `admin`, `config`, `eab`, `majordomo`, `policy`, `provisioners`) | `protoc-gen-go` | `spec/linkedca/.proto` | +| `majordomo_grpc.pb.go` | `protoc-gen-go-grpc` | `spec/linkedca/majordomo.proto` | + +Everything else at the repo root is hand-written: `context.go`, `policy.go`, their `_test.go` files, and `tools.go`. + +### Regenerating + +The `.proto` files under `spec/linkedca/` are the source of truth. To change the API: + +1. Edit the relevant `spec/linkedca/*.proto` file. +2. `make bootstrap` once to install `protoc-gen-go` and `protoc-gen-go-grpc` at the versions pinned in `go.mod` (`tools.go` keeps them in the module graph). `protoc` itself must already be installed; it is not managed by this repo. +3. `make generate`. It first checks the plugin versions against the minimums in the Makefile (`GEN_GO_MIN_VERSION`, `GEN_GRPC_MIN_VERSION`), deletes every `*.pb.go`, then runs `protoc --proto_path=spec --go_out=. --go-grpc_out=.` with `--go_opt=module=github.com/smallstep/linkedca` so output lands at the repo root rather than in a nested directory. +4. Commit the regenerated `*.pb.go` alongside the `.proto` change. The header of each generated file records the `protoc` and plugin versions used, so expect a header diff if your local versions differ from the last generation. + +There is no `buf` configuration and no `go generate` directive; `make generate` is the only regeneration path. Every proto file uses `package linkedca;` and `option go_package = "github.com/smallstep/linkedca";`, and intra-repo imports are written relative to `spec/` (for example `import "linkedca/policy.proto";`). + +## Architecture + +``` +linkedca/ +├── spec/linkedca/ +│ ├── majordomo.proto # Majordomo service: Login, GetConfiguration, provisioner/admin CRUD, +│ │ # Post*/Revoke*/Get* certificate RPCs, ACME account CRUD +│ ├── config.proto # Configuration, Authority, KMS, TLS, SSH, templates (imports admin/provisioners/policy) +│ ├── provisioners.proto # Provisioner + per-type details (JWK, OIDC, GCP, AWS, Azure, ACME, X5C, K8sSA, SCEP, ...), Claims, Webhook +│ ├── policy.proto # Policy: X509 / SSH host / SSH user allow-deny name lists +│ ├── admin.proto # Admin, AdminList +│ ├── acme.proto # ACMEAccount +│ └── eab.proto # EABKey (ACME external account binding) +├── *.pb.go # generated messages (one per proto), DO NOT EDIT +├── majordomo_grpc.pb.go # generated MajordomoClient / MajordomoServer, DO NOT EDIT +├── context.go # hand-written: context helpers for Admin, Provisioner, EABKey +├── policy.go # hand-written: Policy.Deduplicate() +├── tools.go # build-tagged imports that pin the protoc plugins in go.mod +└── Makefile +``` + +The hand-written Go is deliberately thin: + +- `context.go` provides `NewContextWith` / `FromContext` / `MustFromContext` triples for `*Admin`, `*Provisioner`, and `*EABKey`, keyed on a private `contextKeyType`. The `FromContext` variants return `ok == false` for a stored nil pointer. +- `policy.go` adds `(*Policy).Deduplicate()`, which removes duplicate entries from every allow/deny name list while preserving order and is nil-safe. + +Adding behaviour to a generated type follows the same pattern: put methods in a new hand-written file at the root rather than touching `*.pb.go`. + +## Conventions + +- Plain Go module, no CLI, no config, no logging, no environment variables. +- Tests use `testify/assert`; `policy_test.go` is table-driven and `context_test.go` uses `t.Parallel()`. +- The repo is public and Apache-2.0 licensed; keep issues, PRs, and commit messages free of anything not appropriate for a public audience. +- Releases are semver tags (`vX.Y.Z`). Downstream consumers pin a tagged version, so a proto change that adds fields is a minor bump; renaming or removing fields or RPCs is a breaking change for every consumer of the generated types. +- Dependabot (`.github/dependabot.yml`) bumps Go modules weekly and `dependabot-auto-merge.yml` auto-merges those PRs; most of the recent history is such bumps. + +## Related repos + +- `github.com/smallstep/certificates` (`step-ca`) and `github.com/smallstep/cli` (`step`) import this module. To test a proto change against one of them before tagging, add `replace github.com/smallstep/linkedca => ../linkedca` to that repo's `go.mod` locally (do not commit the replace). +- `github.com/smallstep/workflows` supplies the reusable CI workflows and the `.golangci.yml` that `make lint` downloads. From 8a332c5c48de33c06bc04b2ca72e9bcc5072f4c8 Mon Sep 17 00:00:00 2001 From: Carl Tashian Date: Thu, 10 Sep 2026 09:19:16 -0700 Subject: [PATCH 2/3] Move guidance to AGENTS.md, import it from CLAUDE.md AGENTS.md is the vendor-neutral file read by other coding agents. Claude Code does not read it natively, so CLAUDE.md becomes a one-line @AGENTS.md import that loads the same content. Co-Authored-By: Claude Fable 5.1 Claude-Session: https://claude.ai/code/session_01JuSZFSx1cTEnqY55oquacV --- AGENTS.md | 89 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ CLAUDE.md | 90 +------------------------------------------------------ 2 files changed, 90 insertions(+), 89 deletions(-) create mode 100644 AGENTS.md diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..575f474 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,89 @@ +# AGENTS.md + +Guidance for AI coding agents working in this repository. Claude Code loads it through the one-line `@AGENTS.md` import in `CLAUDE.md`. + +## Overview + +`linkedca` is a small Go library (module `github.com/smallstep/linkedca`, package `linkedca`) holding the protocol buffer definitions and generated Go code for Linked CAs: the `Majordomo` gRPC service and the messages that describe a CA's configuration, authority, provisioners, admins, ACME accounts, EAB keys, and certificate issuance policy. It is imported by `step-ca` (`github.com/smallstep/certificates`) and `step` (`github.com/smallstep/cli`). There is no binary to run; `make build` is a no-op. + +## Commands + +```bash +make test # unit tests with coverage (gotestsum, via go tool) +make race # unit tests with the race detector +make lint # golangci-lint (config fetched from smallstep/workflows) + govulncheck +make fmt # goimports -local github.com/smallstep/linkedca +make generate # regenerate all *.pb.go from spec/ (see below) +make bootstrap # go install protoc-gen-go and protoc-gen-go-grpc +make all # lint + generate + test +``` + +```bash +go test -run TestAdminFromContext ./... # run a single test +go build ./... # compile check +``` + +`make test` and `go build ./...` need only a Go toolchain; they complete in a few seconds. `make lint` needs network access (it curls `.golangci.yml` from the `smallstep/workflows` repo). All Go tools (`gotestsum`, `goimports`, `golangci-lint`, `govulncheck`) are `tool` directives in `go.mod`, so nothing needs to be on `$PATH` except `go`, `protoc`, and the two protoc plugins. + +CI (`.github/workflows/ci.yml`) calls the shared `smallstep/workflows` `goCI.yml` on every pull request: build, test, lint, govulncheck, and CodeQL across the Go versions in the matrix (`only-latest-golang: false`). + +## Generated Code — Do Not Edit + +| Pattern | Generator | Source | +|---------|-----------|--------| +| `*.pb.go` (`acme`, `admin`, `config`, `eab`, `majordomo`, `policy`, `provisioners`) | `protoc-gen-go` | `spec/linkedca/.proto` | +| `majordomo_grpc.pb.go` | `protoc-gen-go-grpc` | `spec/linkedca/majordomo.proto` | + +Everything else at the repo root is hand-written: `context.go`, `policy.go`, their `_test.go` files, and `tools.go`. + +### Regenerating + +The `.proto` files under `spec/linkedca/` are the source of truth. To change the API: + +1. Edit the relevant `spec/linkedca/*.proto` file. +2. `make bootstrap` once to install `protoc-gen-go` and `protoc-gen-go-grpc` at the versions pinned in `go.mod` (`tools.go` keeps them in the module graph). `protoc` itself must already be installed; it is not managed by this repo. +3. `make generate`. It first checks the plugin versions against the minimums in the Makefile (`GEN_GO_MIN_VERSION`, `GEN_GRPC_MIN_VERSION`), deletes every `*.pb.go`, then runs `protoc --proto_path=spec --go_out=. --go-grpc_out=.` with `--go_opt=module=github.com/smallstep/linkedca` so output lands at the repo root rather than in a nested directory. +4. Commit the regenerated `*.pb.go` alongside the `.proto` change. The header of each generated file records the `protoc` and plugin versions used, so expect a header diff if your local versions differ from the last generation. + +There is no `buf` configuration and no `go generate` directive; `make generate` is the only regeneration path. Every proto file uses `package linkedca;` and `option go_package = "github.com/smallstep/linkedca";`, and intra-repo imports are written relative to `spec/` (for example `import "linkedca/policy.proto";`). + +## Architecture + +``` +linkedca/ +├── spec/linkedca/ +│ ├── majordomo.proto # Majordomo service: Login, GetConfiguration, provisioner/admin CRUD, +│ │ # Post*/Revoke*/Get* certificate RPCs, ACME account CRUD +│ ├── config.proto # Configuration, Authority, KMS, TLS, SSH, templates (imports admin/provisioners/policy) +│ ├── provisioners.proto # Provisioner + per-type details (JWK, OIDC, GCP, AWS, Azure, ACME, X5C, K8sSA, SCEP, ...), Claims, Webhook +│ ├── policy.proto # Policy: X509 / SSH host / SSH user allow-deny name lists +│ ├── admin.proto # Admin, AdminList +│ ├── acme.proto # ACMEAccount +│ └── eab.proto # EABKey (ACME external account binding) +├── *.pb.go # generated messages (one per proto), DO NOT EDIT +├── majordomo_grpc.pb.go # generated MajordomoClient / MajordomoServer, DO NOT EDIT +├── context.go # hand-written: context helpers for Admin, Provisioner, EABKey +├── policy.go # hand-written: Policy.Deduplicate() +├── tools.go # build-tagged imports that pin the protoc plugins in go.mod +└── Makefile +``` + +The hand-written Go is deliberately thin: + +- `context.go` provides `NewContextWith` / `FromContext` / `MustFromContext` triples for `*Admin`, `*Provisioner`, and `*EABKey`, keyed on a private `contextKeyType`. The `FromContext` variants return `ok == false` for a stored nil pointer. +- `policy.go` adds `(*Policy).Deduplicate()`, which removes duplicate entries from every allow/deny name list while preserving order and is nil-safe. + +Adding behaviour to a generated type follows the same pattern: put methods in a new hand-written file at the root rather than touching `*.pb.go`. + +## Conventions + +- Plain Go module, no CLI, no config, no logging, no environment variables. +- Tests use `testify/assert`; `policy_test.go` is table-driven and `context_test.go` uses `t.Parallel()`. +- The repo is public and Apache-2.0 licensed; keep issues, PRs, and commit messages free of anything not appropriate for a public audience. +- Releases are semver tags (`vX.Y.Z`). Downstream consumers pin a tagged version, so a proto change that adds fields is a minor bump; renaming or removing fields or RPCs is a breaking change for every consumer of the generated types. +- Dependabot (`.github/dependabot.yml`) bumps Go modules weekly and `dependabot-auto-merge.yml` auto-merges those PRs; most of the recent history is such bumps. + +## Related repos + +- `github.com/smallstep/certificates` (`step-ca`) and `github.com/smallstep/cli` (`step`) import this module. To test a proto change against one of them before tagging, add `replace github.com/smallstep/linkedca => ../linkedca` to that repo's `go.mod` locally (do not commit the replace). +- `github.com/smallstep/workflows` supplies the reusable CI workflows and the `.golangci.yml` that `make lint` downloads. diff --git a/CLAUDE.md b/CLAUDE.md index ef7a5ee..43c994c 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -1,89 +1 @@ -# CLAUDE.md - -This file provides guidance to Claude Code when working with code in this repository. - -## Overview - -`linkedca` is a small Go library (module `github.com/smallstep/linkedca`, package `linkedca`) holding the protocol buffer definitions and generated Go code for Linked CAs: the `Majordomo` gRPC service and the messages that describe a CA's configuration, authority, provisioners, admins, ACME accounts, EAB keys, and certificate issuance policy. It is imported by `step-ca` (`github.com/smallstep/certificates`) and `step` (`github.com/smallstep/cli`). There is no binary to run; `make build` is a no-op. - -## Commands - -```bash -make test # unit tests with coverage (gotestsum, via go tool) -make race # unit tests with the race detector -make lint # golangci-lint (config fetched from smallstep/workflows) + govulncheck -make fmt # goimports -local github.com/smallstep/linkedca -make generate # regenerate all *.pb.go from spec/ (see below) -make bootstrap # go install protoc-gen-go and protoc-gen-go-grpc -make all # lint + generate + test -``` - -```bash -go test -run TestAdminFromContext ./... # run a single test -go build ./... # compile check -``` - -`make test` and `go build ./...` need only a Go toolchain; they complete in a few seconds. `make lint` needs network access (it curls `.golangci.yml` from the `smallstep/workflows` repo). All Go tools (`gotestsum`, `goimports`, `golangci-lint`, `govulncheck`) are `tool` directives in `go.mod`, so nothing needs to be on `$PATH` except `go`, `protoc`, and the two protoc plugins. - -CI (`.github/workflows/ci.yml`) calls the shared `smallstep/workflows` `goCI.yml` on every pull request: build, test, lint, govulncheck, and CodeQL across the Go versions in the matrix (`only-latest-golang: false`). - -## Generated Code — Do Not Edit - -| Pattern | Generator | Source | -|---------|-----------|--------| -| `*.pb.go` (`acme`, `admin`, `config`, `eab`, `majordomo`, `policy`, `provisioners`) | `protoc-gen-go` | `spec/linkedca/.proto` | -| `majordomo_grpc.pb.go` | `protoc-gen-go-grpc` | `spec/linkedca/majordomo.proto` | - -Everything else at the repo root is hand-written: `context.go`, `policy.go`, their `_test.go` files, and `tools.go`. - -### Regenerating - -The `.proto` files under `spec/linkedca/` are the source of truth. To change the API: - -1. Edit the relevant `spec/linkedca/*.proto` file. -2. `make bootstrap` once to install `protoc-gen-go` and `protoc-gen-go-grpc` at the versions pinned in `go.mod` (`tools.go` keeps them in the module graph). `protoc` itself must already be installed; it is not managed by this repo. -3. `make generate`. It first checks the plugin versions against the minimums in the Makefile (`GEN_GO_MIN_VERSION`, `GEN_GRPC_MIN_VERSION`), deletes every `*.pb.go`, then runs `protoc --proto_path=spec --go_out=. --go-grpc_out=.` with `--go_opt=module=github.com/smallstep/linkedca` so output lands at the repo root rather than in a nested directory. -4. Commit the regenerated `*.pb.go` alongside the `.proto` change. The header of each generated file records the `protoc` and plugin versions used, so expect a header diff if your local versions differ from the last generation. - -There is no `buf` configuration and no `go generate` directive; `make generate` is the only regeneration path. Every proto file uses `package linkedca;` and `option go_package = "github.com/smallstep/linkedca";`, and intra-repo imports are written relative to `spec/` (for example `import "linkedca/policy.proto";`). - -## Architecture - -``` -linkedca/ -├── spec/linkedca/ -│ ├── majordomo.proto # Majordomo service: Login, GetConfiguration, provisioner/admin CRUD, -│ │ # Post*/Revoke*/Get* certificate RPCs, ACME account CRUD -│ ├── config.proto # Configuration, Authority, KMS, TLS, SSH, templates (imports admin/provisioners/policy) -│ ├── provisioners.proto # Provisioner + per-type details (JWK, OIDC, GCP, AWS, Azure, ACME, X5C, K8sSA, SCEP, ...), Claims, Webhook -│ ├── policy.proto # Policy: X509 / SSH host / SSH user allow-deny name lists -│ ├── admin.proto # Admin, AdminList -│ ├── acme.proto # ACMEAccount -│ └── eab.proto # EABKey (ACME external account binding) -├── *.pb.go # generated messages (one per proto), DO NOT EDIT -├── majordomo_grpc.pb.go # generated MajordomoClient / MajordomoServer, DO NOT EDIT -├── context.go # hand-written: context helpers for Admin, Provisioner, EABKey -├── policy.go # hand-written: Policy.Deduplicate() -├── tools.go # build-tagged imports that pin the protoc plugins in go.mod -└── Makefile -``` - -The hand-written Go is deliberately thin: - -- `context.go` provides `NewContextWith` / `FromContext` / `MustFromContext` triples for `*Admin`, `*Provisioner`, and `*EABKey`, keyed on a private `contextKeyType`. The `FromContext` variants return `ok == false` for a stored nil pointer. -- `policy.go` adds `(*Policy).Deduplicate()`, which removes duplicate entries from every allow/deny name list while preserving order and is nil-safe. - -Adding behaviour to a generated type follows the same pattern: put methods in a new hand-written file at the root rather than touching `*.pb.go`. - -## Conventions - -- Plain Go module, no CLI, no config, no logging, no environment variables. -- Tests use `testify/assert`; `policy_test.go` is table-driven and `context_test.go` uses `t.Parallel()`. -- The repo is public and Apache-2.0 licensed; keep issues, PRs, and commit messages free of anything not appropriate for a public audience. -- Releases are semver tags (`vX.Y.Z`). Downstream consumers pin a tagged version, so a proto change that adds fields is a minor bump; renaming or removing fields or RPCs is a breaking change for every consumer of the generated types. -- Dependabot (`.github/dependabot.yml`) bumps Go modules weekly and `dependabot-auto-merge.yml` auto-merges those PRs; most of the recent history is such bumps. - -## Related repos - -- `github.com/smallstep/certificates` (`step-ca`) and `github.com/smallstep/cli` (`step`) import this module. To test a proto change against one of them before tagging, add `replace github.com/smallstep/linkedca => ../linkedca` to that repo's `go.mod` locally (do not commit the replace). -- `github.com/smallstep/workflows` supplies the reusable CI workflows and the `.golangci.yml` that `make lint` downloads. +@AGENTS.md From 4bca374bbcd1b5838562c1d848cd7b335ffa189d Mon Sep 17 00:00:00 2001 From: Carl Tashian Date: Thu, 10 Sep 2026 09:38:46 -0700 Subject: [PATCH 3/3] Move Claude Code stub to .claude/CLAUDE.md Per review: keep the repo root to AGENTS.md and put the Claude Code import stub under .claude/, importing @../AGENTS.md. Co-Authored-By: Claude Fable 5.1 Claude-Session: https://claude.ai/code/session_01JuSZFSx1cTEnqY55oquacV --- .claude/CLAUDE.md | 1 + AGENTS.md | 2 +- CLAUDE.md | 1 - 3 files changed, 2 insertions(+), 2 deletions(-) create mode 100644 .claude/CLAUDE.md delete mode 100644 CLAUDE.md diff --git a/.claude/CLAUDE.md b/.claude/CLAUDE.md new file mode 100644 index 0000000..dba71e9 --- /dev/null +++ b/.claude/CLAUDE.md @@ -0,0 +1 @@ +@../AGENTS.md diff --git a/AGENTS.md b/AGENTS.md index 575f474..435666d 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1,6 +1,6 @@ # AGENTS.md -Guidance for AI coding agents working in this repository. Claude Code loads it through the one-line `@AGENTS.md` import in `CLAUDE.md`. +Guidance for AI coding agents working in this repository. Claude Code loads it through the one-line `@../AGENTS.md` import in `.claude/CLAUDE.md`. ## Overview diff --git a/CLAUDE.md b/CLAUDE.md deleted file mode 100644 index 43c994c..0000000 --- a/CLAUDE.md +++ /dev/null @@ -1 +0,0 @@ -@AGENTS.md