name: review-go-codex description: > Systematic consistency review of the go-codex library: codecs (Layer 1), REST/event/MCP API contracts (Layer 2), and forge pipelines (Layer 3). Use when asked to "review go-codex", "consistency audit", "check for inconsistencies", "simple workflow review", "audit API surface", or after a significant round of refactoring. Reviews cross-layer naming parity, param types, builder methods, error shapes, structured errors, observer pattern, unit test coverage, and example correctness.
review-go-codex
Systematic consistency audit of the go-codex library across all three layers. Reads key source files,
applies the checklist in references/checklist.md, collects findings, categorises by severity, and
plans a fix round.
User Experience North Star
Every finding and fix must be evaluated against one question:
Does this make the library more declarative, simple, and consistent for the user?
The three layers form a single workflow. A user should be able to move between them without context switching or learning a new mental model:
| Layer | User action | How it should feel |
|---|---|---|
| Layer 1 — Codec | Define codex.Codec[T] |
Declare shape + constraints once; derive encode/decode/schema for free |
| Layer 2 — API contract | NewRoute / NewChannel / NewTool / NewResource |
Declare the contract as a value; pass it around; register it anywhere |
| Layer 3 — Pipeline | forge.NewFunction + Registry |
Declare computation contracts; compose; register; govern |
The workflow across layers is always: declare → compose → register. If a finding breaks this
pattern — forces the user to use imperative calls, repeat themselves, or learn layer-specific
vocabulary — it is at minimum a small finding.
Concrete implications for every review:
- Declarative: API objects (
RouteHandle,ChannelHandle,ToolHandle,ResourceHandle,PromptHandle,FunctionSpec) are values, not builder side-effects. Users pass and store them. No magic, no global state. - Simple: one method does one thing. Avoid overloaded methods. Param defaults should be safe.
- Consistent: same concept, same name.
Metastructs,Optinterfaces,Builder/Registryfluent methods, error type shapes — all should follow the same pattern across layers. If Layer 2 hasWithCodec, Layer 2 events should have it too; if Layer 3 adds governance fields, their names should mirror Layer 2'sMetanaming.
When to Use This Skill
- User says "review go-codex", "consistency audit", "check go-codex API", "audit"
- User says "consistent, declarative, simple workflow"
- After a significant feature addition or refactoring round
- When something "feels off" but the user cannot pinpoint it
Step-by-Step Workflow
Phase 1 — Read key files in parallel
Read all of these before opening any finding:
| File | Why |
|---|---|
api/rest/builder.go |
Layer 2 REST: all param types, builder methods, error types |
api/events/builder.go |
Layer 2 events: ChannelHandle, TopicParam, Builder |
api/mcp/builder.go |
Layer 2 MCP: ToolHandle, ResourceHandle, PromptHandle, Builder, MCPSpec |
api/mcp/errors.go |
MCP error types: ToolInputError, ResourceParamError, PromptArgError, … |
forge/forge.go |
Layer 3: PipelineInfo, FunctionMeta, Registry, error types |
render/pipeline/pipeline.go |
Pipeline YAML renderer |
render/asyncapi/v3/document.go |
AsyncAPI renderer |
render/openapi/openapi.go |
OpenAPI renderer |
render/jsonschema/jsonschema.go |
JSON Schema renderer: schema.Schema → json.RawMessage for MCP |
adapters/nethttp/adapter.go |
Adapter: observer calls, security enforcement |
adapters/nethttp/client.go |
Client adapter: Call, CallOptions, typed transport errors |
adapters/chi/adapter.go |
Adapter: same as nethttp |
adapters/mqtt/adapter.go |
Adapter: subscribe/publish, observer calls |
adapters/mcpgo/adapter.go |
MCP adapter: ToolHandler/ResourceHandler/PromptHandler, observer, errors |
.github/instructions/go-codex.instructions.md |
Design contract and prior decisions |
Also scan: api/rest/*_test.go, api/events/*_test.go, api/mcp/*_test.go, adapters/mcpgo/*_test.go, forge/*_test.go, render/pipeline/*_test.go
Then read references/history.md to see what was already fixed. Do not re-report these.
Phase 2 — Apply the checklist
Work through references/checklist.md section by section:
- Cross-layer naming parity
- Param type consistency
- Builder method parity
- Codec field godoc
- Format API parity
- Forge consistency
- Error sentinel consistency (structured errors)
- Observer pattern
- Unit test coverage
- Example correctness
Phase 3 — Record findings
For every issue found, assign:
| Field | Values |
|---|---|
| ID | G<N> (sequential) |
| Severity | bug / small / trivial |
| Category | one of the 10 checklist sections |
| File:line | exact location |
| Problem | one sentence |
| Fix | one sentence |
Present findings as a table, then group by priority: bugs first, then small, then trivial.
Phase 4 — Produce a plan
Write findings and their priority order to the session plan.md. Do NOT start implementing until the user confirms the plan or says "start" / "get to work".
Phase 5 — Implement (after approval)
For each finding, in priority order:
- Apply the fix
- Run
go fmt ./...— format immediately after each edit - Run
go build ./...(must stay clean) - Run
go test ./...(all packages must pass) - Run
just check(staticcheck + gosec — no new warnings) - Update
.github/instructions/go-codex.instructions.mdif any exported API changed
After all fixes:
- Run every example:
for d in examples/*/; do go run ./$d; done— each must exit 0 - If any example uses a stale pattern, update it to match the current API
Phase 6 — Verify
Run in this order — each must be clean before proceeding to the next:
go fmt ./... # format all files; no diff should remain
go build ./... # must compile with zero errors
go test ./... # all packages must pass
just check # staticcheck + gosec; no new suppressions allowed
If just check surfaces a warning introduced by your changes, fix it before proceeding.
Do not add //nolint or //gosec suppressions to silence new findings.
Phase 7 — Update History
Before producing the commit summary, append a new section to
references/history.md for this round. Follow the existing format:
## Round <N> (<short title>)
- **<ID> — <finding title>**: one-sentence description of what was wrong and how it was fixed.
- ...
---
Rules:
- Include only findings that were actually implemented (not deferred/skipped).
- One bullet per finding; keep the description concise and factual.
- Insert the new section above Round
(newest round at top of file, below the header).
Phase 8 — Commit Summary
After updating history.md, produce a commit-ready summary. Do not commit — present this to the user and let them decide.
Format:
<short imperative title — max 72 chars>
Layer(s) affected: <Layer 1 / Layer 2 REST / Layer 2 events / Layer 2 MCP / Layer 2 MCP adapter / Layer 3 forge>
Round: R<N>
Findings fixed:
- <ID> [<severity>] <one-line description of fix>
- ...
Tests: <N> new / updated test(s) in <package(s)>
Examples: <"no changes needed" | list of updated examples>
Docs: .github/instructions/go-codex.instructions.md updated
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Rules:
- Title must be imperative: "Fix ValidateTopicVars missing key check", not "Fixed…"
- List only findings that were actually implemented, not skipped or deferred
- If zero examples changed, write "Examples: no changes needed"
- Always include the
Co-authored-bytrailer
Structured Errors Guardrail
go-codex uses typed errors, not bare strings. Check every error return site:
- Server adapters (nethttp/chi) must return typed errors:
rest.PathParamError,rest.QueryParamError,rest.HeaderParamError,rest.CookieParamError,rest.MissingPathVarError,rest.SecurityCredentialError,rest.SecurityError. - Client adapter (
nethttp.Call) must return typed errors (allerrors.As-navigable):rest.PathParamError,rest.QueryParamError,rest.CookieParamError,rest.HeaderParamError,rest.MissingPathVarError— same as server (from pre-flight codec validation, no HTTP call sent)nethttp.UnexpectedStatusError{Method, Path, StatusCode, Body}— non-2xx responsenethttp.RequestBuildError{Err}—http.NewRequestWithContextfailure (invalid URL, cancelled ctx)nethttp.RequestError{Method, Path, Err}—http.Client.Dofailure (network, DNS, TLS, timeout)nethttp.ResponseBodyError{Err}—io.ReadAllfailure after successful connection- Bare
fmt.Errorf(...)without wrapping a typed sentinel is a finding inclient.go.
- events adapter must return:
events.TopicParamError,events.MissingTopicVarError. - forge must return:
forge.InputError,forge.OutputError,forge.ApplyError,forge.RefinementError. - api/mcp typed errors (all
errors.As-navigable):mcp.ToolInputError{Name, Err}— fromToolHandle.Decodemcp.ToolOutputError{Name, Err}— fromToolHandle.Encodemcp.ResourceEncodeError{URI, Err}— fromResourceHandle.Encodemcp.ResourceParamError{Name, Value, Err}— fromResourceHandle.BuildURI/ValidateURIVarsmcp.MissingResourceVarError{Name}— fromResourceHandle.BuildURI/ValidateURIVarsmcp.InvalidResourceParamError{Name, URITemplate}— fromResource.Registermcp.PromptArgError{Name, Err}— fromPromptHandle.ValidateArgsmcp.MissingPromptArgError{Name}— fromPromptHandle.ValidateArgs
- adapters/mcpgo error behavior — different from REST/events adapters:
- Input decode/validation failures →
mcp.NewToolResultError(err.Error())returned as*mcp.CallToolResultwithIsError: true(not a Go error). LLM sees the error text. - Output encode failures → protocol-level Go error
(nil, err)(server contract violation). fmt.Errorf("...")wrapping a typed sentinel is fine; barefmt.Errorfwithout a typed wrapper is a finding.
- Input decode/validation failures →
fmt.Errorf("...")wrapping a structured error is fine; a barefmt.Errorfwithout wrapping a typed sentinel is a finding.- Callers must be able to
errors.As/ type-switch on every error for structured logging.
Observer Pattern Guardrail
stats.Observer has four interfaces; adapters must call them correctly:
| Interface | Who calls it | When |
|---|---|---|
stats.ValidationObserver |
codecs (internal) | on codec validation failure |
stats.Observer |
adapters (nethttp, chi, mqtt, mcpgo) | on decode/encode start+finish, errors |
stats.PipelineObserver |
forge Registry.Apply |
on each function apply |
stats.SecurityObserver |
adapters | on security rejection — type-asserted, never embedded |
Rules:
SecurityObservermust be guarded:if so, ok := obs.(stats.SecurityObserver); ok { so.RecordSecurityRejection(...) }PipelineObserver.RecordApplymust be called for every function in a pipeline, including wrapped collection ops (Map, Filter, etc.)- Adapter
Observermust be called on every code path, including early-exit error paths adapters/nethttpclient (Call) observer rules:RecordRequest(method, path, statusCode, duration)called on every code path — status 0 when no HTTP call was sent (pre-flight validation failure,CredentialFuncerror,RequestBuildError)stats.ReportErrors(obs, location, err)called for param validation failures beforeRecordRequest(location:"path","query","cookie","header","body")RecordRequestuses the route path template (e.g."/users/{id}"), not the concrete URL — this allows observers to group metrics by route- Status 0 is the sentinel for "call attempted but no HTTP request reached the network" — document this in any
Observergodoc for client-side code
adapters/mcpgoobserver rules:RecordRequest("tool"|"resource"|"prompt", name, statusCode, duration)— called on every code path (200 success, 400 input error, 500 handler/output error)stats.ReportErrors(obs, "input", err)— called beforeRecordRequestwhen codec validation fails (firesRecordValidationErrorper field)- No
SecurityObserverat the mcpgo adapter level — MCP security is handled outside the adapter (noSecurityFuncfield onmcpgo.Options) - Observer location values:
"input"for tool argument decode/validation;"prompt.args"for prompt arg codec failures
Unit Test Coverage
For each exported type or method added, there must be at least one test covering:
- Happy path (valid input → expected output)
- Error path (invalid input → correct typed error)
- For builder methods: that the method returns the right shape (field set correctly)
Spot-check:
grep -n "func Test" api/rest/builder_test.go api/events/builder_test.go forge/forge_test.go render/pipeline/pipeline_test.go
If a new exported symbol has no corresponding TestX function, file a trivial finding.
Example Correctness
Every examples/*/main.go must:
- Build cleanly:
go build . - Demonstrate the feature its directory name implies
- Use current API (no stale patterns):
- Must use
.WithCodec(codec)notCodec: &codec - Must use
NewRoute/NewSSERoute/NewChanneldeclarative constructors - Must not call deleted or renamed methods
- Must use
Run them all:
for d in examples/*/; do
echo "=== $d ===" && cd $d && timeout 5 go run . &
cd - >/dev/null
done
wait
If an example panics or uses a stale pattern, file a finding.
Gotchas
- Do not re-report R1-Rxx items. See
references/history.mdfor the full list of what is already fixed. FunctionKindScalaris""(empty string).NewFunction/Composenever writeKind— scalar functions haveKind==""by design. Therender/pipelinerenderer omitskind:for scalar. This is correct.rest.Builder.AddServerdiscardsnameafter description fallback. OpenAPI servers are a keyless ordered array.events.Builder.AddServerstores the name (AsyncAPI servers are keyed). Same call site, different semantics.PathParamandTopicParamhave noRequiredfield. This is by design — OpenAPI mandates path params are always required; topic vars must always be present. Godoc explains this.ResourceParamhas noRequiredfield. URI vars in a template must always be present (same rationale as PathParam/TopicParam).PromptArgDOES haveRequired bool— prompt args are optional by default.SecuritySchemecodec is spec-only. No adapter enforces it unlessSecurityFuncdoes so explicitly.SubscribeFormats/PublishFormatstake priority overFormats. Adapters must check these before falling back tohandle.Formats.ToolHandle.Decodetakesany, not[]byte. MCP protocol already deserializes arguments tomap[string]anybefore the adapter callsBindArguments. Re-encoding to bytes and back would be wasteful. This is correct and intentional — do not flag as inconsistency with REST/eventsDecode([]byte).- MCP input errors are
IsError: trueresults, not Go errors.adapters/mcpgo.ToolHandlerreturnsmcp.NewToolResultError(...)withIsError: truefor codec validation failures. This is the MCP protocol's way of reporting tool errors to the LLM. Do not flag as missing typed error return. mcp.InfousesNamenotTitle. REST usesInfo{Title}(OpenAPI spec), events usesInfo{Title}(AsyncAPI spec), MCP usesInfo{Name}(MCP protocol). All are protocol-driven — this is correct, not an inconsistency.api/mcphas no security methods. NoAddSecurityScheme,AddGlobalSecurity, orSecurityFunc— MCP security is handled separately and not part of the coreapi/mcpbuilder. This is by design.- Do not invent new API surface during a review. Findings should fix inconsistencies in existing API, not design new features.
- Update
go-codex.instructions.mdafter every code change. The instructions file is the single source of design truth — it must stay in sync.
References
references/history.md— findings fixed in Roundsreferences/checklist.md— full cross-layer consistency checklist.github/instructions/go-codex.instructions.md— design contract