diff --git a/.agents/skills/verify-interface/features/README.md b/.agents/skills/verify-interface/features/README.md index 5fbc2f9..e6e9f22 100644 --- a/.agents/skills/verify-interface/features/README.md +++ b/.agents/skills/verify-interface/features/README.md @@ -16,15 +16,6 @@ One file per feature. `covers:` lists the source files its journeys exercise, pl | [types](types.md) | A TypeScript author compiles against the packed declarations. | declared-shapes | | [smoke](smoke.md) | The fixed journeys every verdict runs. | core-calls, export-surface | -## Known findings - -| Finding | Page | Issue | -|---|---|---| -| `@Get("")` registers the location `/users/` with a trailing slash, where the docs say it registers the controller root | `docs/2.controllers.md` | AntelopeJS/interface-api#75 | -| `@Connection` is documented as valid only on a `@WebsocketHandler` handler, and nothing enforces it | `docs/4.parameters.md` | AntelopeJS/interface-api#76 | - -No journey pins either behaviour as correct. - ## Not driven The interface holds the contract and the decorators; `@antelopejs/api` serves requests. A journey here drives the packed interface next to a stub provider, with fake requests, or through real legacy decorators, so these documented behaviours are not driven, each with its reason. diff --git a/.agents/skills/verify-interface/features/routes.md b/.agents/skills/verify-interface/features/routes.md index d4a3af4..93ed6d2 100644 --- a/.agents/skills/verify-interface/features/routes.md +++ b/.agents/skills/verify-interface/features/routes.md @@ -20,7 +20,7 @@ A module author maps a controller method to an endpoint with `@Get`, `@Post`, `@ ## Sub-features -- `route-locations`: the HTTP method, handler mode, full location (method name when omitted, repeated slashes collapsed) and priority each decorator registers, and that the decorator returns nothing a class can see. +- `route-locations`: the HTTP method, handler mode, full location (method name when omitted, the controller root with no trailing slash for `@Get("")`, repeated slashes collapsed) and priority each decorator registers, and that the decorator returns nothing a class can see. - `route-errors`: a class `Controller` did not build, a descriptor with no method and a symbol key with no location each throw a `TypeError` with the documented message, and nothing is registered. ## How to get to it (user POV) @@ -35,5 +35,4 @@ pnpm exec antelopejs-verify journey "$RUN" routes ## Gotchas -- `docs/2.controllers.md` says `@Get("")` registers the controller root, but the registered location carries a trailing slash (`/users/`). That is a known finding (AntelopeJS/interface-api#75). The journey strips one trailing slash before comparing, so it pins neither form. - The symbol key case is undocumented in `docs/`; it is a check of the TypeError the code raises. diff --git a/.agents/skills/verify-interface/scenarios-second.ts b/.agents/skills/verify-interface/scenarios-second.ts index 6f8184b..18fc6ef 100644 --- a/.agents/skills/verify-interface/scenarios-second.ts +++ b/.agents/skills/verify-interface/scenarios-second.ts @@ -21,7 +21,7 @@ export const SECOND_SCENARIOS = { printed: "{\"plain\":[\"TypeError\",\"Route decorators apply to Controller classes; Plain has no location\"],\"novalue\":[\"TypeError\",\"Route decorators apply to methods; nov has no value\"],\"symbol\":[\"TypeError\",\"Route decorators on a symbol-keyed method need a location; Users[Symbol(s)] has none\"],\"noController\":[\"TypeError\",\"Cannot register GET /q: RouteHandler.controller is required\"],\"none\":0}", }, "route-locations": { - body: "class Users extends a.Controller(\"/users\") {}\ndeco(a.Get(), Users, \"listUsers\"); deco(a.Get(\"\"), Users, \"root\"); deco(a.Get(\":id\"), Users, \"one\"); deco(a.Get(\"//a//b\"), Users, \"slashes\");\ndeco(a.Post(\"p\"), Users, \"p1\"); deco(a.Put(\"u\"), Users, \"p2\"); deco(a.Delete(\"d\"), Users, \"p3\");\ndeco(a.Get(\"pm\", \"prefix\"), Users, \"pm\"); deco(a.Prefix(\"get\", \"x\", a.HandlerPriority.HIGHEST), Users, \"pre\"); deco(a.Postfix(\"get\", \"x\"), Users, \"post\"); deco(a.Monitor(\"get\", \"x\", 3), Users, \"mon\"); deco(a.WebsocketHandler(\"ws\"), Users, \"sock\"); deco(a.Route(\"handler\", \"patch\", \"r\", 1), Users, \"rt\");\nR.routes = a.getRegisteredRouteHandlers().map((r) => [r.handler.mode, r.handler.method, r.handler.location.replace(/\\/$/, \"\"), r.handler.callback.name, r.handler.priority ?? null]);\nconst out = deco(a.Get(\"ret\"), Users, \"ret\"); R.returned = [out === undefined];\ndeco(a.Post(\"m1\", \"prefix\"), Users, \"postPrefix\"); deco(a.Put(\"m2\", \"postfix\"), Users, \"putPostfix\"); deco(a.Delete(\"m3\", \"monitor\"), Users, \"deleteMonitor\");\ndeco(a.Postfix(\"get\", \"y\", a.HandlerPriority.HIGH), Users, \"postfixHigh\"); deco(a.Prefix(\"get\", \"y\", a.HandlerPriority.LOW), Users, \"prefixLow\"); deco(a.Monitor(\"get\", \"y\"), Users, \"monitorDefault\");\nR.modes = [\"postPrefix\", \"putPostfix\", \"deleteMonitor\", \"postfixHigh\", \"prefixLow\", \"monitorDefault\"].map((name) => { const h = a.getRegisteredRouteHandlers().find((r) => r.handler.callback.name === name).handler; return [name, h.mode, h.method, h.priority ?? null]; });\na.Parameter(\"id\")(Users.prototype, \"withParam\", 1); a.Parameter(\"authorization\", \"header\")(Users.prototype, \"ownerToken\");\ndeco(a.Get(\"wp\"), Users, \"withParam\");\nconst wp = a.getRegisteredRoutes().find((r) => r.callbackName === \"withParam\");\nR.computed = [wp.parameters.map((p) => (p === null ? null : \"computed\")), Object.keys(wp.properties)];\nconst pc = a.ProcessCallback(Users.prototype, \"viaProcess\", { value: named(\"viaProcess\") }, \"handler\", \"get\", \"pc\", a.HandlerPriority.LOW);\nconst pcRoute = a.getRegisteredRoutes().find((r) => r.callbackName === \"viaProcess\");\nR.processCallback = [Object.keys(pc), typeof pc.id, String(pc.id) === pcRoute.id, pcRoute.location, pcRoute.priority];\nconst before = a.getRegisteredRoutes().length; a.UnregisterRoute(pc.id); R.processUnregister = before - a.getRegisteredRoutes().length;\nclass Bare extends a.Controller(\"/bare\") {}\ndeco(a.Prefix(\"get\"), Bare, \"pfx\"); deco(a.Postfix(\"get\"), Bare, \"psx\"); deco(a.Monitor(\"get\"), Bare, \"mon\"); deco(a.WebsocketHandler(), Bare, \"sock\"); deco(a.Delete(), Bare, \"del\"); deco(a.Post(), Bare, \"pst\"); deco(a.Put(), Bare, \"put\");\nR.omitted = a.getRegisteredRouteHandlers().filter((r) => r.handler.controller === Bare).map((r) => [r.handler.mode, r.handler.method, r.handler.location]);\n", + body: "class Users extends a.Controller(\"/users\") {}\ndeco(a.Get(), Users, \"listUsers\"); deco(a.Get(\"\"), Users, \"root\"); deco(a.Get(\":id\"), Users, \"one\"); deco(a.Get(\"//a//b\"), Users, \"slashes\");\ndeco(a.Post(\"p\"), Users, \"p1\"); deco(a.Put(\"u\"), Users, \"p2\"); deco(a.Delete(\"d\"), Users, \"p3\");\ndeco(a.Get(\"pm\", \"prefix\"), Users, \"pm\"); deco(a.Prefix(\"get\", \"x\", a.HandlerPriority.HIGHEST), Users, \"pre\"); deco(a.Postfix(\"get\", \"x\"), Users, \"post\"); deco(a.Monitor(\"get\", \"x\", 3), Users, \"mon\"); deco(a.WebsocketHandler(\"ws\"), Users, \"sock\"); deco(a.Route(\"handler\", \"patch\", \"r\", 1), Users, \"rt\");\nR.routes = a.getRegisteredRouteHandlers().map((r) => [r.handler.mode, r.handler.method, r.handler.location, r.handler.callback.name, r.handler.priority ?? null]);\nconst out = deco(a.Get(\"ret\"), Users, \"ret\"); R.returned = [out === undefined];\ndeco(a.Post(\"m1\", \"prefix\"), Users, \"postPrefix\"); deco(a.Put(\"m2\", \"postfix\"), Users, \"putPostfix\"); deco(a.Delete(\"m3\", \"monitor\"), Users, \"deleteMonitor\");\ndeco(a.Postfix(\"get\", \"y\", a.HandlerPriority.HIGH), Users, \"postfixHigh\"); deco(a.Prefix(\"get\", \"y\", a.HandlerPriority.LOW), Users, \"prefixLow\"); deco(a.Monitor(\"get\", \"y\"), Users, \"monitorDefault\");\nR.modes = [\"postPrefix\", \"putPostfix\", \"deleteMonitor\", \"postfixHigh\", \"prefixLow\", \"monitorDefault\"].map((name) => { const h = a.getRegisteredRouteHandlers().find((r) => r.handler.callback.name === name).handler; return [name, h.mode, h.method, h.priority ?? null]; });\na.Parameter(\"id\")(Users.prototype, \"withParam\", 1); a.Parameter(\"authorization\", \"header\")(Users.prototype, \"ownerToken\");\ndeco(a.Get(\"wp\"), Users, \"withParam\");\nconst wp = a.getRegisteredRoutes().find((r) => r.callbackName === \"withParam\");\nR.computed = [wp.parameters.map((p) => (p === null ? null : \"computed\")), Object.keys(wp.properties)];\nconst pc = a.ProcessCallback(Users.prototype, \"viaProcess\", { value: named(\"viaProcess\") }, \"handler\", \"get\", \"pc\", a.HandlerPriority.LOW);\nconst pcRoute = a.getRegisteredRoutes().find((r) => r.callbackName === \"viaProcess\");\nR.processCallback = [Object.keys(pc), typeof pc.id, String(pc.id) === pcRoute.id, pcRoute.location, pcRoute.priority];\nconst before = a.getRegisteredRoutes().length; a.UnregisterRoute(pc.id); R.processUnregister = before - a.getRegisteredRoutes().length;\nclass Bare extends a.Controller(\"/bare\") {}\ndeco(a.Prefix(\"get\"), Bare, \"pfx\"); deco(a.Postfix(\"get\"), Bare, \"psx\"); deco(a.Monitor(\"get\"), Bare, \"mon\"); deco(a.WebsocketHandler(), Bare, \"sock\"); deco(a.Delete(), Bare, \"del\"); deco(a.Post(), Bare, \"pst\"); deco(a.Put(), Bare, \"put\");\nR.omitted = a.getRegisteredRouteHandlers().filter((r) => r.handler.controller === Bare).map((r) => [r.handler.mode, r.handler.method, r.handler.location]);\n", printed: "{\"routes\":[[\"handler\",\"get\",\"/users/listUsers\",\"listUsers\",null],[\"handler\",\"get\",\"/users\",\"root\",null],[\"handler\",\"get\",\"/users/:id\",\"one\",null],[\"handler\",\"get\",\"/users/a/b\",\"slashes\",null],[\"handler\",\"post\",\"/users/p\",\"p1\",null],[\"handler\",\"put\",\"/users/u\",\"p2\",null],[\"handler\",\"delete\",\"/users/d\",\"p3\",null],[\"prefix\",\"get\",\"/users/pm\",\"pm\",null],[\"prefix\",\"get\",\"/users/x\",\"pre\",0],[\"postfix\",\"get\",\"/users/x\",\"post\",null],[\"monitor\",\"get\",\"/users/x\",\"mon\",3],[\"websocket\",\"get\",\"/users/ws\",\"sock\",null],[\"handler\",\"patch\",\"/users/r\",\"rt\",1]],\"returned\":[true],\"modes\":[[\"postPrefix\",\"prefix\",\"post\",null],[\"putPostfix\",\"postfix\",\"put\",null],[\"deleteMonitor\",\"monitor\",\"delete\",null],[\"postfixHigh\",\"postfix\",\"get\",1],[\"prefixLow\",\"prefix\",\"get\",3],[\"monitorDefault\",\"monitor\",\"get\",null]],\"computed\":[[null,\"computed\"],[\"ownerToken\"]],\"processCallback\":[[\"id\"],\"number\",true,\"/users/pc\",3],\"processUnregister\":1,\"omitted\":[[\"prefix\",\"get\",\"/bare/pfx\"],[\"postfix\",\"get\",\"/bare/psx\"],[\"monitor\",\"get\",\"/bare/mon\"],[\"websocket\",\"get\",\"/bare/sock\"],[\"handler\",\"delete\",\"/bare/del\"],[\"handler\",\"post\",\"/bare/pst\"],[\"handler\",\"put\",\"/bare/put\"]]}", }, "sources": {