Skip to content

T-DJ5G-gate-create-allowlist-covers-schema-fields

Status: open/ready · Impact: medium · Complexity: small

AUTO-DEFINED: this spec was best-effort machine-authored by /sdlc:task-auto-define on 2026-08-03 because the task was spawned non-interactively. Review the Goal, Approach, Today, Files-to-touch, and Acceptance-criteria carefully before trusting it.

Entity create ops carry a hand-maintained fields: allowlist that must be edited in lockstep with the entity’s frontmatter schema. The shared factory validates in one direction only — a declared field missing from the schema throws, but a schema field missing from the declaration is silently invisible, so the new flag never appears on the CLI and nothing fails. Add a gate that checks the other direction: a schema field must be either declared creatable or explicitly marked non-creatable, so the omission fails at commit time instead of surfacing through a hand-run --help check.

Adding kind to the Task schema did not make --kind appear on sdlc task create: apps/sdlc/lib/model/entities/task/ops/create.ts carries an explicit fields: allowlist that must be edited in lockstep with every new schema field, and the omission surfaced only through a manual --help check. A gate asserting that each entity create op’s allowlist covers every creatable schema field would catch this class of omission automatically. This is an sdlc-general convention (create ops are sdlc’s own surface), so the gate belongs in apps/sdlc/lib/services/gate, not in repo-level tooling.

T-JOXA-task-kind-field-and-leaf-dispatch

LocationRole today
apps/sdlc/lib/model/entities/task/ops/create.tsThe task create spec. Its fields: map names the nine creation-settable inputs by hand; a schema field absent here produces no CLI flag and no error.
apps/sdlc/lib/model/ops/_create.tsThe shared defineCreateOp factory. resolveFieldSchema throws when a DECLARED field is absent from the entity schema, but nothing walks the schema looking for undeclared fields.
apps/sdlc/lib/model/entities/task/schema.tsTaskSchema — the source of truth for task field types and static defaults. Gaining a key here changes validation but not the create surface.
apps/sdlc/lib/model/entities/_registry.tsENTITY_SCHEMAS maps each entity type name to its Zod schema; the enumeration a coverage check would walk.
apps/sdlc/lib/model/entities/task/ops/_lint_state_origin_core.tsHolds TASK_STATE_FIELDS, the existing hand-listed set of workflow-owned task fields — prior art for naming fields that are not author-set.
apps/sdlc/lib/services/gate/ops/The sdlc-general gate ops (skill-prose, worktree-scope, markdown-fixtures, corpus-invocation) — where this check belongs per the root CLAUDE.md split.
apps/sdlc/lib/_generated/op-manifest.tsGenerated static barrel that self-registers op modules; regenerated by apps/sdlc/scripts/gen-op-manifest.ts.
lefthook.ymlPre-commit wiring. Existing gates run as project-check-* entries shelling out to bun apps/sdlc/cli/sdlc.ts gate ....

sdlc gate create-fields-coverage walks the entity-schema registry and, for each type that registered a create spec, diffs the schema’s frontmatter keys against the spec’s declared fields: keys. A key that is neither declared creatable nor explicitly listed as non-creatable is reported as uncovered and the gate exits non-zero, naming the entity, the field, and the create-op file to edit. Non-creatable keys become an explicit nonCreatable: declaration on the create spec, sitting next to the allowlist it complements, so adding a schema field forces a conscious choice between “expose it at birth” and “workflow owns it” instead of defaulting to silence. The gate runs in lefthook.yml alongside the other pre-commit checks.

  1. In apps/sdlc/lib/model/ops/_create.ts, add an optional nonCreatable?: string[] key to CreateOpSpec, documented as the schema keys deliberately not settable at birth (workflow-owned state, legacy fields). Add a module-level COMMON_NON_CREATABLE set for the keys the authoring pipeline itself owns and no create spec should declare: id, type, schema_version, title, last_reviewed.
  2. In the same file, have defineCreateOp record each spec into an exported registry — CREATE_SPECS: Map<string, { fieldKeys: string[]; nonCreatable: string[] }> keyed by entity type — so the gate reads declarations from the live op definitions rather than re-parsing TypeScript source. Note that fmKey is the schema-side name when it differs from the input key, so record field.fmKey ?? key; skip noFrontmatter fields (task’s headline), which are op-only and have no schema key.
  3. Add the gate op at apps/sdlc/lib/services/gate/ops/create-fields-coverage.ts using defineOp with path: ["gate", "create-fields-coverage"] and hidden: true (matching worktree-scope.ts). Its handler imports ENTITY_SCHEMAS and CREATE_SPECS, and for each entry present in both, computes objectShape(schema) keys minus declared keys minus that spec’s nonCreatable minus COMMON_NON_CREATABLE. Types with no create spec are skipped, not failed.
  4. Give it output { checked: string[]; uncovered: Array<{ type, field, opPath }> } and a render hook that prints one line per uncovered field on stderr — naming the entity type, the field, and the create-op path to edit — and returns 1 when uncovered is non-empty, 0 otherwise.
  5. Populate nonCreatable on the six create specs found by ls apps/sdlc/lib/model/entities/*/ops/create.ts so the gate is green at HEAD. For task that is the workflow-owned and legacy tail: readiness_verified_at, touchpoints_verified_at, definition_gap, completion_note, relevance_note, prs, parent_key, depends_on, resolution, resolution_date, resolution_commit. Reconcile the choice against TASK_STATE_FIELDS in _lint_state_origin_core.ts and note in the spec’s doc-comment where the two sets differ and why.
  6. Regenerate the op barrel by running apps/sdlc/scripts/gen-op-manifest.ts under bun run.
  7. Wire the gate into lefthook.yml as a pre-commit entry (project-check-create-fields-coverage) running bun apps/sdlc/cli/sdlc.ts gate create-fields-coverage, placed beside the existing project-check-skill-prose entry.
  8. Add apps/sdlc/lib/services/gate/tests/create_fields_coverage.test.ts covering: the live registry is clean; a synthetic schema/spec pair with an undeclared key reports exactly that key; a key listed in nonCreatable is not reported; an fmKey-renamed field is matched by its schema-side name.
LocationKindChange
apps/sdlc/lib/services/gate/ops/create-fields-coverage.tsnewThe gate op: diff each registered create spec’s declared keys against its entity schema’s keys.
apps/sdlc/lib/services/gate/tests/create_fields_coverage.test.tsnewUnit tests for the coverage diff, the nonCreatable exemption, and fmKey renames.
apps/sdlc/lib/model/ops/_create.tsmodifyAdd nonCreatable to CreateOpSpec, the COMMON_NON_CREATABLE set, and the exported CREATE_SPECS registry populated by defineCreateOp.
apps/sdlc/lib/model/entities/task/ops/create.tsmodifyDeclare the task schema’s workflow-owned and legacy keys as nonCreatable.
apps/sdlc/lib/model/entities/backlog/ops/create.tsmodifyDeclare that entity’s non-creatable schema keys.
apps/sdlc/lib/model/entities/milestone/ops/create.tsmodifyDeclare that entity’s non-creatable schema keys.
apps/sdlc/lib/model/entities/reference/ops/create.tsmodifyDeclare that entity’s non-creatable schema keys.
apps/sdlc/lib/model/entities/standard/ops/create.tsmodifyDeclare that entity’s non-creatable schema keys.
apps/sdlc/lib/model/entities/term/ops/create.tsmodifyDeclare that entity’s non-creatable schema keys.
apps/sdlc/lib/_generated/op-manifest.tsmodifyRegenerated barrel picks up the new gate module.
lefthook.ymlmodifyAdd the pre-commit entry that runs the new gate.
  • AC-1: bun apps/sdlc/cli/sdlc.ts gate create-fields-coverage exits 0 on a clean checkout, with the six specs listed by ls apps/sdlc/lib/model/entities/*/ops/create.ts each declaring or exempting their schema’s keys.
  • AC-2: A test in apps/sdlc/lib/services/gate/tests/create_fields_coverage.test.ts adds a schema key that is absent from both the spec’s fields: and its nonCreatable:, and asserts the gate reports that key by name and returns a non-zero render exit code.
  • AC-3: A test in the same file asserts a key listed in nonCreatable: is absent from the uncovered output.
  • AC-4: A test in the same file asserts a field declared with fmKey: "target_date" is matched against the schema key target_date, not the input key.
  • AC-5: apps/sdlc/lib/_generated/op-manifest.ts contains an import of @lib/services/gate/ops/create-fields-coverage.ts, and lefthook.yml contains a pre-commit entry invoking gate create-fields-coverage.
  • AC-6: bun test apps/sdlc and bunx tsc --noEmit both exit 0.
  • Auto-generating the fields: allowlist from the schema. The gate reports the omission; the author still decides how the field is exposed.
  • The update-op field surface in apps/sdlc/lib/model/ops/_update.ts. A sibling gap worth its own task once this direction is proven.
  • Template and skill-prose coverage — whether a newly creatable field also appears in the entity’s Eta template or its authoring skill.
  • Backfilling missing flags discovered by the gate beyond what is needed to make the six current specs pass.
  • T-JOXA-task-kind-field-and-leaf-dispatch adds kind to the task schema. If it merges first, kind must be declared creatable or non-creatable here; if this task merges first, that task’s PR carries the declaration.

Spawned by /sdlc:spawn-task-pr on 2026-08-03 UTC from T-JOXA-task-kind-field-and-leaf-dispatch in git@github.com:sksizer/dev.git.


← Back to Tasks