Skip to content

refactor: domain-neutral core with pluggable domain modules - #2

Merged
MikkoNumminen merged 7 commits into
masterfrom
refactor/domain-neutral-core
Jul 4, 2026
Merged

refactor: domain-neutral core with pluggable domain modules#2
MikkoNumminen merged 7 commits into
masterfrom
refactor/domain-neutral-core

Conversation

@MikkoNumminen

Copy link
Copy Markdown
Owner

Turns the retail-specific tool into a reusable engine: the core references no domain values, and retail becomes one data-only domain module. A second domains/game/ module is included purely to prove the contract — a new domain is a new folder, zero core edits.

What moved where

  • Core owns mechanism, not values. StructuringSchema is now just the five universal field names. The category/severity/type values live in a per-domain DomainDescriptor, loaded from domains/<name>/domain.json and selected by config Domain:Active — switchable from the CLI (--Domain:Active=game), validated at startup (unknown domain fails the boot).
  • Domain data: alert lexicon, planted stories, and the domain-voiced synthesis/alert-nomination prompts moved into domains/<name>/. The neutral structuring prompt stays in core and is templated with the active domain's taxonomy.
  • UI/report neutral: /schema serves the active domain's taxonomy + display labels + category-field label; the desk renders labels from it (no hardcoded map); the report uses the domain's field label (osasto for retail, area for game).

Proof

  • Behavior preserved: retail seed-42 corpus byte-identical — SHA 732BE558… reproduced from the pre-refactor commit df704be and this branch.
  • All 81 tests green (15 Llm + 23 Generator + 43 Api).
  • CLI switch demonstrated: booting with --Domain:Active=game flips /schema from retail (osasto, grocery categories) to game (area, Matchmaking & netcode, …) with no code change.

Docs

ADR-0012 (the mechanism; closes the gap ADR-0007 flagged), docs/domains.md (the authoring contract), rewritten docs/domain/retail.md, new docs/domain/game.md. GitHub repo description updated from the old "Filters information and creates a report".


⚠️ Do not merge without Mikko's explicit approval — queued for review per the standing rule.

Turn the retail-specific tool into a reusable engine. The core (schema shape,
LLM abstraction, salvage layer, deterministic-alert mechanism, ingest, analysis,
generator) now references no domain values. Retail is one data-only module under
domains/retail/; a second domains/game/ module proves a new domain is a new
folder with zero core edits.

- StructuringSchema reduced to the universal field-name set; the category /
  severity / type VALUES move to a per-domain DomainDescriptor loaded from
  domains/<name>/domain.json and selected by config (Domain:Active) — switchable
  from the CLI, e.g. --Domain:Active=game.
- Alert lexicon, planted stories, and the domain-voiced prompts (synthesis,
  alert-nomination) become domain data. The neutral structuring prompt is
  templated with the active domain's taxonomy at load time.
- /schema serves the active domain's taxonomy plus display labels; the desk UI
  renders labels from it (no hardcoded map). The report uses the domain's
  category-field label (osasto for retail, area for game).
- IActiveDomain resolves domain data files and prompt paths; AddActiveDomain
  wires it, validated at startup (an unknown domain fails the boot).

Behavior preserved: seed-42 retail corpus byte-identical; all 81 tests green.
- ADR-0012: domain-neutral core with pluggable domain modules (the realized
  mechanism; closes the boundary gap ADR-0007 flagged but did not fix).
- ADR-0007: marked realized-by-0012; the department->category rename and the
  closed hardcoding gap noted inline.
- docs/domains.md: the authoring contract — module anatomy, domain.json schema,
  the Domain:Active switch, and a checklist for adding a domain.
- docs/domain/retail.md: rewritten for the data-only module (was describing the
  old hardcoded-in-engine state).
- docs/domain/game.md: the second module as a switchability proof.
- README + ADR index: point at domains.md and ADR-0012.
…for game

Add a hand-written placeholder variants pool for the game module
(data/corpus/game/dev-placeholder-variants.jsonl, 12 story + 18 noise items,
NON-EVIDENTIAL). generate --Domain:Active=game composes a 69-item game corpus
with machine-checkable ground truth: the planted progression-loss story grounds
to 9 in-window ids, trend worsening, category bugs_crashes, zero story-tag
leakage. Proves the seeded generator — not just /schema — is domain-neutral.

- .gitignore: make the generated/ground-truth placeholder patterns recursive so
  per-domain subfolders (data/corpus/game/) are covered.
- mock-data register: game pool + generated artifacts recorded per the hard rule.
- docs/domain/game.md: the generate command, plus two honestly-flagged source
  edges (Ingest:AllowedSources and the composer's noise-source fallback are still
  retail-flavored) as the next neutrality steps.
Robustness:
- ActiveDomain: an empty "severities":{}/"types":{} object now falls back to
  CoreDefaults instead of silently yielding an empty enum set (which would reject
  every item at structuring time).
- Api startup: fail fast if the active domain omits/misspells a required prompt
  role or its file is missing — a misconfigured domain now fails the boot instead
  of 500ing mid-report (the "report always renders" guarantee only covers an
  unreachable LLM, not a bad domain).
- Generator: catch InvalidDataException (bad stories.json, story with no pool
  items) → clean exit 2 with the message, not a stack trace.
- ActiveDomain: ResolveDir returns an absolute path so AlertKeywordsPath/
  StoriesPath/PromptPaths honor the documented absolute contract; parse
  domain.json once (was twice); warn when the structuring prompt lacks the
  {{categories}} placeholder.

Docs/comments (CLAUDE.md keep-AI-docs-current rule):
- schema.md rewritten for the domain module (category, not department; values
  from IActiveDomain, not StructuringSchema.cs).
- data/corpus/README.md, README.md, architecture.md, TODO.md, and the
  AlertMatcher/CorpusItem code comments: config/alert-keywords.json ->
  domains/<active>/alert-keywords.json; Generator:Stories -> stories.json.
- ADR-0012: byte-identity claim made precise (placeholder pool, SHA reproduced
  from df704be and the refactored tree).
- game.md: disclose the pool-path design gap alongside the source couplings.

Behavior preserved: retail seed-42 byte-identical (732BE558), 81 tests green,
retail boot + /schema verified.
@MikkoNumminen
MikkoNumminen merged commit de0169b into master Jul 4, 2026
@MikkoNumminen
MikkoNumminen deleted the refactor/domain-neutral-core branch July 4, 2026 15:37
MikkoNumminen added a commit that referenced this pull request Jul 6, 2026
…allback)

A judgment pass over the first real variants.jsonl found the LLM story
rephrasings drift: core-004's both variants went English, core-003-v1 claims
'first time' at step 3 of a worsening arc, plus tone-softening and one
cross-story topic drift. generate --seed 42 had selected two into the live
corpus (gen-42-0034 English, gen-42-0063 count-inverted), both slipping past
verify (which checks grounding/trend, not language/ordinals).

Take ADR-0011's pre-registered fallback: StoryVariantsPerItem=0. Drop the 21
story-variant lines from the pool, set the generator config, recompose seed 42.
Story steps now compose from Mikko's originals only (100% authored escalation);
noise still x6 so corpus variety is unaffected. Same 3 stories, windows, trends.

Resolves task #2. Validated (with the ADR-0018 reliability fixes): acceptance
PASS -- dairy 5/4, availability 5/3, no-keyword safety alert fires.

ADR-0019; decisions index updated.
MikkoNumminen added a commit that referenced this pull request Jul 6, 2026
…allback)

A judgment pass over the first real variants.jsonl found the LLM story
rephrasings drift: core-004's both variants went English, core-003-v1 claims
'first time' at step 3 of a worsening arc, plus tone-softening and one
cross-story topic drift. generate --seed 42 had selected two into the live
corpus (gen-42-0034 English, gen-42-0063 count-inverted), both slipping past
verify (which checks grounding/trend, not language/ordinals).

Take ADR-0011's pre-registered fallback: StoryVariantsPerItem=0. Drop the 21
story-variant lines from the pool, set the generator config, recompose seed 42.
Story steps now compose from Mikko's originals only (100% authored escalation);
noise still x6 so corpus variety is unaffected. Same 3 stories, windows, trends.

Resolves task #2. Validated (with the ADR-0018 reliability fixes): acceptance
PASS -- dairy 5/4, availability 5/3, no-keyword safety alert fires.

ADR-0019; decisions index updated.
MikkoNumminen added a commit that referenced this pull request Jul 6, 2026
…allback) (#19)

A judgment pass over the first real variants.jsonl found the LLM story
rephrasings drift: core-004's both variants went English, core-003-v1 claims
'first time' at step 3 of a worsening arc, plus tone-softening and one
cross-story topic drift. generate --seed 42 had selected two into the live
corpus (gen-42-0034 English, gen-42-0063 count-inverted), both slipping past
verify (which checks grounding/trend, not language/ordinals).

Take ADR-0011's pre-registered fallback: StoryVariantsPerItem=0. Drop the 21
story-variant lines from the pool, set the generator config, recompose seed 42.
Story steps now compose from Mikko's originals only (100% authored escalation);
noise still x6 so corpus variety is unaffected. Same 3 stories, windows, trends.

Resolves task #2. Validated (with the ADR-0018 reliability fixes): acceptance
PASS -- dairy 5/4, availability 5/3, no-keyword safety alert fires.

ADR-0019; decisions index updated.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant