fix(api): every error body is a coded message with the right code, args and owner #279
No reviewers
Labels
No labels
bug
enhancement
needs-triage
ready-for-agent
ready-for-human
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
Cloonar/deckle!279
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "afk/272"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Closes #272
What changes on the wire
Page-path rule, one owner.
pageops.ValidatePagePath(param, page, langs)is the only place that checks a page path (empty, not local, translation file). The server calls it, and its own copy is gone.400 request.missingParam {name}, wherenameis the key the route reads (page,path,fromPageortoPage).400 page.translationPathon every route.page.pathMissingandeditor.translationPathare removed from the registry and the catalogs.Engine refusals have their own codes.
content.MutateErrorgains aKind. One mapping inapimsg.FromError(internal/apimsg/engine.go) turns each engine error into a code:element.outOfRange,element.notContainer,element.moveIntoItself,element.rootPathandelement.invalidNode: 409, as before.page.parseFailedandpage.serializeRefused: 422.Every template is
{detail}, so the English text and the statuses are unchanged.writeError/writeOpError. I put it inFromErrorbecause the server'swriteErrorand pageops'passThroughboth already call it, and pageops can't import the server. So pageops' create serializer refusals get the code without a second copy of the mapping. apimsg importing content creates no cycle, and content still doesn't import apimsg.Escaping media ref →
400 library.invalidRef {ref}. The rule is coded once, inmedia.ResolvePath. Media info, delete, rename and move all answer it, mediaops included. The text no longer contains the absolute media root.media.folderNotFound.args.folderis now the folder as sent (outer slashes trimmed). The library root can be missing (no media directory), and that case gets its own code:404 media.rootNotFound, with no args.Smaller fixes:
translation.appeared {path}is content-root-relative.args,{}when the message names nothing.field.unknownechoesitems[0].fooas sent.GET /api/statusanswers a JSON405 request.method.docs/api.mdis updated for each item.error.unexpectedis now listed only for underlying failures (filesystem, git, front-matter apply, re-encoding).Not affected
scaffold/site/AGENTS.md, the scaffolded skills,docs/harness.mdanddocs/architecture.md. None of them names an affected code (checked with grep).Verification
go build ./...,go vet ./...,gofmt -landgo test ./...all pass, includingTestCallSites,TestExport, the route test anddocs/docs_test.go.cd web && npm testpasses 3210 tests.TestPagePathRefusals: missingpage/pathand translation paths.TestEngineRefusalCodes: a stale[5]delete gives 409element.outOfRangewith the old text as its detail.TestCodedErrorBodies: POST/api/statusgives a JSON 405.TestValidatePagePath,TestMutateErrorKind,TestFromErrorEngine,TestResolvePathRefusalsAreCoded,TestCodedEscapingMediaRef,TestCodedMediaFolderNotFound,TestTranslateAppearedNamesRelativePathandTestFormCheckUnknownField, plus the publish job's"args":{}.grep -rn "editor.translationPath\|page.pathMissing" internal web/src docsfinds nothing.🤖 Generated with Claude Code
- an escaping media ref is library.invalidRef {ref}, decided once in media.ResolvePath, from every media route and mediaops - media.folderNotFound carries the folder as sent; a missing library root answers media.rootNotFound instead of English inside args - translation.appeared names the content-root-relative path - a failed publish job always carries args, {} when empty - field.unknown echoes an item-row key as sent (items[0].foo) - a wrong method on GET /api/status answers a JSON request.method - five stale doc comments say what the code does Refs #272 Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>[autoland] verdict: pass
PASS — validated against the landing core.
Signal:
labctl pr checks 279aggregate success (ci / build-and-test: npm ci/build/test, go vet, go test ./..., go build; release skipped as expected on a PR). Not re-run locally. gofmt -l clean. Branch is current with main (no commits on main missing from afk/272).Conventions: Conventional Commits title;
Closes #272present; diff scope matches the issue's acceptance criteria item by item:pageops.ValidatePagePath(internal/pageops/paths.go:536); server's copy removed (internal/server/server.go:704-765), each route passes its own key (page/path/fromPage/toPage).apimsg.engineMessage(internal/apimsg/engine.go), reached via FromError. This deviates from the brief's writeError, and the PR explains why: a single mapping that pageops passThrough reaches too. content still does not import apimsg.{}, field.unknown echoes items[i].x, /api/status JSON 405, doc comments fixed.