Service scaffold · commit conventions · dispatch protocol.
Source: CanaryGo/docs/conventions.md — if this page disagrees with that file, the file wins.
Every domain module lives under internal/<module>/. Standard layout:
Every domain package exposes a single Mount(r chi.Router) that registers its routes. The binary's main.go calls each module's Mount in sequence. No route registration outside of the module that owns it.
| Concern | Convention |
|---|---|
| Path style | /v1/<resource>[/{id}] — lower-snake-case segments. Most-specific routes first. |
| Pagination | ?page=N&size=M — 1-indexed, 50 default / 200 max. |
| Success envelope | {"items":[…],"page":1,"size":50,"count":17} for collections. Single resource returned directly. |
| Error envelope | {"code":"snake_case_error","message":"detail"} — code is the stable wire identifier; message is advisory. |
| Body limit | http.MaxBytesReader(w, r.Body, 1<<20) — 1 MiB unless the handler documents otherwise. |
| Sentinel | HTTP status | Code |
|---|---|---|
| ErrNotFound | 404 | not_found |
| ErrConflict | 409 | conflict |
| ErrValidation | 400 | invalid_request |
| (other) | 500 | internal_error — message redacted, error logged via zap |
The store exposes a concrete *Store (constructed via NewStore(pool)) and one or more narrow interfaces (Reader, Writer, or method-named). Handlers accept the interface — tests substitute a stub. Sentinel errors are package-scoped; no shared errs package.
internal/db/sqlc/<module>.sql. Generated output in internal/db/query/<module>/ — do not hand-edit. Regenerate with make sqlc-gen.
All money fields use github.com/shopspring/decimal against numeric(14,4) columns. Wire format is string ("12.34"). Currency travels as a separate string field — no Money wrapper struct.
Commits follow a structured format so the log is machine-readable and dispatch-traceable.
<type>(<scope>): <description> — <ticket>
Optional body: what changed and why. Bullet list preferred for multi-point changes.
Co-Authored-By: Claude Sonnet 4.x <noreply@anthropic.com>
| Type | When to use |
|---|---|
| feat | New capability — a route, a store method, a migration that adds surface. |
| fix | Bug fix. Scope is the affected module. |
| demo | Work scoped to the GRO-802 demo workstream (Square OAuth, vault, discovery). |
| chore | Non-functional: dependency updates, gitignore changes, file moves. |
| test | Tests only — no production code change. |
| docs | Documentation only. |
| refactor | Code change with no behavior change — naming, extraction, restructure. |
One dispatch per commit series. A commit that touches three modules to deliver one ticket is correct. Three commits across three dispatches that all land in the same PR is a process error.
All work in this repo is Linear-dispatch-driven. There are no free-form commits to main without a corresponding ticket. The dispatch is the unit of accountability — it carries the brief, the target, the priority, and the completion record.
| Step | Action |
|---|---|
| 1 — Create | Open a Linear issue in the Dispatch project. Imperative title (add location filter to inventory handler). Description carries the full brief — expected inputs, expected outputs, acceptance criteria. |
| 2 — Pick up | Set status → In Progress. Post a comment confirming pickup and estimated completion. |
| 3 — Execute | Code, test, commit per the conventions above. Outputs land in the repo at the paths named in the dispatch. |
| 4 — Complete | Set status → Done. Post a closing comment: artifact paths, commit SHA, one-paragraph summary, any follow-on tickets recommended. |
| Failure | Set status → Cancelled with a reason comment. Do not leave issues In Progress indefinitely. |
Issues carry Target/laptop or Target/any labels and optionally an Agent/<name> label. Priority is Linear's native field. Do not duplicate priority in labels.
The first dispatch for any new contributor is: walk the onboarding path and propose patches via PR for any confusing surfaces or gaps. This is the right first task — it exercises the full dispatch lifecycle while producing genuine value (fixing the thing that was confusing is better than any contrived starter task).