Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion docs/spec/IMPLEMENTATION_STATUS.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,9 @@ file changes; never carry a historical total forward independently of the regist
| REQ-VISUALIZATION-003 | `IMPLEMENTED` | #42 | #45, `d27a94e63580a2d7d0da005a245ae36fec5560fd` | `src/diagnostics/m0Preview.test.ts` — asserts `docs/m0-preview.json`'s legacy baseline seed/turns/hash match `docs/spec/LEGACY_BASELINE.md` exactly, its canonical-scaffolding flag matches the real `toolchainStatus()` in `src/index.ts`, and `docs/index.html` fetches the static artifact without referencing any canonical or legacy runtime module path. Manual build/render smoke check (headless-Chromium screenshots at 1280×1000, 360×900, and a full-page 1100×1900 capture) against a local static server, described in `docs/spec/M0_PREVIEW.md`; 7 files / 16 tests passed and both runtime suites green at merge commit `d27a94e63580a2d7d0da005a245ae36fec5560fd`. Actual production GitHub Pages deployment was explicitly out of AUTHOR scope in #45 and has not been independently re-verified by this reconciliation, which is bounded to `gh pr list` and the merged pull request's own body per `AUTHOR_RUNBOOK.md` section 1. |
| REQ-CORE-001 | `IMPLEMENTED` | #46 | #48, `58a0b49156131ec81fcb23d357f7e15b327bde4d` | `src/domain/id.test.ts` (13-kind prefix table, per-kind sequential allocation, duplicate-creation-key rejection, cross-run replay determinism, stable creation-key-order contract independent of array/Map build order, `CORE-T16` retirement/non-reuse including wrong-kind and double-retire rejection, and the corrected-head regressions verified at `c584d830ce46d68a398c089b06d72b1ced7b33c0` — failure-atomic batch allocation and runtime kind/creation-key validation) plus `src/domain/id.typecheck.test.ts` (`tsc --noEmit` regressions proving distinct ID kinds and bare strings are not mutually assignable). See `docs/adr/0003-canonical-identity-and-allocation.md` for the design. |
| REQ-CORE-002 | `IMPLEMENTED` | #57 | #58, `002343805d14a4d3b43faf089aef9f7d52da9d4a` | `src/domain/numeric.test.ts` (rejects `NaN`/`+Infinity`/`-Infinity`/non-number input, accepts ordinary finite numbers including `0` and negative values) and `src/domain/ordering.test.ts` (`stableOrderBy`/`sortByPersistentId` produce an identical normalized result from differently-shuffled input — `CORE-T2`-shaped evidence — using `src/domain/id.ts` IDs as the concrete key type; a dedicated non-identity assertion proves the sort actually reorders rather than passing vacuously). |
| REQ-CONFIG-001 | `IN_PROGRESS` | #64 | pending — implemented on branch `claude/issue-64-config-hierarchy`, not yet merged | `src/config/configLayers.typecheck.test.ts` (`tsc --noEmit` regressions proving `RunOptions`, `SimulationConfig`, `ScenarioDefinition` and `DefinitionPack` are structurally distinct, non-interchangeable types) and `src/config/validation.test.ts` (`assertNoBehavioralOverrides` accepts a minimal well-formed scenario and the legitimate array-shaped `markets`/`clans` seed fields; rejects a `SimulationConfig`-owned behavioral key including the `markets`/`clans` name-collision case where the smuggled value is an object rather than an array; rejects an arbitrary unknown key; rejects a non-object candidate). Not yet mergeable evidence: promote to `IMPLEMENTED` only once a pull request referencing this row actually merges. |

**Summary: 7 of 25 requirements implemented; 0 in progress.** The other 18
**Summary: 7 of 25 requirements implemented; 1 in progress.** The other 17
requirement identifiers in `docs/spec/mirror/REQUIREMENTS_REGISTRY.csv` — including
the remaining `REQ-VISUALIZATION-002, 004, 005` — are not yet mapped to this table;
that mapping, and their own implementation evidence, is separate follow-up work, not
Expand Down
80 changes: 80 additions & 0 deletions src/config/configLayers.typecheck.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
import { describe, expect, it } from "vitest";

import type { DefinitionPack } from "./definitionPack";
import type { RunOptions } from "./runOptions";
import type { ScenarioDefinition } from "./scenarioDefinition";
import type { SimulationConfig } from "./simulationConfig";

// These assertions are the actual regression: `npm run typecheck` must fail if any
// `@ts-expect-error` below stops being an error (TypeScript reports an unused
// directive as an error under this repo's strict config), proving the four
// configuration layers are structurally distinct, non-interchangeable types —
// section 2's "RunOptions chooses a run... SimulationConfig owns reusable
// behavioral tuning... ScenarioDefinition owns starting world stocks... /
// DefinitionPack owns immutable type definitions" ownership boundary is a
// compile-time guarantee, not just a naming convention.
describe("configuration layer separation (compile-time)", () => {
it("rejects assigning a ScenarioDefinition where RunOptions is required, and vice versa", () => {
const runOptions: RunOptions = { scenarioId: "baseline-multistate-v1", seed: 7, diagnosticsLevel: "OFF" };
const scenario: ScenarioDefinition = {
id: "baseline-multistate-v1",
version: "1.0.0",
name: "Baseline multistate",
description: "Canonical baseline scenario",
definitionPackId: "baseline-pack-v1",
geography: [],
transportLinks: [],
states: [],
currencies: [],
monetaryAuthorities: [],
clans: [],
cohorts: [],
productionUnits: [],
};

// @ts-expect-error a ScenarioDefinition is not assignable to RunOptions.
const mismatchedRunOptions: RunOptions = scenario;
// @ts-expect-error a RunOptions is not assignable to ScenarioDefinition.
const mismatchedScenario: ScenarioDefinition = runOptions;

expect(runOptions.scenarioId).toBe(scenario.id);
void mismatchedRunOptions;
void mismatchedScenario;
});

it("rejects assigning a SimulationConfig where a DefinitionPack is required, and vice versa", () => {
const simulationConfig: SimulationConfig = {
configVersion: "1.0.0",
numeric: {},
cadence: {},
markets: {},
trade: {},
production: {},
labor: {},
population: {},
clans: {},
fiscal: {},
monetary: {},
expansion: {},
events: {},
performance: {},
};
const definitionPack: DefinitionPack = {
id: "baseline-pack-v1",
version: "1.0.0",
goods: {},
recipes: {},
eventDefinitions: {},
metricDefinitions: {},
};

// @ts-expect-error a DefinitionPack is not assignable to SimulationConfig.
const mismatchedConfig: SimulationConfig = definitionPack;
// @ts-expect-error a SimulationConfig is not assignable to DefinitionPack.
const mismatchedPack: DefinitionPack = simulationConfig;

expect(simulationConfig.configVersion).not.toBe(definitionPack.id);
void mismatchedConfig;
void mismatchedPack;
});
});
31 changes: 31 additions & 0 deletions src/config/definitionPack.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
/**
* DefinitionPack layer (REQ-CONFIG-001).
*
* See `docs/spec/mirror/06 - Handoff/03 — CANONICAL_CONFIG_AND_WORLD_GENERATION.md`
* section 2: "DefinitionPack owns immutable type definitions and recipes."
* The definition types below (sections 15, 21) are named placeholders — their
* concrete fields land with the baseline-definition-pack requirement
* (`REQ-CONFIG-003`) that owns them, not with this scaffolding requirement.
*/
import type { GoodId } from "../domain/id";

/** Concrete fields land with the baseline definition pack requirement (section 15). */
export interface GoodDefinition {}

/** Concrete fields land with the baseline definition pack requirement (section 15). */
export interface RecipeDefinition {}

/** Concrete fields land with the events requirement that owns event definitions (section 13). */
export interface EventDefinition {}

/** Concrete fields land with whichever requirement first defines a derived diagnostic metric. */
export interface MetricDefinition {}

export interface DefinitionPack {
readonly id: string;
readonly version: string;
readonly goods: Readonly<Record<GoodId, GoodDefinition>>;
readonly recipes: Readonly<Record<string, RecipeDefinition>>;
readonly eventDefinitions: Readonly<Record<string, EventDefinition>>;
readonly metricDefinitions: Readonly<Record<string, MetricDefinition>>;
}
57 changes: 53 additions & 4 deletions src/config/index.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,57 @@
/**
* Config module area (REQ-MIGRATION-003 scaffolding).
* Config module area (REQ-MIGRATION-003 scaffolding; REQ-CONFIG-001).
*
* Owns RunOptions, SimulationConfig, ScenarioDefinition, DefinitionPack and
* their validators from Milestone 1 onward. Deliberately empty of behavior
* in M0 — see AGENTS.md and ADR 0002.
* Owns the four canonical configuration layers — `RunOptions`,
* `SimulationConfig`, `ScenarioDefinition`, `DefinitionPack` — and the
* validator that mechanically rejects scenario-specific behavioral overrides.
* See `docs/spec/mirror/06 - Handoff/03 — CANONICAL_CONFIG_AND_WORLD_GENERATION.md`
* section 2.
*/
export const CONFIG_MODULE_AREA = "config" as const;

export type { RunOptions } from "./runOptions";

export {
SIMULATION_CONFIG_BEHAVIORAL_KEYS,
type CadenceConfig,
type ClanConfig,
type EventConfig,
type ExpansionConfig,
type FiscalConfig,
type LaborConfig,
type MarketConfig,
type MonetaryConfig,
type NumericConfig,
type PerformanceConfig,
type PopulationConfig,
type ProductionConfig,
type SimulationConfig,
type TradeConfig,
} from "./simulationConfig";

export {
SCENARIO_DEFINITION_KEYS,
type BondSeed,
type ClanSeed,
type CohortSeed,
type CurrencySeed,
type InitialEventSeed,
type MarketSeed,
type MonetaryAuthoritySeed,
type ProductionUnitSeed,
type RegionSeed,
type ScenarioDefinition,
type ScenarioVariationConfig,
type StateSeed,
type TransportLinkSeed,
} from "./scenarioDefinition";

export {
type DefinitionPack,
type EventDefinition,
type GoodDefinition,
type MetricDefinition,
type RecipeDefinition,
} from "./definitionPack";

export { assertNoBehavioralOverrides } from "./validation";
14 changes: 14 additions & 0 deletions src/config/runOptions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
/**
* RunOptions layer (REQ-CONFIG-001).
*
* See `docs/spec/mirror/06 - Handoff/03 — CANONICAL_CONFIG_AND_WORLD_GENERATION.md`
* section 2: "RunOptions chooses a run." It never carries behavioral tuning or
* starting-world data — those belong to `SimulationConfig` and
* `ScenarioDefinition` respectively.
*/
export interface RunOptions {
readonly scenarioId: string;
readonly seed: number;
readonly maxTicks?: number;
readonly diagnosticsLevel: "OFF" | "SUMMARY" | "DEBUG";
}
94 changes: 94 additions & 0 deletions src/config/scenarioDefinition.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
/**
* ScenarioDefinition layer (REQ-CONFIG-001).
*
* See `docs/spec/mirror/06 - Handoff/03 — CANONICAL_CONFIG_AND_WORLD_GENERATION.md`
* section 2: "ScenarioDefinition owns starting world stocks/topology/institutions."
* The seed types below (section 16) are named placeholders — their concrete
* fields land with the world-genesis requirement that consumes them
* (`REQ-CONFIG-003`/`REQ-CORE-003`), not with this scaffolding requirement.
*/

/** Concrete fields land with world genesis (section 16). */
export interface RegionSeed {}

/** Concrete fields land with world genesis (section 16). */
export interface TransportLinkSeed {}

/** Concrete fields land with world genesis (section 16). */
export interface StateSeed {}

/** Concrete fields land with world genesis (section 16). */
export interface CurrencySeed {}

/** Concrete fields land with world genesis (section 16). */
export interface MonetaryAuthoritySeed {}

/** Concrete fields land with world genesis (section 16). */
export interface ClanSeed {}

/** Concrete fields land with world genesis (section 16). */
export interface CohortSeed {}

/** Concrete fields land with world genesis (section 16). */
export interface ProductionUnitSeed {}

/** Concrete fields land with world genesis (section 16). */
export interface MarketSeed {}

/** Concrete fields land with world genesis (section 16, bond opening positions). */
export interface BondSeed {}

/** Concrete fields land with world genesis (section 16, initial scheduled events). */
export interface InitialEventSeed {}

/** Concrete fields land with deterministic bounded scenario variation (section 18). */
export interface ScenarioVariationConfig {}

export interface ScenarioDefinition {
readonly id: string;
readonly version: string;
readonly name: string;
readonly description: string;
readonly definitionPackId: string;
readonly geography: readonly RegionSeed[];
readonly transportLinks: readonly TransportLinkSeed[];
readonly states: readonly StateSeed[];
readonly currencies: readonly CurrencySeed[];
readonly monetaryAuthorities: readonly MonetaryAuthoritySeed[];
readonly clans: readonly ClanSeed[];
readonly cohorts: readonly CohortSeed[];
readonly productionUnits: readonly ProductionUnitSeed[];
readonly markets?: readonly MarketSeed[];
readonly bonds?: readonly BondSeed[];
readonly initialEvents?: readonly InitialEventSeed[];
readonly variation?: ScenarioVariationConfig;
}

/**
* Every key section 2's `ScenarioDefinition` interface declares. `markets` and
* `clans` also happen to be `SimulationConfig` behavioral-key names (section 2's
* two hierarchies were not designed to avoid key-name collisions) — an array
* value there is the legitimate seed-list field; a plain-object value is a
* smuggled `MarketConfig`/`ClanConfig` behavioral patch. See
* `./validation.ts`'s `assertNoBehavioralOverrides`, which is the actual
* mechanical proof that scenario-specific behavioral overrides are rejected.
*/
export const SCENARIO_DEFINITION_KEYS = [
"id",
"version",
"name",
"description",
"definitionPackId",
"geography",
"transportLinks",
"states",
"currencies",
"monetaryAuthorities",
"clans",
"cohorts",
"productionUnits",
"markets",
"bonds",
"initialEvents",
"variation",
] as const satisfies readonly (keyof ScenarioDefinition)[];
89 changes: 89 additions & 0 deletions src/config/simulationConfig.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
/**
* SimulationConfig layer (REQ-CONFIG-001).
*
* See `docs/spec/mirror/06 - Handoff/03 — CANONICAL_CONFIG_AND_WORLD_GENERATION.md`
* section 2: "SimulationConfig owns reusable behavioral tuning" shared across
* scenarios. Each of the thirteen sub-config types below is a named placeholder —
* its concrete field-level defaults (sections 3-14 of that document) land with the
* requirement that owns the corresponding subsystem (M3-M10), not with this
* scaffolding requirement. Declaring them now, rather than inlining `unknown`,
* is what makes `SimulationConfig` a real distinct type today and lets later
* requirements add fields without touching this file's shape.
*/

/** Concrete fields land with the numeric-tolerance requirement that owns them (section 3). */
export interface NumericConfig {}

/** Concrete fields land with the cadence-scheduling requirement that owns them (section 3). */
export interface CadenceConfig {}

/** Concrete fields land with the markets requirement that owns them (section 4). */
export interface MarketConfig {}

/** Concrete fields land with the trade/FX requirement that owns them (section 5). */
export interface TradeConfig {}

/** Concrete fields land with the production requirement that owns them (section 6). */
export interface ProductionConfig {}

/** Concrete fields land with the labor requirement that owns them (section 7). */
export interface LaborConfig {}

/** Concrete fields land with the population requirement that owns them (section 8). */
export interface PopulationConfig {}

/** Concrete fields land with the clans requirement that owns them (section 9). */
export interface ClanConfig {}

/** Concrete fields land with the fiscal requirement that owns them (section 10). */
export interface FiscalConfig {}

/** Concrete fields land with the monetary requirement that owns them (section 11). */
export interface MonetaryConfig {}

/** Concrete fields land with the expansion requirement that owns them (section 12). */
export interface ExpansionConfig {}

/** Concrete fields land with the events requirement that owns them (section 13). */
export interface EventConfig {}

/** Concrete fields land with the performance requirement that owns them (section 14). */
export interface PerformanceConfig {}

/**
* The thirteen behavioral-tuning keys `SimulationConfig` owns, exactly as named
* in section 2. Scenario data may never carry one of these keys directly — see
* `./validation.ts`'s `assertNoBehavioralOverrides`.
*/
export const SIMULATION_CONFIG_BEHAVIORAL_KEYS = [
"numeric",
"cadence",
"markets",
"trade",
"production",
"labor",
"population",
"clans",
"fiscal",
"monetary",
"expansion",
"events",
"performance",
] as const satisfies readonly (keyof Omit<SimulationConfig, "configVersion">)[];

export interface SimulationConfig {
readonly configVersion: string;
readonly numeric: NumericConfig;
readonly cadence: CadenceConfig;
readonly markets: MarketConfig;
readonly trade: TradeConfig;
readonly production: ProductionConfig;
readonly labor: LaborConfig;
readonly population: PopulationConfig;
readonly clans: ClanConfig;
readonly fiscal: FiscalConfig;
readonly monetary: MonetaryConfig;
readonly expansion: ExpansionConfig;
readonly events: EventConfig;
readonly performance: PerformanceConfig;
}
Loading
Loading