You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
feat(event): emit typed error envelopes across the event domain
Replace every command-facing error path in the event domain — the
consume/schema command layer, the +subscribe shortcut, EventKey
definitions, and the consume orchestration — with typed errs.*
envelopes, so consumers get stable type, subtype, param, hint, and
missing_scopes metadata for classification and recovery instead of
free-form message text.
- Input validation (--jq, --param, --output-dir, --filter, --route,
unknown EventKey, EventKey params) reports validation /
invalid_argument with the offending flag in param and an actionable
hint.
- Scope preflight reports authorization / missing_scope with the
machine-readable missing_scopes list; console-subscription and
single-bus preconditions report failed_precondition with recovery
hints.
- The consume API boundary passes already-typed errors through and
classifies transport, non-JSON HTTP, and unparsable responses; the
vc note-detail retry now matches the not-found code on typed errors
(it silently never fired against the legacy envelope shape).
- Previously-bare failures exited 1 with a plain-text "Error:" line
and now exit with their category code (validation 2, auth 3,
network 4, internal 5) alongside the typed stderr envelope.
- forbidigo and errscontract guards now cover the event paths so
regressions fail lint; AGENTS.md and the lark-event skill document
the typed contract for agent consumers.
Validation: make unit-test (race) green; event unit and e2e suites
assert category/subtype/param/hint and cause preservation against the
real binary; errscontract and golangci lint clean.
Copy file name to clipboardExpand all lines: AGENTS.md
+25-1Lines changed: 25 additions & 1 deletion
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -75,7 +75,31 @@ The one rule to internalize: **every error message you write will be parsed by a
75
75
76
76
### Structured errors in commands
77
77
78
-
`RunE` functions must return `output.Errorf` / `output.ErrWithHint` — never bare `fmt.Errorf`. AI agents parse stderr as JSON; bare errors break this contract.
78
+
Command-facing failures must be typed `errs.*` errors — never the legacy `output.Err*` helpers and never a final bare `fmt.Errorf`. AI agents parse the stderr envelope's `type` / `subtype` / `param` / `hint` fields to decide their next action; the full taxonomy lives in `errs/ERROR_CONTRACT.md`.
79
+
80
+
Picking a constructor:
81
+
82
+
| Failure | Constructor |
83
+
|---------|-------------|
84
+
| User flag/arg fails validation |`errs.NewValidationError(errs.SubtypeInvalidArgument, ...).WithParam("--flag")`|
85
+
| Valid request, wrong system state |`errs.NewValidationError(errs.SubtypeFailedPrecondition, ...).WithHint(...)`|
86
+
| Lark API returned `code != 0`|`runtime.CallAPITyped` (shortcuts) / `errclass.BuildAPIError` (raw responses) — never hand-build |
87
+
| Network / transport failure |`errs.NewNetworkError(errs.SubtypeNetworkTransport, ...)`|
88
+
| Local file I/O failure |`errs.NewInternalError(errs.SubtypeFileIO, ...)` — validate the path first (`validate.SafeInputPath` / `SafeOutputPath`) and use `vfs.*`|
89
+
| Unclassified lower-layer error as final |`errs.NewInternalError(errs.SubtypeUnknown, ...).WithCause(err)`|
90
+
| Lower layer already returned a typed error | pass it through unchanged — re-wrapping downgrades its classification |
91
+
92
+
Signatures that are easy to guess wrong:
93
+
94
+
-`runtime.CallAPITyped(method, url string, params map[string]interface{}, data interface{}) (map[string]interface{}, error)` — it performs the HTTP request itself and classifies `code != 0` into a typed error; just return the error it gives you.
95
+
- Typed pass-through check: `if _, ok := errs.ProblemOf(err); ok { return err }` — `ProblemOf` returns `(*errs.Problem, bool)`, not a nilable pointer.
96
+
-`.WithParam` exists only on `*errs.ValidationError`. `InternalError` / `NetworkError` have no param field — file or endpoint context goes in the message or `.WithHint(...)`.
97
+
98
+
`forbidigo` + `lint/errscontract` reject the legacy `output.Err*` helpers, bare final `fmt.Errorf` / `errors.New`, and legacy envelope literals on migrated paths. Beyond what lint catches, three authoring conventions apply:
99
+
100
+
- Preserve the underlying error with `.WithCause(err)` so `errors.Is` / `errors.Unwrap` keep working.
101
+
-`param` names only the user input that actually failed. Recovery guidance goes in `.WithHint(...)`; machine-readable recovery fields (`missing_scopes`, `log_id`) carry server/system ground truth only — never caller-side guesses.
102
+
- Error-path tests assert typed metadata via `errs.ProblemOf` (`category` / `subtype` / `param`) and cause preservation, not message substrings alone.
0 commit comments