generated-artifact boundary
rule
generated output is owned by its generator input and command. fix the source, regenerate, review the result, never hand-edit the artifact.
why
a hand-edit to a generated file is a temporary local fork: the next generation overwrites the repair without repairing its cause. two versions of truth exist (generator input vs edited artifact) and no one knows which is authoritative; consumers build against stale edited code while the authored input moves on. durable fix: change the authored owner, regenerate all dependents, prove the output correct. a fresh generation from unchanged input must produce the same artifact — otherwise the generator is nondeterministic and the authority boundary is broken.
applies to
OpenAPI documents, Fern SDKs, generated indexes, Worker bindings, migration metadata, ORM declarations, build output.
repair direction
trace backward: generated file → generator command/config → authored source input. repair the first authored owner that explains the wrong output, then regenerate every dependent. a generated diff is evidence of source behavior, not a second implementation surface.
| observation | repair owner |
|---|---|
| generated client lacks an operation | mounted route/schema/OpenAPI source input. |
| generated type has a wrong field or closed value | named source contract/schema. |
| generated name contains marketing identity | generator input, API title, schema, or organization metadata. |
| generation fails or produces at the wrong path | generator configuration and task graph. |
| consumer cannot import generated output | package public export/build boundary. |
| generated content differs after no source change | nondeterministic generator input, environment, or tool version. |
decision process
| shortcut | failure | selected direction |
|---|---|---|
| edit a generated line | next generation removes the repair. | edit source and regenerate. |
| hand-write a missing consumer type | parallel contracts drift while local checks stay green. | expose/regenerate the public generated type. |
| commit unexplained generated diff | source authority and tool behavior remain unknown. | inspect the source-to-output chain before accepting it. |
| regenerate only at release time | consumers build against stale local history. | make generation an owned lifecycle task. |
verification
- run the owner generator, typecheck the produced package, inspect whether the generated diff corresponds to the authored input.
- from a clean derived-output state, run the direct consumer — proves the task graph creates output before import and no untracked hand edit or local cache carries the build.
good and bad
bad
// route schema (authored)export const ReadWorkflowStatusResponseSchema = z.object({phase: z.string(), // oops: should be an enumoutput: z.string(),});// generated client (v1) — wrong typeexport type ReadWorkflowStatusResponse = {phase: string;output: string;};// developer hand-edits the generated file (WRONG — temporary fork)export type ReadWorkflowStatusResponse = {phase: "queued" | "running" | "complete"; // added by hand ✗output: string;};// interface imports the hand-edited type, works fineconst { phase } = await readWorkflowStatus();if (phase === "running") { ... } // TypeScript thinks phase is enum// next morning: `pnpm generate` overwrites the hand-edit, source still wrongexport type ReadWorkflowStatusResponse = {phase: string; // BACK TO WRONG TYPE; hand-edit is goneoutput: string;};// interface now has a type errorconst { phase } = await readWorkflowStatus();if (phase === "running") { ... } // ERROR: phase is string, not literal union
the hand-edit fixed the symptom (wrong generated type), not the root cause (wrong source schema). next generation reverts it; the interface breaks.
good
// route schema (authored) — fix the sourceexport const ReadWorkflowStatusResponseSchema = z.object({phase: WorkflowPhaseEnumSchema, // FIXED: enum instead of stringoutput: z.string(),});// never edit generated/client.ts directly — run generationpnpm generate// generated/client.ts (regenerated from fixed source)export type ReadWorkflowStatusResponse = {phase: "queued" | "running" | "complete"; // now correctoutput: string;};// typecheck passes; generated type is authoritativeconst { phase } = await readWorkflowStatus();if (phase === "running") { ... } // ✓ phase correctly narrowed
fix the authored schema, regenerate; every consumer gets the correct type from one source of truth.