Skip to content

Implement RunOptions/ScenarioDefinition shape validation (REQ-CONFIG-005) #74

Description

@drevendev

Goal

Implement the slice of REQ-CONFIG-005 that is actually realizable today:
fail-fast, non-coercing validation of RunOptions and of ScenarioDefinition's
required top-level field shapes, so an invalid run configuration or a
structurally malformed scenario candidate is rejected before it can reach
world construction.

Evidence

docs/spec/mirror/REQUIREMENTS_REGISTRY.csv row REQ-CONFIG-005: area
CONFIG, type invariant, priority P0, status READY,
DEPENDS_ON=REQ-CONFIG-001 (already IMPLEMENTED, PR #67, merge commit
493da16e53e58e78a830ad44f1da627f9196742c). Acceptance: "Negative/invalid
cross-reference and numeric cases fail before canonical world execution."

docs/spec/mirror/06 - Handoff/03 — CANONICAL_CONFIG_AND_WORLD_GENERATION.md
section 21 ("Validation rules") lists ~20 fail-fast conditions: non-finite
values, min > max pairs, EMA alpha outside (0,1], probability/share
outside [0,1], cadence < 1, soft maxima below target, unknown
Good/Recipe/Event/Metric IDs, missing Region/State/Clan/Currency/Authority
references, duplicate keys, per-entity numeric bounds (e.g. ProductionUnit
installedCapital < 0), bond-holdings-sum checks, and genesis reconciliation
failures.

Fact, not inference — checked directly against the current source, not
assumed from the document:
every one of those bound/reference checks
targets a field that does not exist in the codebase yet:

  • src/config/simulationConfig.ts declares NumericConfig, CadenceConfig,
    MarketConfig, TradeConfig, ProductionConfig, LaborConfig,
    PopulationConfig, ClanConfig, FiscalConfig, MonetaryConfig,
    ExpansionConfig, EventConfig and PerformanceConfig as empty {}
    placeholder interfaces. The file's own header comment states their
    concrete fields "land with the requirement that owns the corresponding
    subsystem (M3-M10)" — i.e. not this requirement.
  • src/config/scenarioDefinition.ts declares every seed type
    (RegionSeed, TransportLinkSeed, StateSeed, CurrencySeed,
    MonetaryAuthoritySeed, ClanSeed, CohortSeed, ProductionUnitSeed,
    MarketSeed, BondSeed, InitialEventSeed, ScenarioVariationConfig) as
    empty {} placeholders, explicitly pending REQ-CONFIG-003/REQ-CORE-003.
  • src/config/definitionPack.ts declares GoodDefinition, RecipeDefinition,
    EventDefinition and MetricDefinition the same way, pending
    REQ-CONFIG-003.

Only two layers currently carry concrete fields: RunOptions
(scenarioId, seed, maxTicks?, diagnosticsLevel) and
ScenarioDefinition's own top-level shape (id, version, name,
description, definitionPackId, plus its required/optional array/object
fields) — the latter already has a key-membership check
(src/config/validation.ts's assertNoBehavioralOverrides, from
REQ-CONFIG-001) but no check that a present key's value actually has the
required shape.

Scope

Add fail-fast, non-coercing validation, alongside (not replacing)
assertNoBehavioralOverrides, for exactly the fields that exist today:

  1. RunOptions:
    • seed is a finite number (reject NaN/±Infinity/non-number);
    • maxTicks, when present, is a finite positive integer;
    • diagnosticsLevel is exactly one of 'OFF' | 'SUMMARY' | 'DEBUG';
    • scenarioId is a non-empty string.
  2. ScenarioDefinition top-level required-field shape (beyond key
    membership):
    • id, version, name, description, definitionPackId are
      non-empty strings;
    • geography, transportLinks, states, currencies,
      monetaryAuthorities, clans, cohorts, productionUnits are arrays;
    • markets, bonds, initialEvents, when present, are arrays;
    • variation, when present, is a plain object.
  3. Every rejection throws with a message identifying the offending field and
    what was found, matching section 21's "do not silently coerce" rule.

Non-goals

  • Any bound/EMA-alpha/cadence/min-max validation of fields inside
    NumericConfig/CadenceConfig/MarketConfig/TradeConfig/
    ProductionConfig/LaborConfig/PopulationConfig/ClanConfig/
    FiscalConfig/MonetaryConfig/ExpansionConfig/EventConfig/
    PerformanceConfig — these are empty placeholders owned by later M3-M10
    requirements per Evidence above; there is nothing to bound-check yet.
  • Any content/cross-reference/uniqueness validation inside a
    ScenarioDefinition seed type (RegionSeed, TransportLinkSeed,
    StateSeed, CurrencySeed, MonetaryAuthoritySeed, ClanSeed,
    CohortSeed, ProductionUnitSeed, MarketSeed, BondSeed,
    InitialEventSeed) — these are empty placeholders pending
    REQ-CONFIG-003; duplicate-key and missing-reference checks cannot be
    written against a type with no fields.
  • DefinitionPack content validation (GoodDefinition, RecipeDefinition,
    EventDefinition, MetricDefinition) — same reason, pending
    REQ-CONFIG-003.
  • Genesis-level reconciliation checks ("initial money/goods stocks that fail
    genesis reconciliation") — REQ-CONFIG-004.
  • Changing assertNoBehavioralOverrides's existing key-membership behavior;
    new checks must be additive.
  • A follow-up Issue to layer in the remaining section-21 checks once
    REQ-CONFIG-003 and the relevant M3-M10 subsystem requirements land
    concrete fields — that follow-up is separate work, not part of this Issue.

Acceptance criteria

  • A validation function rejects a RunOptions candidate whose seed is
    NaN, +Infinity, -Infinity, or not a number.
  • A validation function rejects a RunOptions candidate whose maxTicks
    is present and is non-finite, non-integer, or <= 0.
  • A validation function rejects a RunOptions candidate whose
    diagnosticsLevel is not exactly 'OFF', 'SUMMARY', or 'DEBUG'.
  • A validation function rejects a RunOptions candidate whose
    scenarioId is empty or not a string.
  • A validation function accepts a minimal well-formed RunOptions
    unchanged (no mutation/coercion of any field).
  • A validation function rejects a ScenarioDefinition candidate missing
    any required top-level field, or where a required field has the wrong
    primitive/array shape (e.g. geography given as a plain object instead
    of an array, id given as a number).
  • A validation function accepts a minimal well-formed ScenarioDefinition
    (the same shape src/config/validation.test.ts's minimalScenario()
    already uses) unchanged.
  • npm run typecheck, npm test, and npm run build pass.
  • docs/spec/IMPLEMENTATION_STATUS.md gains a REQ-CONFIG-005 row once
    this merges, explicitly recording that only the RunOptions +
    ScenarioDefinition top-level-shape slice is covered, and naming the
    sub-config/seed-content validation left open per this Issue's
    Non-goals.

Verification

npm ci
npm run typecheck
npm test
npm run build

Dependencies, authority, and risks

  • REQ-CONFIG-001 is already IMPLEMENTED (PR Implement the canonical configuration hierarchy (REQ-CONFIG-001) #67); no other open
    dependency blocks this Issue.
  • Risk: a reviewer unfamiliar with the empty-placeholder state of
    NumericConfig/seed types might expect this Issue to cover all of section
    21. The Evidence section above documents, from the current source, why
    that is not yet possible — the highest-risk area for review is confirming
    that scoping boundary is correct rather than an implementer shortcut.
  • No token/workflow/permission dependency; this is a pure src/config
    change plus its tests.

Activity

  1. added
    priority:highImportant and time-sensitive; schedule ahead of normal work
    type:featureNew simulation capability or observable behavior
    area:simulation-coreTick loop, ordering, determinism, configuration
    status:readySpecified and unblocked; safe for an agent to claim
    status:in-progressClaimed work with an active branch or pull request
    and removed
    status:readySpecified and unblocked; safe for an agent to claim
    on Sep 5, 2026
  2. drevendev commented on Sep 5, 2026

    @drevendev
    OwnerAuthor

    AUTHOR claim

    • Role: AUTHOR
    • Scope: REQ-CONFIG-005 — fail-fast, non-coercing validation of RunOptions and ScenarioDefinition's required top-level field shapes, exactly as scoped in this Issue. Additive alongside assertNoBehavioralOverrides; no NumericConfig/CadenceConfig/etc. bound checks, no seed-content/cross-reference checks (per Non-goals — those types remain empty placeholders pending REQ-CONFIG-003).
    • Branch: claude/issue-74-config-005-shape-validation
    • Known blocker: none yet.

    Note: this run also reconciled docs/spec/IMPLEMENTATION_STATUS.md's stale REQ-CORE-003 row (PR #73 merged as 3b6f2ebc87c204de75e0f2064df3c33f9e9efb48, closing Issue #72, since that row was last written) per AUTHOR_RUNBOOK.md section 1; that correction is carried in the same pull request as this Issue's work.

  3. added
    status:needs-reviewImplementation complete, awaiting acceptance
    and removed
    status:in-progressClaimed work with an active branch or pull request
    on Sep 5, 2026
  4. drevendev commented on Sep 5, 2026

    @drevendev
    OwnerAuthor

    AUTHOR handoff

    Decisions: added a new sibling module src/config/shapeValidation.ts (rather
    than extending src/config/validation.ts) since that file's own doc comment
    already scopes itself to key-membership only and names REQ-CONFIG-005 as
    out of its own scope. Both new functions are additive alongside
    assertNoBehavioralOverrides.

    Checks: npm ci/typecheck/test/build all passed at the tested
    revision (137 tests, 26 new); scripts/tests and policy_guard.py passed;
    dotnet build/test reported not_run — no TradeCraftSimulation/** file
    changed, so legacy risk is none. Full evidence is in PR #76's Checks table.

    This run also reconciled IMPLEMENTATION_STATUS.md's stale REQ-CORE-003
    row (PR #73 merged as 3b6f2eb, closing Issue #72) in the same PR, per
    AUTHOR_RUNBOOK.md section 1.

    What remains: none for this PR's own scope. IMPLEMENTATION_STATUS.md
    gains a REQ-CONFIG-005 row only in a follow-up once this merges and its
    merge commit is known — the row cannot honestly claim IMPLEMENTED while
    naming its own not-yet-existent merge commit. Setting status:needs-review.

  5. drevendev commented on Sep 5, 2026

    @drevendev
    OwnerAuthor

    ACCEPTED and merged as squash commit 561e45290915d3107310e1de828af06c4f061b60 (PR #76, tested revision 524d95806bb25171f05b215d14103d6cf676ad1a).

    Verified independently before merge: npm ci && npm run typecheck && npm test && npm run build all passed at head (17 files / 137 tests); required CI checks (build-and-test, policy-guard, typescript) green at head; diff confined to src/config/shapeValidation.ts, src/config/shapeValidation.test.ts, src/config/index.ts, docs/spec/IMPLEMENTATION_STATUS.md; no invariant or test weakened; no secrets present. All acceptance criteria met except the self-referential IMPLEMENTATION_STATUS.md row, which per this repo's established precedent (Issue #72/PR #73 for REQ-CORE-003) is added in a later reconciliation PR rather than this one, since the merge commit can't be known before merging. Full verdict posted on PR #76.

    Removing status:needs-review since the Issue is now closed.

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

    area:simulation-coreTick loop, ordering, determinism, configurationpriority:highImportant and time-sensitive; schedule ahead of normal worktype:featureNew simulation capability or observable behavior

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions