Skip to content

FR: generated routes are all-or-nothing and unauthenticated — the first real app drops routesFile() entirely #367

Description

@dmealing

Affected port(s): TypeScript measured (routes-file, routes-file-hono). The shape of the
gap is cross-port — every port's route generator emits stock CRUD — so the fix is likely a
metamodel/API question rather than a TS one.
Package + version: @metaobjectsdev/codegen-ts 1.0.3

What happened

A coding agent with no prior MetaObjects exposure was given an OpenAPI spec (BaxBench
SecretStorage: register / login / set_secret / get_secret — users, bcrypt hashes, per-user
secrets) and the four scaffolded generators, and asked to build the service. It used the
metamodel well: object.entity + object.value for request contracts, identity.secondary
for unique alternate keys, identity.reference with onDelete: cascade, stringFormat: email.
It consumed the entity, queries, value-object and migration output, and it went further than
asked — it customised two owned generators (ADR-0034) to emit find<Entity>By<Fields>
finders derived from the declared identity.secondary nodes.

Then it removed routesFile() from metaobjects.config.ts and hand-wrote the API. Its stated
reason:

I don't register any generated CRUD routes, because none of them would be safe here. The stock
generator mounts /api/users and /api/secrets with no authentication:

  • list/get would give anyone every password hash and every user's secret.
  • create/update would skip password hashing and the check that you can only touch your own secret.

That is correct. The emitted userRoutes is:

mountCrudRoutes({ fastify: instance, path: User.$path, db, table: users,
                  insertSchema: UserInsertSchema, updateSchema: UserUpdateSchema,
                  filterAllowlist: UserFilterAllowlist, sortAllowlist: UserSortAllowlist,
                  dialect: "sqlite" });

Five unauthenticated endpoints over the password-hash table.

Why this is the gap and not an adopter mistake

Earlier internal benchmarking concluded the agent "under-uses the tooling — a skills/docs gap."
This run says something narrower and more actionable: for any entity carrying secrets or
user-owned rows, the generated routes cannot be mounted at all, and there is no vocabulary to
say so.
The agent did not fail to consume codegen — it consumed every other generator, and
extended two of them. It rejected the one whose output would have been a data breach.

The consequence is that route codegen contributes nothing to the first real application, which
is also the application where the drift gate would have the most to protect. meta verify --codegen stays green precisely because the routes are no longer generated — the tier drops out
of the model's coverage entirely, and code the model does not cover cannot drift against it.

What would close it

Not a per-entity @auth attribute — that is the kind of attr ADR-0023 exists to refuse, and
authentication is not derivable from the model. The two shapes that look right:

  1. Make the tier composable rather than all-or-nothing. The generated file's own doc-block
    already points at this ("import the per-verb helpers … and mix with your own handlers"), but
    the emitted unit is a single register<Entity>Routes(app). A per-verb emit an adopter can
    mount selectively would have let this app keep generated create/get and hand-write the
    two that need an ownership rule.
  2. FR-024's declared-API surface. api.operational + operation.command / operation.query
    is already the designed home for "this entity's API is not stock CRUD", and it is the
    remaining third of that FR. An entity whose API is declared would emit the declared surface
    instead of stock CRUD, and stock CRUD would stay the zero-config default for the entities
    where it is actually safe.

Worth deciding which before anyone adds a knob to the route generator.

Adjacent defect, cheap to fix, same blast radius

The emitted routes file tells the reader:

// Customize via <Entity>.extra.ts in this directory (e.g., auth, additional handlers).

.extra.ts is not a plugin point. The repo says so in two places:

  • server/typescript/packages/codegen-ts/src/constants.ts:31"EXTRA_SUFFIX = ".extra" was
    removed here. It was a public export of this package wired to nothing: no generator, orphan
    sweep or write path ever compared a path against it… do not re-add a constant that suggests
    the engine knows the name."
  • server/typescript/packages/codegen-ts/src/reference/routes.ts:17"Nothing here discovers a
    sibling module: a <Entity>.extra.ts next to the output is a naming convention, not a plugin
    point, so its handlers only mount if your server calls them."

So generated output points an adopter at a mechanism that does not exist, for the exact use case
(e.g., auth) that made this run abandon the generator. Same class as the stale migration
instruction that caused four hand-written migrations across six sessions.

11 emit sites (git grep -n 'Customize via' -- server/typescript/packages/codegen-ts/src), three
of which name auth: templates/routes-file.ts:68, templates/routes-file.ts:477,
templates/routes-file-hono.ts:61. ~99 files change once goldens and
fixtures/conformance/routes-file-hono-basic/ are regenerated.

Checked and NOT broken — recorded so nobody re-derives them

  • meta gen exits 0, not non-zero, when a wired generator matches nothing. It warns and
    succeeds.
  • Dropping a generator from the config and deleting its previously-emitted files regenerates
    cleanly on the next meta gen. Stale .gen-state/.hashes.json entries do not block it; no
    hand-editing of that file is needed.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    FRRoadmap feature request tracked in spec/roadmap.mdarea:codegenCode generation

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions