fix(plan-eng-review,plan-ceo-review): narrow absolute schema-bias language, add legitimate-JSON and measured-denormalization counterexamples

PR review feedback (garrytan/gstack#1071): the data-model bias guidance was
overly absolute ("diff size is NOT a goal", "three clean models... in all
respects", "JSONField is not an escape hatch") and lacked counterexamples for
cases where JSON columns or denormalization are the right call.

- "Data model exception to right-sized diff" bullet (both skills): reframes
  from an absolute rule to "don't let diff size alone decide the shape",
  explicitly allows denormalization backed by a measurement, and adds the
  counterexample of a field that would only ever be read through one FK join
  with no independent consumer.
- JSONField bullet (both skills): keeps the core diagnostic but adds concrete
  legitimate uses — third-party webhook/OAuth payload caching, open-ended
  preference bags, event payloads shaped by an external producer, and
  schemas still being discovered.
- plan-eng-review cognitive patterns #11 (normalize first) and #13
  (structure beats blobs) get the same measured-reason / known-polymorphism
  counterexamples.
- Architecture Review's "Data model honesty" bullet and the "Single
  Responsibility" and "JSONField as polymorphism escape hatch" checklist
  items (now living in sections/review-sections.md.tmpl per main's carve
  refactor) get matching nuance.

Bumps carve-guards.ts skeleton-size budgets for plan-eng-review (69_000 ->
70_000) and plan-ceo-review (91_000 -> 92_000) to cover the added prose;
verified against test/parity-suite.test.ts (13/13 pass) and
test/skill-validation.test.ts's load-bearing-bullet guardrails (still pass —
none of the anchor phrases the static tests check were removed).
This commit is contained in:
David Grant 2026-07-15 17:20:19 -07:00
parent fe3f0d6d6c
commit 5374987c6c
7 changed files with 31 additions and 28 deletions

View File

@ -897,8 +897,8 @@ Do NOT make any code changes. Do NOT start implementation. Your only job right n
* I err on the side of handling more edge cases, not fewer; thoughtfulness > speed.
* Bias toward explicit over clever.
* Right-sized diff: favor the smallest diff that cleanly expresses the change ... but don't compress a necessary rewrite into a minimal patch. If the existing foundation is broken, invoke permission #9 and say "scrap it and do this instead."
* **Data model exception to "right-sized diff": for schema and model changes, diff size is NOT a goal.** Normalize first; denormalize only for measured performance reasons. Three clean models that each do one thing are better than one model doing three things, in all respects. When tempted to merge new fields into an existing model to reduce table count, ask: "does each model have exactly one job?" If splitting is honest, split. Count concepts, not tables.
* **JSONField is not an escape hatch for polymorphism.** When tempted to add a JSONField/HStoreField/payload column to encode variant-specific data (different keys for different kinds), promote it to explicit columns instead. JSONField loses FK enforcement, cascade/protect behavior, CheckConstraints on shape, queryability, type system, and self-documentation. Diagnostic: if you find yourself documenting "what keys appear in this JSONField for which variant," you have schema, you just put it in a place where the DB can't help you. Legitimate JSONField uses are genuinely schemaless data (third-party API response caches, user preference bags, opaque blobs). Polymorphism with knowable variants belongs in explicit columns + CheckConstraints.
* **Data model exception to "right-sized diff": for schema and model changes, don't let diff size alone decide the shape.** Normalize first; denormalize when you have a measured reason — a profiled hot path, a load test, a documented query-plan cost — and say so in the plan. Splitting a model that's doing two unrelated jobs into two clean models is usually worth the extra table, but it's a judgment call, not an automatic rule: if the "split" model would only ever be read through one FK join, written by exactly one code path, and queried by nothing else, keeping it inline can be the right, deliberate choice too. When tempted to merge new fields into an existing model purely to reduce table count, ask: "does each model have exactly one job?" If splitting is honest, split; if the fields genuinely belong to the same concept, don't split just to look normalized. Count concepts, not tables.
* **JSONField is not an escape hatch for polymorphism.** When tempted to add a JSONField/HStoreField/payload column to encode variant-specific data (different keys for different kinds) that you can already enumerate and expect to stay stable, promote it to explicit columns instead. JSONField loses FK enforcement, cascade/protect behavior, CheckConstraints on shape, queryability, type system, and self-documentation. Diagnostic: if you find yourself documenting "what keys appear in this JSONField for which variant," you have schema, you just put it in a place where the DB can't help you. This isn't a blanket ban on JSON columns — legitimate uses are common: caching a third-party API's response verbatim (Stripe webhook payloads, OAuth provider profiles), a genuinely open-ended user-preferences bag nobody queries into, event/analytics payloads shaped by an external producer, or a schema still being discovered (variants that aren't stable yet — revisit once they are). Polymorphism with knowable, stable variants belongs in explicit columns + CheckConstraints; polymorphism you don't control or haven't discovered yet can legitimately stay in JSON.
* Observability is not optional — new codepaths need logs, metrics, or traces.
* Security is not optional — new codepaths need threat modeling.
* Deployments are not atomic — plan for partial states, rollbacks, and feature flags.

View File

@ -86,8 +86,8 @@ Do NOT make any code changes. Do NOT start implementation. Your only job right n
* I err on the side of handling more edge cases, not fewer; thoughtfulness > speed.
* Bias toward explicit over clever.
* Right-sized diff: favor the smallest diff that cleanly expresses the change ... but don't compress a necessary rewrite into a minimal patch. If the existing foundation is broken, invoke permission #9 and say "scrap it and do this instead."
* **Data model exception to "right-sized diff": for schema and model changes, diff size is NOT a goal.** Normalize first; denormalize only for measured performance reasons. Three clean models that each do one thing are better than one model doing three things, in all respects. When tempted to merge new fields into an existing model to reduce table count, ask: "does each model have exactly one job?" If splitting is honest, split. Count concepts, not tables.
* **JSONField is not an escape hatch for polymorphism.** When tempted to add a JSONField/HStoreField/payload column to encode variant-specific data (different keys for different kinds), promote it to explicit columns instead. JSONField loses FK enforcement, cascade/protect behavior, CheckConstraints on shape, queryability, type system, and self-documentation. Diagnostic: if you find yourself documenting "what keys appear in this JSONField for which variant," you have schema, you just put it in a place where the DB can't help you. Legitimate JSONField uses are genuinely schemaless data (third-party API response caches, user preference bags, opaque blobs). Polymorphism with knowable variants belongs in explicit columns + CheckConstraints.
* **Data model exception to "right-sized diff": for schema and model changes, don't let diff size alone decide the shape.** Normalize first; denormalize when you have a measured reason — a profiled hot path, a load test, a documented query-plan cost — and say so in the plan. Splitting a model that's doing two unrelated jobs into two clean models is usually worth the extra table, but it's a judgment call, not an automatic rule: if the "split" model would only ever be read through one FK join, written by exactly one code path, and queried by nothing else, keeping it inline can be the right, deliberate choice too. When tempted to merge new fields into an existing model purely to reduce table count, ask: "does each model have exactly one job?" If splitting is honest, split; if the fields genuinely belong to the same concept, don't split just to look normalized. Count concepts, not tables.
* **JSONField is not an escape hatch for polymorphism.** When tempted to add a JSONField/HStoreField/payload column to encode variant-specific data (different keys for different kinds) that you can already enumerate and expect to stay stable, promote it to explicit columns instead. JSONField loses FK enforcement, cascade/protect behavior, CheckConstraints on shape, queryability, type system, and self-documentation. Diagnostic: if you find yourself documenting "what keys appear in this JSONField for which variant," you have schema, you just put it in a place where the DB can't help you. This isn't a blanket ban on JSON columns — legitimate uses are common: caching a third-party API's response verbatim (Stripe webhook payloads, OAuth provider profiles), a genuinely open-ended user-preferences bag nobody queries into, event/analytics payloads shaped by an external producer, or a schema still being discovered (variants that aren't stable yet — revisit once they are). Polymorphism with knowable, stable variants belongs in explicit columns + CheckConstraints; polymorphism you don't control or haven't discovered yet can legitimately stay in JSON.
* Observability is not optional — new codepaths need logs, metrics, or traces.
* Security is not optional — new codepaths need threat modeling.
* Deployments are not atomic — plan for partial states, rollbacks, and feature flags.

View File

@ -834,8 +834,8 @@ If the user asks you to compress or the system triggers context compaction: Step
* I err on the side of handling more edge cases, not fewer; thoughtfulness > speed.
* Bias toward explicit over clever.
* Right-sized diff: favor the smallest diff that cleanly expresses the change ... but don't compress a necessary rewrite into a minimal patch. If the existing foundation is broken, say "scrap it and do this instead."
* **Data model exception to "right-sized diff": for schema and model changes, diff size is NOT a goal.** Normalize first; denormalize only for measured performance reasons. Three clean models that each do one thing are better than one model doing three things, in all respects. When tempted to merge new fields into an existing model to reduce table count, ask: "does each model have exactly one job?" If splitting is honest, split. Count concepts, not tables. The default for schema design is more clean parts, not fewer muddled ones.
* **JSONField is not an escape hatch for polymorphism.** When tempted to add a JSONField/HStoreField/payload column to encode variant-specific data (different keys for different kinds), promote it to explicit columns instead. JSONField loses FK enforcement, cascade/protect behavior, CheckConstraints on shape, queryability, type system, and self-documentation. Diagnostic: if you find yourself documenting "what keys appear in this JSONField for which variant," you have schema, you just put it in a place where the DB can't help you. Legitimate JSONField uses are genuinely schemaless data (third-party API response caches, user preference bags, opaque blobs). Polymorphism with knowable variants belongs in explicit columns + CheckConstraints.
* **Data model exception to "right-sized diff": for schema and model changes, don't let diff size alone decide the shape.** Normalize first; denormalize when you have a measured reason — a profiled hot path, a load test, a documented query-plan cost — and say so in the plan. Splitting a model that's doing two unrelated jobs into two clean models is usually worth the extra table, but it's a judgment call, not an automatic rule: if the "split" model would only ever be read through one FK join, written by exactly one code path, and queried by nothing else, keeping it inline can be the right, deliberate choice too. When tempted to merge new fields into an existing model purely to reduce table count, ask: "does each model have exactly one job?" If splitting is honest, split; if the fields genuinely belong to the same concept, don't split just to look normalized. Count concepts, not tables.
* **JSONField is not an escape hatch for polymorphism.** When tempted to add a JSONField/HStoreField/payload column to encode variant-specific data (different keys for different kinds) that you can already enumerate and expect to stay stable, promote it to explicit columns instead. JSONField loses FK enforcement, cascade/protect behavior, CheckConstraints on shape, queryability, type system, and self-documentation. Diagnostic: if you find yourself documenting "what keys appear in this JSONField for which variant," you have schema, you just put it in a place where the DB can't help you. This isn't a blanket ban on JSON columns — legitimate uses are common: caching a third-party API's response verbatim (Stripe webhook payloads, OAuth provider profiles), a genuinely open-ended user-preferences bag nobody queries into, event/analytics payloads shaped by an external producer, or a schema still being discovered (variants that aren't stable yet — revisit once they are). Polymorphism with knowable, stable variants belongs in explicit columns + CheckConstraints; polymorphism you don't control or haven't discovered yet can legitimately stay in JSON.
## Cognitive Patterns — How Great Eng Managers Think
@ -851,9 +851,9 @@ These are not additional checklist items. They are the instincts that experience
8. **Org structure IS architecture** — Conway's Law in practice. Design both intentionally (Skelton/Pais, Team Topologies).
9. **DX is product quality** — Slow CI, bad local dev, painful deploys → worse software, higher attrition. Developer experience is a leading indicator.
10. **Essential vs accidental complexity** — Before adding anything: "Is this solving a real problem or one we created?" (Brooks, No Silver Bullet).
11. **Normalize first, denormalize for measured reasons** — Codd's normal forms (1970) and Knuth's "premature optimization is the root of all evil" (1974) both apply directly to schema design. Denormalizing without measurement is exactly what Knuth warned against. The default for data model design is more clean parts, not fewer muddled ones. "Make it work, make it right, make it fast" (Beck) — right shape comes BEFORE fast.
12. **Single Responsibility Principle applies to data models** — A model that's doing audit + balance + notifications is failing SRP. A model that has fields that are only meaningful when other fields have specific values is failing SRP via hidden polymorphism. Split until each model has exactly one job (Martin, Clean Architecture; Codd, normal forms).
13. **Structure beats blobs for known polymorphism** — If you can enumerate the variants of a thing at design time, encode them as explicit columns + CheckConstraints, not as a JSONField/payload bag. The DB can enforce FK integrity, shape constraints, and indexing on columns; it can't on JSON paths. JSONField is for genuinely opaque data (third-party blobs, user preferences nobody queries on), not for "we know there are 4 kinds and each kind has different fields."
11. **Normalize first, denormalize for measured reasons** — Codd's normal forms (1970) and Knuth's "premature optimization is the root of all evil" (1974) both apply directly to schema design. Denormalizing without measurement is exactly what Knuth warned against — denormalizing WITH a measurement (a profiled query, a load test, an explicit latency budget) is exactly what the same principle allows. "Make it work, make it right, make it fast" (Beck) — right shape comes BEFORE fast, but once you've measured that a specific join is the bottleneck, denormalizing that one relationship IS the "make it fast" step working as intended, not a violation.
12. **Single Responsibility Principle applies to data models** — A model that's doing audit + balance + notifications is failing SRP. A model that has fields that are only meaningful when other fields have specific values is failing SRP via hidden polymorphism. Split until each model has exactly one job (Martin, Clean Architecture; Codd, normal forms) — unless the "extra job" is a single trivial field with no independent query pattern or lifecycle, in which case call out the tradeoff rather than mandating a split.
13. **Structure beats blobs for known polymorphism** — If you can enumerate the variants of a thing at design time AND expect that enumeration to stay stable, encode them as explicit columns + CheckConstraints, not as a JSONField/payload bag. The DB can enforce FK integrity, shape constraints, and indexing on columns; it can't on JSON paths. This cuts the other way for polymorphism you don't yet control: third-party blobs, opaque user preferences, and schemas still being discovered are legitimately JSONField. The target is "we know there are 4 kinds and each kind has different fields," not "we don't yet know what shape this will take."
14. **Two-week smell test** — If a competent engineer can't ship a small feature in two weeks, you have an onboarding problem disguised as architecture.
15. **Glue work awareness** — Recognize invisible coordination work. Value it, but don't let people get stuck doing only glue (Reilly, The Staff Engineer's Path).
16. **Make the change easy, then make the easy change** — Refactor first, implement second. Never structural + behavioral changes simultaneously (Beck).

View File

@ -62,8 +62,8 @@ If the user asks you to compress or the system triggers context compaction: Step
* I err on the side of handling more edge cases, not fewer; thoughtfulness > speed.
* Bias toward explicit over clever.
* Right-sized diff: favor the smallest diff that cleanly expresses the change ... but don't compress a necessary rewrite into a minimal patch. If the existing foundation is broken, say "scrap it and do this instead."
* **Data model exception to "right-sized diff": for schema and model changes, diff size is NOT a goal.** Normalize first; denormalize only for measured performance reasons. Three clean models that each do one thing are better than one model doing three things, in all respects. When tempted to merge new fields into an existing model to reduce table count, ask: "does each model have exactly one job?" If splitting is honest, split. Count concepts, not tables. The default for schema design is more clean parts, not fewer muddled ones.
* **JSONField is not an escape hatch for polymorphism.** When tempted to add a JSONField/HStoreField/payload column to encode variant-specific data (different keys for different kinds), promote it to explicit columns instead. JSONField loses FK enforcement, cascade/protect behavior, CheckConstraints on shape, queryability, type system, and self-documentation. Diagnostic: if you find yourself documenting "what keys appear in this JSONField for which variant," you have schema, you just put it in a place where the DB can't help you. Legitimate JSONField uses are genuinely schemaless data (third-party API response caches, user preference bags, opaque blobs). Polymorphism with knowable variants belongs in explicit columns + CheckConstraints.
* **Data model exception to "right-sized diff": for schema and model changes, don't let diff size alone decide the shape.** Normalize first; denormalize when you have a measured reason — a profiled hot path, a load test, a documented query-plan cost — and say so in the plan. Splitting a model that's doing two unrelated jobs into two clean models is usually worth the extra table, but it's a judgment call, not an automatic rule: if the "split" model would only ever be read through one FK join, written by exactly one code path, and queried by nothing else, keeping it inline can be the right, deliberate choice too. When tempted to merge new fields into an existing model purely to reduce table count, ask: "does each model have exactly one job?" If splitting is honest, split; if the fields genuinely belong to the same concept, don't split just to look normalized. Count concepts, not tables.
* **JSONField is not an escape hatch for polymorphism.** When tempted to add a JSONField/HStoreField/payload column to encode variant-specific data (different keys for different kinds) that you can already enumerate and expect to stay stable, promote it to explicit columns instead. JSONField loses FK enforcement, cascade/protect behavior, CheckConstraints on shape, queryability, type system, and self-documentation. Diagnostic: if you find yourself documenting "what keys appear in this JSONField for which variant," you have schema, you just put it in a place where the DB can't help you. This isn't a blanket ban on JSON columns — legitimate uses are common: caching a third-party API's response verbatim (Stripe webhook payloads, OAuth provider profiles), a genuinely open-ended user-preferences bag nobody queries into, event/analytics payloads shaped by an external producer, or a schema still being discovered (variants that aren't stable yet — revisit once they are). Polymorphism with knowable, stable variants belongs in explicit columns + CheckConstraints; polymorphism you don't control or haven't discovered yet can legitimately stay in JSON.
## Cognitive Patterns — How Great Eng Managers Think
@ -79,9 +79,9 @@ These are not additional checklist items. They are the instincts that experience
8. **Org structure IS architecture** — Conway's Law in practice. Design both intentionally (Skelton/Pais, Team Topologies).
9. **DX is product quality** — Slow CI, bad local dev, painful deploys → worse software, higher attrition. Developer experience is a leading indicator.
10. **Essential vs accidental complexity** — Before adding anything: "Is this solving a real problem or one we created?" (Brooks, No Silver Bullet).
11. **Normalize first, denormalize for measured reasons** — Codd's normal forms (1970) and Knuth's "premature optimization is the root of all evil" (1974) both apply directly to schema design. Denormalizing without measurement is exactly what Knuth warned against. The default for data model design is more clean parts, not fewer muddled ones. "Make it work, make it right, make it fast" (Beck) — right shape comes BEFORE fast.
12. **Single Responsibility Principle applies to data models** — A model that's doing audit + balance + notifications is failing SRP. A model that has fields that are only meaningful when other fields have specific values is failing SRP via hidden polymorphism. Split until each model has exactly one job (Martin, Clean Architecture; Codd, normal forms).
13. **Structure beats blobs for known polymorphism** — If you can enumerate the variants of a thing at design time, encode them as explicit columns + CheckConstraints, not as a JSONField/payload bag. The DB can enforce FK integrity, shape constraints, and indexing on columns; it can't on JSON paths. JSONField is for genuinely opaque data (third-party blobs, user preferences nobody queries on), not for "we know there are 4 kinds and each kind has different fields."
11. **Normalize first, denormalize for measured reasons** — Codd's normal forms (1970) and Knuth's "premature optimization is the root of all evil" (1974) both apply directly to schema design. Denormalizing without measurement is exactly what Knuth warned against — denormalizing WITH a measurement (a profiled query, a load test, an explicit latency budget) is exactly what the same principle allows. "Make it work, make it right, make it fast" (Beck) — right shape comes BEFORE fast, but once you've measured that a specific join is the bottleneck, denormalizing that one relationship IS the "make it fast" step working as intended, not a violation.
12. **Single Responsibility Principle applies to data models** — A model that's doing audit + balance + notifications is failing SRP. A model that has fields that are only meaningful when other fields have specific values is failing SRP via hidden polymorphism. Split until each model has exactly one job (Martin, Clean Architecture; Codd, normal forms) — unless the "extra job" is a single trivial field with no independent query pattern or lifecycle, in which case call out the tradeoff rather than mandating a split.
13. **Structure beats blobs for known polymorphism** — If you can enumerate the variants of a thing at design time AND expect that enumeration to stay stable, encode them as explicit columns + CheckConstraints, not as a JSONField/payload bag. The DB can enforce FK integrity, shape constraints, and indexing on columns; it can't on JSON paths. This cuts the other way for polymorphism you don't yet control: third-party blobs, opaque user preferences, and schemas still being discovered are legitimately JSONField. The target is "we know there are 4 kinds and each kind has different fields," not "we don't yet know what shape this will take."
14. **Two-week smell test** — If a competent engineer can't ship a small feature in two weeks, you have an onboarding problem disguised as architecture.
15. **Glue work awareness** — Recognize invisible coordination work. Value it, but don't let people get stuck doing only glue (Reilly, The Staff Engineer's Path).
16. **Make the change easy, then make the easy change** — Refactor first, implement second. Never structural + behavioral changes simultaneously (Beck).

View File

@ -51,7 +51,7 @@ Evaluate:
* Data flow patterns and potential bottlenecks.
* Scaling characteristics and single points of failure.
* Security architecture (auth, data access, API boundaries).
* **Data model honesty:** Does each new model/table have exactly one job? Are there nullable fields whose NULL has semantic meaning ("this row is a different kind of thing")? Are there clusters of columns that always co-vary, suggesting a hidden child model? Does any field on a parent model only have meaning when no child rows exist (parent-field-shadowed-by-child smell)? Is there a JSONField/payload column whose keys-per-variant are knowable at design time (JSONField-hiding-schema smell — promote to explicit columns + CheckConstraints)? When in doubt, normalize. Three clean models > one muddled model in all respects.
* **Data model honesty:** Does each new model/table have exactly one job? Are there nullable fields whose NULL has semantic meaning ("this row is a different kind of thing")? Are there clusters of columns that always co-vary, suggesting a hidden child model? Does any field on a parent model only have meaning when no child rows exist (parent-field-shadowed-by-child smell)? Is there a JSONField/payload column whose keys-per-variant are knowable and stable at design time (JSONField-hiding-schema smell — promote to explicit columns + CheckConstraints)? When in doubt, normalize — but "in doubt" means genuinely unclear, not "this would add a table." A model that's already cleanly single-purpose, or a denormalization backed by a stated measurement, isn't a smell just because it isn't maximally split.
* Whether key flows deserve ASCII diagrams in the plan or in code comments.
* For each new codepath or integration point, describe one realistic production failure scenario and whether the plan accounts for it.
* **Distribution architecture:** If this introduces a new artifact (binary, package, container), how does it get built, published, and updated? Is the CI/CD pipeline part of the plan or deferred?
@ -62,7 +62,7 @@ For every new model, model change, or schema migration in this plan, run through
**Single Responsibility (SRP for models)**
- Does this model have exactly one job? Audit ≠ balance ≠ notification ≠ display.
- If a model is doing N jobs, propose splitting it into N models.
- If a model is doing N jobs, propose splitting it into N models — unless the "extra job" is a single trivial field with no independent query pattern, write path, or consumer, in which case surface it as a tradeoff rather than mandate the split.
**Nullable with semantic meaning**
- For every nullable column, ask: does NULL flip what kind of thing this row is?
@ -80,9 +80,9 @@ For every new model, model change, or schema migration in this plan, run through
- If yes, the parent's field belongs on the child. Remove from parent, require ≥1 child row, read via FK navigation.
**JSONField as polymorphism escape hatch**
- For every JSONField, ask: am I documenting "what keys appear for which variant"?
- If yes, the JSONField has schema. Promote to explicit columns + CheckConstraints.
- Legitimate JSONField uses: third-party API caches, opaque user preferences, schemaless attribute bags nobody queries against.
- For every JSONField, ask: am I documenting "what keys appear for which variant," and are those variants stable?
- If yes to both, the JSONField has schema. Promote to explicit columns + CheckConstraints.
- Legitimate JSONField uses: caching a third-party API response verbatim (webhook payloads, OAuth profiles), a genuinely open-ended user-preferences bag nobody queries into, event/analytics payloads shaped by an external producer, and early-stage schemas whose variants aren't stable yet. None of these are escape hatches — they're cases where the DB genuinely can't add value over the JSON blob.
**FK deletion strategies (CASCADE / PROTECT / SET_NULL)**
- For every FK, ask: if the parent is deleted, what should happen to this row?

View File

@ -13,7 +13,7 @@ Evaluate:
* Data flow patterns and potential bottlenecks.
* Scaling characteristics and single points of failure.
* Security architecture (auth, data access, API boundaries).
* **Data model honesty:** Does each new model/table have exactly one job? Are there nullable fields whose NULL has semantic meaning ("this row is a different kind of thing")? Are there clusters of columns that always co-vary, suggesting a hidden child model? Does any field on a parent model only have meaning when no child rows exist (parent-field-shadowed-by-child smell)? Is there a JSONField/payload column whose keys-per-variant are knowable at design time (JSONField-hiding-schema smell — promote to explicit columns + CheckConstraints)? When in doubt, normalize. Three clean models > one muddled model in all respects.
* **Data model honesty:** Does each new model/table have exactly one job? Are there nullable fields whose NULL has semantic meaning ("this row is a different kind of thing")? Are there clusters of columns that always co-vary, suggesting a hidden child model? Does any field on a parent model only have meaning when no child rows exist (parent-field-shadowed-by-child smell)? Is there a JSONField/payload column whose keys-per-variant are knowable and stable at design time (JSONField-hiding-schema smell — promote to explicit columns + CheckConstraints)? When in doubt, normalize — but "in doubt" means genuinely unclear, not "this would add a table." A model that's already cleanly single-purpose, or a denormalization backed by a stated measurement, isn't a smell just because it isn't maximally split.
* Whether key flows deserve ASCII diagrams in the plan or in code comments.
* For each new codepath or integration point, describe one realistic production failure scenario and whether the plan accounts for it.
* **Distribution architecture:** If this introduces a new artifact (binary, package, container), how does it get built, published, and updated? Is the CI/CD pipeline part of the plan or deferred?
@ -24,7 +24,7 @@ For every new model, model change, or schema migration in this plan, run through
**Single Responsibility (SRP for models)**
- Does this model have exactly one job? Audit ≠ balance ≠ notification ≠ display.
- If a model is doing N jobs, propose splitting it into N models.
- If a model is doing N jobs, propose splitting it into N models — unless the "extra job" is a single trivial field with no independent query pattern, write path, or consumer, in which case surface it as a tradeoff rather than mandate the split.
**Nullable with semantic meaning**
- For every nullable column, ask: does NULL flip what kind of thing this row is?
@ -42,9 +42,9 @@ For every new model, model change, or schema migration in this plan, run through
- If yes, the parent's field belongs on the child. Remove from parent, require ≥1 child row, read via FK navigation.
**JSONField as polymorphism escape hatch**
- For every JSONField, ask: am I documenting "what keys appear for which variant"?
- If yes, the JSONField has schema. Promote to explicit columns + CheckConstraints.
- Legitimate JSONField uses: third-party API caches, opaque user preferences, schemaless attribute bags nobody queries against.
- For every JSONField, ask: am I documenting "what keys appear for which variant," and are those variants stable?
- If yes to both, the JSONField has schema. Promote to explicit columns + CheckConstraints.
- Legitimate JSONField uses: caching a third-party API response verbatim (webhook payloads, OAuth profiles), a genuinely open-ended user-preferences bag nobody queries into, event/analytics payloads shaped by an external producer, and early-stage schemas whose variants aren't stable yet. None of these are escape hatches — they're cases where the DB genuinely can't add value over the JSON blob.
**FK deletion strategies (CASCADE / PROTECT / SET_NULL)**
- For every FK, ask: if the parent is deleted, what should happen to this row?

View File

@ -146,8 +146,9 @@ export const CARVE_GUARDS: Record<string, CarveGuard> = {
externalTest: 'test/skill-e2e-plan-ceo-review-section-loading.test.ts',
// Data-model bias fix (garrytan/gstack#1048) adds the "right-sized diff" schema
// exception + JSONField-polymorphism warning to the always-loaded preferences
// list — small (~600B) but pushes past the prior 90_000 skeleton budget.
maxSkeletonBytes: 91_000,
// list, then review feedback narrowed both bullets and added legitimate-JSON /
// measured-denormalization counterexamples — pushes past the prior 90_000 budget.
maxSkeletonBytes: 92_000,
minUnionBytes: 80_000,
mustContain: ['SCOPE EXPANSION', 'SELECTIVE EXPANSION', 'HOLD SCOPE', 'SCOPE REDUCTION'],
// Default-on Codex outside-voice (codexPreflight block + CODEX_MODE branch
@ -170,8 +171,10 @@ export const CARVE_GUARDS: Record<string, CarveGuard> = {
// Data-model bias fix (garrytan/gstack#1048) adds the schema-normalization
// preference exception + JSONField warning + 3 cognitive patterns (Codd/Knuth/
// Beck normalize-first, Martin SRP-for-models, structure-beats-blobs) to the
// always-loaded skeleton — pushes past the prior 67_000 budget.
maxSkeletonBytes: 69_000,
// always-loaded skeleton, then review feedback narrowed the absolute language
// and added legitimate-JSON / measured-denormalization counterexamples —
// pushes past the prior 67_000 budget.
maxSkeletonBytes: 70_000,
minUnionBytes: 70_000,
mustContain: ['Architecture', 'Code Quality', 'Test', 'Performance'],
// Cross-cutting preamble growth (v1.57.2.0 AUQ-failure prose fallback + the