Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 14 additions & 5 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -37,14 +37,23 @@ build/
# Playwright MCP intermediate files
.playwright-mcp/

# CSW local symlink

# Black-box fixture-maintenance SQL — persistent across reboots, not committed.
# README.md inside the directory is exempted so the directory's purpose is documented in-repo.
tests/blackbox/.fixtures/*
!tests/blackbox/.fixtures/README.md


# Superpowers design specs + implementation plans: generated per-session,
# swept in periodic explicit cleanups rather than tracked.
spec/superpowers/
# Superpowers design specs + implementation plans: generated per-session as
# working artifacts, never tracked. Both paths: the skills default to
# docs/superpowers/, and repos whose docs/ is published content use the root.
superpowers/
docs/superpowers/
.superpowers/

# Working notes — investigation records, scratch analysis, session notes. Same
# rationale as docs/superpowers/ above: they earn their keep while a ticket is
# being built and reviewed, then go stale fast (PIDs, bench state, host facts)
# while still reading as authoritative because they are checked in. Durable
# conclusions belong in CLAUDE.md, an ADR or a code comment; the narrative and
# evidence belong on the ticket.
docs/notes/
25 changes: 25 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# AGENTS.md — trakrf working agreements

Identical in `docs`, `platform` and `infra` — change it in all three or none.
These repos are public. Add only what a fresh session gets wrong otherwise.
Branches, commits, merges: `CONTRIBUTING.md`. Tooling: `.claude/csw.json`.

## Specs, plans and notes are never tracked

Per-session artifacts, disposable by decision. Ignored: `superpowers/`,
`docs/superpowers/`, `.superpowers/`, `docs/notes/` — each repo's `CLAUDE.md`
names which to write to. A plan worth a reviewer's time goes in the PR body.

**Re-used, or merely re-read?** Re-used lives in the repo under a _behaviour_
name — `describe` blocks, log prefixes and artifact dirs included. Merely
re-read belongs on the ticket, with the narrative and evidence. Durable
conclusions go to `CLAUDE.md`, an ADR or a code comment. Keep ticket ids out of
published prose.

## Working rules

- Ask when requirements are unclear; never delete code without instruction
- Verify before claiming done: run it, read the output, report what it said
- Verify the premise, not just the citation
- Search wide, then filter — absence from a narrowed search is not absence
- No invented imports; keep files under 500 lines
68 changes: 15 additions & 53 deletions CLAUDE.md
Original file line number Diff line number Diff line change
@@ -1,55 +1,17 @@
# CLAUDE.md

This file provides guidance to Claude when working with code in this repository.

## Project Overview

Standalone **Docusaurus documentation site** for trakrf (`trakrf/docs`). Content-focused — no application logic, no backend, no monorepo. Just docs and configuration.

## Package Manager

**pnpm EXCLUSIVELY** — never use `npm` or `npx`.

- `npx` → `pnpm dlx`
- `npm run` → `pnpm`

## Common Commands

```bash
pnpm dev # Local dev server
pnpm build # Production build
pnpm serve # Serve production build locally
pnpm typecheck # Type checking
pnpm lint # Linting
```

## Git Workflow

**NEVER PUSH DIRECTLY TO MAIN BRANCH**

1. ALL changes go through a Pull Request — no exceptions
2. Always create a feature/fix branch: `feature/add-xyz`, `fix/broken-xyz`, `docs/update-xyz`
3. NEVER squash merge — preserve individual commit history
4. NEVER merge without explicit confirmation
5. Use conventional commits: `feat:`, `fix:`, `docs:`, `chore:`
6. Prefer incremental commits over amending

## Worktrees

Use **`.claude/worktrees/`** for git worktrees — this is the canonical location across all trakrf repos. Claude Code's native worktree tool (`EnterWorktree`) writes there and cannot be redirected, so the convention matches the tool rather than fighting it. The directory is gitignored (narrowly — the rest of `.claude/` stays tracked). Do not reintroduce a root-level `.worktrees/` or a symlink bridge.

## Style & Conventions

- **TypeScript** for any config or custom components
- **ES Modules only** — no CommonJS
- **Prettier** for formatting
- **Conventional Commits** for git messages
- Keep files under 500 lines

## AI Behavior Rules

- Ask questions when requirements are unclear
- Never delete code without explicit instruction
- Verify builds pass before claiming completion
- Report actual status — no false optimism
- Use only verified packages — no hallucinated imports
@AGENTS.md

Docusaurus documentation site (`trakrf/docs`) — content and config only.
Branches, commits and merges: [CONTRIBUTING.md](CONTRIBUTING.md). Tooling:
`.claude/csw.json`.

- **pnpm exclusively** — `pnpm dlx` not `npx`, `pnpm` not `npm run`
- `pnpm dev` · `build` · `serve` · `typecheck` · `lint` (prettier, gates `validate`)
- TypeScript and ES modules for config and components
- **Specs and plans go to `superpowers/` at the root, never under `docs/`** —
Docusaurus globs `docs/`, so a stray file renders locally but never in CI
- Merging to `main` publishes to customers immediately; unreleased behaviour
waits as a draft PR. Preview builds against the preview API and is separate
- The OpenAPI spec is fetched from the live API at build, not checked in — spec
changes originate in `trakrf/platform`
17 changes: 12 additions & 5 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,15 +34,17 @@ pnpm dev

### 1. Create a Branch

```bash
# Branch naming:
# - feature/add-xyz (new features)
# - fix/broken-xyz (bug fixes)
# - docs/update-xyz (documentation content)
Branch as `<type>/<slug>`, using a [conventional commit](https://www.conventionalcommits.org)
type — `feat`, `fix`, `docs`, `chore`, `style`, `refactor`, `test`.

```bash
git checkout -b docs/update-getting-started
```

Maintainers working from a tracked issue insert the ticket:
`<type>/<ticket>-<slug>`, e.g. `docs/tra-0000-update-getting-started`. Outside
contributors have no ticket and should use the plain `<type>/<slug>` form.

### 2. Write Your Content

- Documentation lives in `docs/` as Markdown or MDX files
Expand Down Expand Up @@ -110,6 +112,11 @@ Rules for the customer-facing release notes:
- [ ] Links are not broken
- [ ] Commit messages use conventional format

4. **How PRs are merged:**

PRs are merged with `--merge`, never squashed and never rebased, so that
individual commit history is preserved.

## Getting Help

- **Questions?** Open a GitHub Discussion
Expand Down
4 changes: 2 additions & 2 deletions docs/api/errors.md
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ When filing a support ticket, include the `error.request_id` value (or the equiv
| `bad_request` | 400 | Request body couldn't be parsed or attributed to a field — invalid JSON syntax, a top-level type mismatch (e.g. an array where an object is expected), or a `PATCH` body that is the literal JSON token `null`. No `fields[]`; `detail` describes the failure. Field-attributable type mismatches (`is_active: "true"` where boolean is expected) return `validation_error` with `fields[]` instead — see [validation_error vs bad_request](#validation_error-vs-bad_request). | No — fix the request |
| `unauthorized` | 401 | Missing, malformed, revoked, or expired API key. The specific cause (missing header, wrong scheme, expired token, revoked key) is in `detail`. | No — re-auth |
| `forbidden` | 403 | Valid key but insufficient scope for this endpoint | No — needs a key with the right scope |
| `not_found` | 404 | Resource lookup failed — an in-range path-param `id` that doesn't resolve (`GET /api/v1/assets/99999`, or a large but in-range value like `GET /api/v1/assets/2147483648`) or a sub-resource path doesn't exist (`GET /api/v1/assets/99999/history`). Surrogate ids no longer range-reject, so a non-resolving in-range id is a `404`, not a validation error — see [ID format](./id-format). A zero, negative, non-numeric, or int64-overflow path-param `id` (e.g. `0`, `-5`, `abc`, a 20-digit value) returns `400 validation_error` against the spec's path-param bounds, not 404 — see [path-parameter validation errors](#path-and-query-parameter-validation-errors). A list filter that resolves to zero rows returns 200 with empty `data[]`, not 404. | No — check the identifier |
| `not_found` | 404 | Resource lookup failed — an in-range path-param `id` that doesn't resolve (`GET /api/v1/assets/99999`, or a large but in-range value like `GET /api/v1/assets/2147483648`) or a sub-resource path doesn't exist (`GET /api/v1/assets/99999/history`). Surrogate ids no longer range-reject, so a non-resolving in-range id is a `404`, not a validation error — see [ID format](./id-format). A zero, negative, non-numeric, or int64-overflow path-param `id` (e.g. `0`, `-5`, `abc`, a 20-digit value) returns `400 validation_error` against the spec's path-param bounds, not 404 — see [path-parameter validation errors](#path-and-query-parameter-validation-errors). A list filter that resolves to zero rows returns 200 with empty `data[]`, not 404. | No — check the identifier |
| `method_not_allowed` | 405 | The route does not accept the HTTP method you used (e.g. `PATCH` on a collection that only supports `GET` and `POST`). Allowed methods are listed in the response `Allow` header and mirrored in `detail` (e.g. `Allowed methods: GET, HEAD, POST`). | No — use a supported method |
| `conflict` | 409 | Two cause classes. **Unique-constraint violation** — typically a duplicate `external_key` on `POST` or `POST /…/rename` for assets/locations, or a duplicate `(tag_type, value)` on tags. **Referential-integrity violation** — `DELETE /api/v1/locations/{location_id}` rejects when the location has active descendant locations or active placed assets; see [Locations: delete semantics](./resource-identifiers#locations-delete-semantics). | No — reconcile (unique: `GET` then `PATCH`; referential: remove dependents first) |
| `unsupported_media_type` | 415 | Request body was sent with a `Content-Type` the endpoint doesn't accept, or the header is missing entirely. `POST` requires `application/json`; `PATCH` requires `application/merge-patch+json` ([RFC 7396](https://datatracker.ietf.org/doc/html/rfc7396)) exclusively — sending `application/json` to a `PATCH` endpoint returns `415` with `detail: "Content-Type must be application/merge-patch+json on PATCH operations"`, and sending `application/merge-patch+json` to a `POST` endpoint returns `415` with `detail: "Content-Type must be application/json"` (no method suffix on the POST side). A missing `Content-Type` header is rejected on every write method. See [HTTP method coverage → Request body Content-Type per method](./http-method-coverage#patch-content-type). | No — fix the `Content-Type` |
Expand Down Expand Up @@ -206,11 +206,11 @@ Current `code` values (extensible):
- `invalid_value` — a value-validation failure: the value was sent as explicit `null` on a non-nullable field, isn't one of the allowed values, fails a format check (email, URL, UUID), fails an enum check, or fails a validation TrakRF has not mapped to a more specific code. Use this code's path when the field _name_ was recognized but the value was wrong; see `unknown_field` for the unrecognized-name case. Integrators branching on `code` per the [tip above](#validation-errors) should treat the null-on-non-nullable case as a value error, not a missing-field error.
- `unknown_field` — the request body contains a top-level key the schema does not declare. Distinct from `invalid_value` so integrators can branch on "typo'd field name" vs. "wrong value." Emitted by the strict-decoder pass that drives [`additionalProperties: false`](./pagination-filtering-sorting#validator-behavior-on-writes) on write request bodies; `fields[].field` names the offending key. Distinct from `invalid_context` (next entry) — `unknown_field` is "the API does not declare this field anywhere," while `invalid_context` is "the field exists elsewhere on the surface but is not allowed in this position."
- `invalid_context` — a known field or parameter was supplied on a surface where it is not allowed. Two semantic categories share this code:

1. **List-endpoint filter on a detail or write endpoint.** Any of the public-API list-endpoint filter parameters (`external_key`, `is_active`, `q`, `parent_id` / `parent_external_key`, `location_id` / `location_external_key`, `asset_id` / `asset_external_key`, `include_deleted`) emits `invalid_context` when it lands on a detail or write endpoint that doesn't declare it (e.g. `GET /api/v1/assets/{asset_id}?external_key=ABC`, `GET /api/v1/locations/{location_id}?include_deleted=true`). The accompanying `message` points at the list-endpoint sibling where the parameter is honored (e.g. `GET /api/v1/assets`) when one can be derived from the request path; `include_deleted` carries its specialized "soft-deleted records are not retrievable by id" wording so the natural-key recovery path stays discoverable.
2. **Field handed off to a dedicated write subresource on `PATCH`.** A field that is settable on the resource but only via a different verb emits `invalid_context` on `PATCH` when the body value differs from the current resource state. The fields: `external_key` (assets and locations) — mutable via `POST /api/v1/{resource}/{id}/rename`; and the `tags` collection (assets and locations) — mutable via `POST /api/v1/{resource}/{id}/tags` and `DELETE /api/v1/{resource}/{id}/tags/{tag_id}`. The accompanying `message` names the dedicated write path. Echoing the current value back is silently stripped under the [accept-if-matches](./resource-identifiers#read-shape-vs-write-shape) half of the rule — `invalid_context` only fires when the body value diverges from the live resource.

Strict-typed clients switching over `code` should add an arm for this value to distinguish "known field on this API, wrong surface or verb here" from `unknown_field` ("the API does not declare this field anywhere") and from `read_only` ("field is truly server-managed; no partner-side write path exists at all"). See [Resource identifiers → Read shape vs. write shape](./resource-identifiers#read-shape-vs-write-shape) for the per-field code matrix.

- `too_short` — the field was present in the request body with a length below the documented minimum (e.g. `"name": ""` on a `min_length: 1` string field). Distinct from `required` (absent-key case) and from `invalid_value` (explicit-`null`-on-non-nullable case). `params.min_length` carries the constraint value.
- `too_long` — string or collection length above the maximum
- `too_small` — numeric value below the minimum
Expand Down
Loading
Loading