refactor: domain-neutral core with pluggable domain modules - #2
Merged
Conversation
…m, Api, Generator, Ctl, Eval + tests, sln)
…ON, prompt, UI, config, tests)
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
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.
This was referenced Jul 12, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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
StructuringSchemais now just the five universal field names. The category/severity/type values live in a per-domainDomainDescriptor, loaded fromdomains/<name>/domain.jsonand selected by configDomain:Active— switchable from the CLI (--Domain:Active=game), validated at startup (unknown domain fails the boot).domains/<name>/. The neutral structuring prompt stays in core and is templated with the active domain's taxonomy./schemaserves 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 (osastofor retail,areafor game).Proof
732BE558…reproduced from the pre-refactor commitdf704beand this branch.--Domain:Active=gameflips/schemafrom 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), rewrittendocs/domain/retail.md, newdocs/domain/game.md. GitHub repo description updated from the old "Filters information and creates a report".