Skip to main content
Amended by 0144: every decorator package ships an Explorer, and the metadata registry lives in @stackra/container.

Context

stackra gen (0041) must find every decorated class (controllers, handlers, crons, models, resources), including those that come from packages. Scanning every TypeScript file with a parser is slow and misses runtime registration. Running the app to find them would start databases and side effects. The container already knows every provider through its modules (0042), and its decorator metadata lives in its own WeakMap store (0005).

Decision

  1. Each member declares its root module in package.json: "stackra": { "module": "<path>" }.
  2. stackra gen imports that module in Node and runs the container’s scan and plan steps only. It never instantiates a provider. Decorator metadata comes from the container’s store.
  3. ts-morph reads only the request and response types of the handlers that discovery found, and emits wire interfaces from them. No other file is parsed.
  4. A validation pass fails the build on duplicate paths, a destructive action without a permission, an unversioned integration event (0078), or exposed PII.

Consequences

  • A package’s module is discovered the same way as a service’s. No file globbing and no naming convention is needed.
  • Module code must be safe to import without starting anything. Side effects belong in providers, which are never instantiated during gen.
  • Discovery is only as complete as the module graph: a class not reachable from the root module does not exist for gen.

Enforcement

The validation pass of decision 4 is itself the check, once built. Planned: pnpm validate fails a tier:service or tier:app member without a stackra.module field. Nothing checks this today.

Supersedes and sources

Supersedes: none. Specifies the discovery mechanism of 0041 decision 2. Sources: owner decision in chat, 2026-10-19 (Round 9 G2).