Skip to content

feat(settings): define canonical typed schema - #114

Merged
Teakowa merged 7 commits into
mainfrom
codex/issue-109-settings-semantic
Aug 28, 2026
Merged

feat(settings): define canonical typed schema#114
Teakowa merged 7 commits into
mainfrom
codex/issue-109-settings-semantic

Conversation

@Teakowa

@Teakowa Teakowa commented Aug 27, 2026

Copy link
Copy Markdown
Contributor

Summary

Implements the typed Workshop settings semantic foundation for #109 while keeping incomplete semantic evidence explicit until #110.

  • Projects reviewed settings into typed scope, target, value-domain, applicability, provenance, and setting identity facts.
  • Preserves common/team slot-bearing settings as TeamAbility; effective applicability narrows Team definitions to concrete heroes and TeamAbility definitions to concrete hero abilities.
  • Defines None ability variants as an unconstrained variant; canonical gameplay topology still rejects missing slots or explicit missing variants.
  • Exposes unresolved semantic identities as SettingIdentity::Unknown (id() -> None) without path-derived pseudo-canonical IDs.
  • Keeps Settings / SettingsNode source-preserving and retains authored values when effective values are clamped.
  • Propagates canonical gameplay-data failures through target-aware presentation lookup.
  • Keeps the borrowed gameplay catalog lookup internal (pub(crate)) because it supports settings queries without adding a public API contract.

Verification

Branch codex/issue-109-settings-semantic is rebased onto current main at 4b6d1d25e2dbd73d7b6ad03596865166a74fb4ec.

  • cargo fmt --all --check — passed
  • cargo clippy --workspace --all-targets -- -D warnings — passed
  • cargo test --workspace --all-targets — passed
  • cargo run -p workshop-rs --bin workshop-catalog-gen -- check — passed (532 entries, 52 enum domains, 2 locales)
  • git diff --check — passed

ADR-0006 now documents the independent axes of reviewed source evidence and resolved canonical identity.

@Teakowa Teakowa left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changes required. Full one-pass audit against #109. The direction and ownership boundaries are good, but the current foundation still freezes several incorrect/overstated semantic contracts.

Blocker

  1. Canonical SettingId normalization is incomplete, so the new public IDs still encode target-specific path structure. canonical_id() normalizes only a narrow subset (enablePrimaryFire/enableSecondaryFire/enableAbility1/enableAbility2, and keys containing Cooldown). Existing projected definitions such as enableAbility3, enableUlt, enablePassive, secondaryFireRechargeRate%, ability1EnemyKb%, ability1SelfKb%, and ability2FuseTime% therefore keep slot/hero-path details in the supposedly canonical concept ID even though SettingTarget already carries the logical slot. This conflicts with #109's identity rule and would force #110 to break IDs that #109 is intended to establish. Define and exercise the concept-identity mapping for the currently projected semantic surface (without concrete ability display names and without duplicating target structure), or do not expose unresolved path-derived IDs as canonical yet.

  2. Effective applicability does not implement the documented known-hero vs unknown distinction. hero_setting_is_evidenced() returns None when a known hero has no entry for a key, and SettingDefinition::applicability() maps that to Unknown. For a wildcard hero definition such as ability1EnemyKb%, the reviewed generated data contains the setting for Ashe but not Ana; querying that definition for Ana therefore becomes Unknown, while ADR-0006 explicitly says a known hero without a reviewed key is NotApplicable. The current test only covers placeholder entries (Some(false)), not an actually absent hero/key. Resolve the completeness/evidence contract and add a representative hero-specific exclusion case; do not infer applicability from whether a locale label happens to be usable.

  3. Unknown numeric bounds are silently promoted to exact effective semantics. NumericBounds documents None as “current reviewed evidence does not establish that bound”, but NumericBounds::effective() returns Some(EffectiveNumber { authored, effective: authored }) when both bounds are None. All current projected Number/Percent definitions use (None, None), so consumers can currently ask for an “effective” value and receive a false claim that no clamping occurs. Preserve unknownness until bounds/clamp behavior are evidenced. Also enforce or otherwise make unrepresentable invalid bound states (non-finite bounds / min > max) before this becomes a public semantic contract.

Major

  1. gamemodes.general is incorrectly modeled as a game-mode target. target_for() turns every gamemodes.<part> path into Mode(part), but the existing settings table explicitly documents general as a literal settings group, not a mode slot. SettingScope::GameModes is correct; the semantic target for gamemodes.general.* must not be Mode("general").

  2. Non-hero en-US presentation lookup is broken. The existing locale table intentionally does not duplicate English names, and the emitter has an explicit en-US => english path. SettingPresentation::localized_name() calls only table::localized_name(), so a normal definition such as main.description returns no en-US spelling despite carrying english_name. Make the schema follow the existing locale contract and cover a normal non-hero en-US lookup.

  3. SettingProvenance flattens distinct evidence sources into one generic claim. Hand-maintained table entries are fixture/oracle-evidenced, while generated entries/hero settings come from the reviewed workshop-data export, yet every projected definition reports the same source = "reviewed workshop-rs settings table and generated settings data" and reviewed = true. #109 requires provenance as a semantic fact; it must identify the actual evidence class/source for the definition rather than implying every entry came from both. Keep this minimal, but machine-meaningful and accurate.

Minor

  1. Definition-side ability target typing regresses to strings and drops variants. Public SettingTarget correctly uses LogicalSlot + optional AbilityVariant, but SettingTargetKind::HeroAbility { slot: String } and private TargetPattern::HeroAbility { slot: String } introduce a second slot identity and cannot express a variant-specific definition. Use the existing canonical slot identity throughout; if current evidence has no variant-specific settings, None is fine, but the #109 foundation should not make the definition side structurally unable to represent the target contract it declares.

The added tests are useful but currently miss the failure modes above: known hero + absent hero-specific key, unknown bounds, gamemodes.general, normal en-US presentation, and broader concept-ID normalization.

CI / scope

The PR's CI is currently red in both stable and MSRV jobs at the pre-existing language_conformance::aggregate_values_count_matches_supported_inventory aggregate-count assertion; fmt/clippy and the real-project scenarios pass before that failure. This PR does not touch that inventory/test, so do not fold an unrelated cleanup into #109. Repair/rebaseline that repository gate separately, then rerun CI before merge.

No additional ownership, source-language dependency, parser/emitter rewrite, source-preservation, or repo-hygiene blockers found in the audited diff.

@Teakowa

Teakowa commented Aug 27, 2026

Copy link
Copy Markdown
Contributor Author

Addressed all review findings in follow-up commit 915bbef:

  • normalized the currently projected ability concept IDs so target slot/hero structure is not encoded in SettingId;
  • known heroes with absent hero/key evidence now resolve to NotApplicable, while unknown heroes remain Unknown;
  • unknown numeric bounds no longer produce an asserted effective value; invalid bounds are rejected;
  • modeled gamemodes.general as a literal group with global/no semantic target;
  • restored the existing en-US presentation contract;
  • separated raw Workshop fixture provenance from workshop-data export provenance;
  • kept definition-side ability targets on LogicalSlot with optional variant;
  • added regression coverage for every review failure mode.

Local verification after the fixes: 23 settings tests passed, fmt, Clippy, catalog check, and diff check passed. The unrelated aggregate-count workspace gate remains unchanged and is tracked separately.

@Teakowa
Teakowa marked this pull request as ready for review August 27, 2026 10:17

@Teakowa Teakowa left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changes still required. Second full audit of the updated head (915bbef) against #109.

The update correctly fixes several previous findings: gamemodes.general is no longer a mode target; non-hero en-US presentation now follows the existing primary-locale behavior; provenance distinguishes fixture-backed vs workshop-data-generated definitions; and definition-side ability targets now reuse LogicalSlot / AbilityVariant. Unknown-all numeric bounds also no longer manufacture an effective value, and CI is green on the current merge context.

Blocker

  1. Known-hero vs unknown applicability is still inconsistent, and the previous hero-specific exclusion case remains wrong for ability settings. The HeroAbility branch first recognizes an unknown hero, but for a known hero it still maps hero_setting_is_evidenced(...) == None to Unknown. A projected hero-specific key such as ability1EnemyKb% is evidenced for Ashe but absent for Ana; querying it for Ana + Ability1 therefore remains Unknown, while ADR-0006 explicitly says a known hero without a reviewed key is NotApplicable. Conversely, the plain Hero branch now maps None directly to NotApplicable without first checking whether the hero itself is known, so an unknown/future hero becomes NotApplicable instead of Unknown. Apply one consistent rule to both branches: unknown hero => Unknown; known hero + evidenced key => applicable/not-applicable according to the reviewed applicability evidence; known hero + absent key in a complete reviewed hero surface => NotApplicable. Add tests for both a real known-hero/absent hero-specific key (e.g. Ana vs an Ashe-only setting) and an unknown hero on a non-ability hero setting.

  2. Canonical SettingId normalization is still incomplete for the currently projected surface. The new ability_concept() removes ability1/2/3 and secondaryFire prefixes, but canonical_id() still special-cases only enableSecondaryFire, not the existing enableGenericSecondaryFire entry. Both are mapped to the secondary-fire logical slot by the existing table, yet the latter is exposed as setting.hero.ability.enableGenericSecondaryFire rather than the same ability.enabled concept. More generally, mechanically stripped suffixes such as EnemyKb / FuseTime are being frozen as public canonical IDs without an asserted canonical vocabulary/casing contract. #109 is the stage that is supposed to establish these identities so #110 does not have to break them. Either finish the concept mapping for every currently exposed projected ability setting (with exact identity tests), or keep unresolved path-derived identities non-canonical/private until #110 supplies reviewed concepts.

  3. Partially-known numeric bounds still overstate the effective value. None is documented as “evidence does not establish that bound”. effective() now correctly returns None when both bounds are unknown, but if only one side is known it always returns an EffectiveNumber. Example: with min = Some(0), max = None and authored 1000, the implementation reports effective 1000 even though the unknown maximum may clamp it. The mirror case exists for known max / unknown min. Preserve unknownness whenever the missing side could affect the authored value; alternatively model unknown vs evidenced-unbounded as distinct states. Add partial-bound tests, not only all-unknown and fully-bounded cases.

Major

  1. Applicability is still derived from locale-label usability rather than an explicit applicability fact. hero_setting_is_evidenced() decides support by checking whether any generated localized label is nonblank/non-placeholder. That happened to encode several current exclusions, but it conflates presentation completeness with semantic applicability and makes a missing/blank locale artifact silently become NotApplicable. #109 explicitly separates applicability from locale presentation, and the previous review asked to resolve this evidence contract rather than infer capability from label usability. If the workshop-data producer's blank/missing entries are an intentionally reviewed applicability oracle, represent/document that as applicability evidence; otherwise keep it Unknown until #110 has explicit applicability data. Do not make locale quality the durable semantic API.

Minor

  1. The canonical documentation index does not link ADR-0006. docs/README.md is explicitly the canonical docs index and currently lists the accepted ADRs through ADR-0005. Since #109 requires this durable settings contract to be recorded in owner docs, add ADR-0006 to the architecture/authority index rather than leaving the accepted contract undiscoverable.

  2. The PR body is stale after the update/main fix. It still reports the old workspace-test failure and an independent QA result of “Blockers: none / Major: none”, while the current CI run is green and the PR has review findings. Refresh the verification/QA summary before merge so the PR does not preserve obsolete status claims.

Re-audited areas with no additional findings

  • ownership/dependency direction remains clean; no OPY/DEL carrier semantics moved into workshop-rs;
  • Settings / SettingsNode remain the source-preserving carrier;
  • no parser/emitter rewrite or unrelated architecture cleanup;
  • value-domain kinds remain aligned with existing KeyKind semantics;
  • NumericBounds::new now rejects non-finite/reversed bound states;
  • SettingTargetKind no longer creates a second stringly-typed slot identity;
  • current CI passes stable, MSRV, real-project scenarios, and catalog check.

Do not proceed to #110 until the remaining identity/applicability/unknown-value contracts above are closed; those are exactly the public semantic decisions #109 exists to settle.

@Teakowa

Teakowa commented Aug 27, 2026

Copy link
Copy Markdown
Contributor Author

Second review follow-up is complete in 11c4d3e8606bc1f3b6a0aa807825b5e3cdcd8068.

Addressed all six findings:

  1. Known heroes with absent hero-specific keys now report NotApplicable; unknown/future heroes report Unknown for both plain hero and hero-ability settings. Added concrete tests for Ana vs. Ashe-only ability1EnemyKb% and a future hero.
  2. Canonical ability IDs now cover every currently projected ability key, including all secondaryFire*, enable variants, and ultimate-generation keys. Future unmapped suffixes are isolated under ability.custom.* rather than exposed as canonical path-derived IDs.
  3. Partial numeric bounds now return an effective value only when the known bound necessarily clamps the authored value; otherwise the result is None. Added lower-only and upper-only tests.
  4. Applicability is documented and named as reviewed producer-export applicability evidence, separate from locale label resolution.
  5. Added ADR-0006 to docs/README.md architecture/authority indexes.
  6. Refreshed the PR body with current verification and QA status.

Local gates are green: settings pipeline 23/23, workspace clippy with -D warnings, catalog check, formatting, and diff hygiene. Please re-run the review on the pushed head.

@Teakowa Teakowa left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changes required. Third full audit against #109 at 11c4d3e. The previous review's partial-bound handling, known/future-hero branches, target typing, locale fallback, provenance split, ADR index, PR summary, and CI issues are materially improved. The remaining problems are now concentrated in the core semantic contract rather than repo hygiene.

Blocker

  1. The hero-name projection is still being used as a complete applicability matrix, which makes ordinary hero settings NotApplicable for known heroes. hero_setting_applicability() searches only GENERATED_HERO_SETTING_NAMES; an absent row becomes None, and both Hero/HeroAbility applicability branches map that to NotApplicable for a known hero. But that generated file is not a complete per-hero settings inventory: the reviewed settings table contains ordinary hero keys such as heroes.<team>.<hero>.health%, enableAutomaticFire, enableScoping, and generic enablePrimaryFire, while the hero-name projection has no health%, enableAutomaticFire, enableScoping, or generic rows for most heroes. For example, the current test can resolve Ana's generic Primary Fire presentation, but the same definition's applicability for Ana is NotApplicable because there is no Ana enablePrimaryFire name row. Absence from a hero-specific name table cannot mean semantic exclusion. The #109 contract needs a real applicability evidence boundary: kit topology + explicit applicability/common/exclusion facts where reviewed; otherwise return Unknown until #110 supplies the catalog data. Renaming is_applicable() and documenting blank labels as producer markers does not make the name dataset complete for keys that never appear in it.

  2. Canonical IDs are now over-normalized and conflate distinct Workshop settings. enableAutomaticFire (No Automatic Fire) and enableScoping (No Scope) are both mapped to setting.hero.ability.enabled, the same concept as the actual Primary/Secondary/Ability enable controls. These are distinct controls over automatic-fire/scoping behavior, not aliases for whether the ability itself is enabled. This loses semantic identity before #110 can populate the catalog. Likewise ability.primaryFireFreezeStack and ability.passiveUnlimitedFuel still duplicate logical-slot specificity that SettingTarget already carries. Canonicalization should remove target/display specificity without collapsing different setting concepts. Use reviewed concept identities (for example an automatic-fire/scoping/freeze-stacking/unlimited-fuel concept where evidence supports it), not an enable* => ability.enabled heuristic.

  3. SettingDefinition::applicability() still does not enforce the #109 kit-topology-first rule, and it ignores ability variants entirely. The method checks hero-name applicability evidence after matching the slot string, but never verifies that the hero actually has that logical slot/variant in the canonical gameplay topology. A target with an otherwise applicable hero/key/slot and variant: Some("missing") can still return Applicable. #109 explicitly says kit topology determines which logical slots exist before setting applicability is evaluated. Either integrate/accept a topology context and reject/mark unknown impossible targets, or change the public contract through an explicit design decision; silently assuming every supplied target is topology-valid does not satisfy the current issue.

Major

  1. Target-aware presentation can return a concrete name for a target that the same definition says is not applicable. localized_name() first tries the hero-specific name and then unconditionally falls back to the generic SettingPresentation. For the Ashe-only ability1EnemyKb% definition queried against Ana, applicability is NotApplicable, but localized_name("en-US", Ana/Ability1) can fall back to Coach Gun Knockback Scalar Enemy. A target-aware resolver should not manufacture a misleading concrete presentation for a non-applicable target. Either gate target resolution by applicability or separate generic definition presentation from target-resolved presentation so the contract is unambiguous.

  2. ability.custom.* plus default scope/target fallbacks silently turn unreviewed future shapes into reviewed canonical facts. ADR-0006 says an unreviewed future ability suffix is emitted as ability.custom.*, but from_entry() still wraps it in a normal SettingDefinition with reviewed: true. Similarly, unknown root/path shapes fall through to SettingScope::Workshop / TargetPattern::Global. Those are real semantic categories, not an Unknown bucket. This contradicts #109's requirement to preserve unknown/evidence-insufficient states and risks freezing placeholder identities that #110 later has to rename. Unknown semantic mappings should remain explicit/unsupported until reviewed rather than receive guessed canonical identities/targets.

  3. The canonical concept mapping has become a second hand-maintained catalog inside schema.rs, crossing into #110's ownership. canonical_id() / ability_concept() now encode a long concrete list of current setting keys and exceptional mappings, while #110 explicitly owns assigning stable concepts and making one canonical typed dataset/generation path the source of truth. The long mirrored test list then duplicates the mapping again. #109 should establish the SettingDefinition/identity model and the mechanism by which reviewed catalog data supplies canonical concepts; it should not require a second code-level catalog that #110 must later migrate or reconcile.

Minor

  1. The doc comment above hero_setting_applicability() still begins by describing “whether ... evidence contains a usable localized label” and immediately follows with a claim that this is semantic applicability rather than locale completeness. That contradiction reflects the underlying data-boundary problem above; once applicability comes from the correct evidence source, keep the helper documentation single-purpose.

CI / merge state

  • Latest PR CI for 11c4d3e is green: stable, MSRV 1.85, real-project scenarios, and Catalog check all passed.
  • The branch is currently 3 commits ahead / 2 behind main; main has since taken #113 and #116. Current PR metadata reports it non-mergeable, so refresh/rebase after the semantic fixes and rerun the merge-result CI.

No additional parser/emitter regression, source-preservation regression, source-language ownership violation, unrelated refactor, or test-count/snapshot-padding issue found in this pass. Do not start #110 until the applicability/identity/topology contract above is closed.

@Teakowa

Teakowa commented Aug 27, 2026

Copy link
Copy Markdown
Contributor Author

Third review follow-up is complete in 60e6845b555dc492fc8fac49a29f98574aa14c6f.

Addressed all seven findings:

  1. Hero-name projection is now presentation-only. Without independent applicability evidence, ordinary hero and topology-valid hero-ability targets return Unknown; missing slots/variants are handled from the gameplay topology first.
  2. Distinct controls are no longer collapsed into ability.enabled; unresolved hero-ability identities use distinct setting.hero.ability.custom.<key> IDs and are marked reviewed: false until Build the canonical typed Workshop settings catalog #110 supplies reviewed concepts.
  3. Applicability now checks canonical gameplay slot/variant topology, including missing variant cases.
  4. Target-aware localized_name returns no concrete name for NotApplicable targets instead of falling back to a misleading target presentation.
  5. Unknown scope/path mappings now use explicit SettingScope::Unknown / SettingTargetKind::Unknown, with unresolved provenance rather than guessed reviewed facts.
  6. Removed the long hand-maintained concept mapping and duplicated identity test catalog from schema.rs; Define the canonical typed settings schema and hero applicability model #109 now exposes the mechanism and explicit unresolved state for Build the canonical typed Workshop settings catalog #110's reviewed catalog.
  7. Simplified the generated hero-settings comment and removed the locale-derived applicability helper; it is now single-purpose presentation data.

Local gates are green: settings pipeline 23/23, workspace clippy with -D warnings, catalog check, formatting, and diff hygiene. Please re-run the review on the pushed head.

@Teakowa Teakowa left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changes required. Full one-pass re-audit of 60e6845 against #109. The third-round fixes materially improve evidence handling: locale data is no longer treated as applicability evidence, topology now rejects missing slots/variants, unresolved applicability stays Unknown, target-aware presentation is gated for NotApplicable, and CI is green. However, the #109 public semantic model still has unresolved architecture/contract issues that would force #110 to redesign it.

Blocker

  1. Team/common hero slot-specific settings cannot be represented without losing the logical slot. The reviewed generated inventory contains entries such as heroes.<team>.enablePrimaryFire and heroes.<team>.enableUlt, in addition to heroes.<team>.<hero>.*. target_for() maps any heroes, Team, ... path without a Hero component to TargetPattern::Team, while semantic_ability_slot_for_path() recognizes keys such as enablePrimaryFire as primaryFire. That leaves a team-level ability definition with target Team and no slot. Today the raw key survives only because its provisional ability.custom.enablePrimaryFire ID still contains the slot-specific path name. Once #110 does what it owns—normalizing this to a reusable concept such as ability.enabled—the slot is lost. #110 would then have to add/change the public target model or put slot specificity back into SettingId, both contrary to #109's boundary. Resolve the target model for Workshop common/team hero settings so slot-specific common settings can carry logical-slot semantics independently of SettingId, and add representative coverage for both heroes.<team>.enablePrimaryFire and a concrete hero-ability path.

  2. Unresolved identity is still encoded as a pseudo-SettingId plus an overloaded provenance flag instead of an explicit unknown identity state. SettingId is documented as a locale-independent Workshop setting concept identity and SettingDefinition as canonical, but every unresolved hero-ability entry still returns a path-derived SettingId under setting.hero.ability.custom.*. The only signal that this is not canonical is string inspection plus SettingProvenance.reviewed = false. That also makes provenance carry two meanings: whether the evidence source was reviewed and whether semantic identity is resolved. ADR-0006 says no path-derived ID is presented as canonical, but the API currently does exactly that through id(). Before #110 freezes data around this, represent identity unknownness explicitly (the exact API shape is an architecture choice) or do not expose an unresolved value as canonical SettingId; keep provenance about provenance.

Major

  1. Topology lookup converts data/invariant failure into semantic Unknown and deep-clones the gameplay catalog on every applicability query. hero_ability_exists() calls gameplay_data::builtin().ok(). A malformed/digest-invalid embedded gameplay dataset is not “insufficient setting evidence”; silently dropping that error changes a data failure into Applicability::Unknown. Also, builtin() clones the cached GameplayCatalog, so each hero-ability applicability query clones the gameplay dataset. This is a poor boundary for a tooling-facing query that #111/completion may call repeatedly. Use an error/invariant boundary that does not masquerade as semantic unknown and avoid cloning the entire catalog per query.

Minor

  1. ADR-0006 still says wildcard hero applicability is “resolved against the reviewed hero-setting data,” but this revision deliberately removed hero-setting presentation data as applicability evidence. The durable contract should state that the current #109 foundation only uses topology to disprove applicability and leaves topology-valid applicability Unknown until #110 supplies explicit applicability evidence.

Verified / resolved

  • prior presentation-as-applicability bug: resolved
  • known/future hero false certainty: resolved by preserving Unknown
  • missing slot/variant topology: now rejected
  • over-collapsed ability identities: no longer silently merged
  • target-aware presentation: NotApplicable guarded
  • numeric unknown/partial-bound behavior remains correct
  • gamemodes.general, en-US, typed slot/variant, provenance source classes, and ADR index remain fixed
  • no new cross-repo ownership, parser/emitter, source-preservation, or unrelated-refactor issues found
  • latest CI for 60e6845: stable, MSRV 1.85, real-project scenarios, and Catalog check all green

The branch is mergeable but still behind current main by two CI-only commits; that is not a semantic blocker given the current successful PR run.

@Teakowa

Teakowa commented Aug 27, 2026

Copy link
Copy Markdown
Contributor Author

Fourth review follow-up is complete in c2ba0628c1528380d8b1d2a44c98196e5fd0c7c5.

Addressed all four findings:

  1. Added SettingTarget::TeamAbility and SettingTargetKind::TeamAbility, and project heroes.<team>.enablePrimaryFire / enableUlt-style paths with their logical slot instead of dropping slot information at Team.
  2. Added SettingIdentity::{Known, Unknown}. Unresolved ability or unknown semantic mappings now expose id() -> None; SettingProvenance remains solely evidence provenance and is no longer overloaded as identity state.
  3. Added gameplay_data::builtin_ref() to borrow the cached gameplay catalog without cloning. Topology loading errors now propagate as Result<Applicability, GameplayDataError> rather than becoming Applicability::Unknown.
  4. Updated ADR-0006 to state that Define the canonical typed settings schema and hero applicability model #109 only uses topology to disprove applicability; topology-valid targets remain Unknown until explicit applicability evidence arrives in Build the canonical typed Workshop settings catalog #110.

Local gates are green: settings pipeline 23/23, workspace clippy with -D warnings, catalog check, formatting, and diff hygiene. Please re-run the review on the pushed head.

@Teakowa Teakowa left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changes required. Full re-audit of c2ba062 against #109 and current main.

The two architecture findings from the previous review are materially fixed: team/common slot-bearing settings now preserve the logical slot through TeamAbility, unresolved ability identities are explicit through SettingIdentity::Unknown/id() -> None, and the gameplay topology lookup is borrowed and fallible instead of cloning the whole catalog or collapsing dataset errors into Unknown.

Blocker

  1. applicability() still does source-target-kind matching rather than the effective hero/ability applicability required by #109. The current table contains common/team hero settings such as heroes.<team>.health%, heroes.<team>.enablePrimaryFire, heroes.<team>.enableUlt, etc. target_for_team() now correctly preserves a slot as TeamAbility, but SettingDefinition::applicability() only matches TargetPattern::Team with SettingTarget::Team and TargetPattern::TeamAbility with SettingTarget::TeamAbility. A consumer asking whether the common health% definition applies to a concrete Hero, or whether the common enablePrimaryFire definition applies to a concrete HeroAbility, therefore falls through to NotApplicable.

    That is an exact false negative and exposes the common/team source organization that #109 explicitly says consumers must not need to understand. The #109 AC says effective hero/ability applicability must be queryable independently of common/exclusion/addition organization. The foundation should support the narrowing relation from common/team definitions to concrete hero/hero-ability targets (using team matching and gameplay topology where needed), returning Unknown when applicability evidence is still incomplete rather than NotApplicable. Add representative tests for at least Team -> Hero and TeamAbility -> HeroAbility.

    Relatedly, the TeamAbility branch currently requires expected_variant == actual_variant; this makes a definition with no variant constraint reject a concrete variant, unlike the HeroAbility branch where None is non-restrictive. Keep the optional-variant semantics consistent when fixing the common-to-concrete path.

Major

  1. localized_name() silently drops the newly explicit gameplay-data failure. applicability() now correctly returns Result<Applicability, GameplayDataError>, but localized_name() calls self.applicability(target).ok()?. A malformed/digest-invalid canonical gameplay dataset therefore becomes None, indistinguishable from “no presentation / not applicable”. This recreates the exact failure-boundary problem the new fallible applicability API was intended to fix. Propagate the error (for example with a fallible target-aware presentation query), or otherwise keep a clearly separate best-effort API rather than silently converting a canonical-data failure into absence.

  2. ADR-0006 still documents the superseded identity contract. The accepted ADR says unresolved hero-ability settings are isolated under setting.hero.ability.custom.* and “marked as semantically unreviewed”. The implementation now deliberately does the opposite: SettingIdentity::Unknown, id() -> None, while provenance remains reviewed: true because the evidence record itself is reviewed. #109 requires the owner-side ADR to be the durable contract, so update that paragraph to describe the explicit unknown-identity state and keep identity review separate from provenance review.

Minor

  1. gameplay_data::builtin_ref() expands the public gameplay API even though #109 only needs an internal borrowed lookup. gameplay_data is a public module, so pub fn builtin_ref() becomes a new consumer contract. If there is no independent consumer requirement for a borrowed 'static dataset API, prefer pub(crate) (or another internal helper) and keep the #109 patch from broadening the gameplay API incidentally. If this is intentionally a public gameplay contract, document that intent rather than exposing it as an implementation side effect.

CI / merge readiness

The latest c2ba062 run is fully green: stable, Rust 1.85, Catalog check, and all five real-project scenarios pass. However, the branch is currently two commits behind main (#113 and #116, both CI-related), and the successful pull-request run records base SHA f43f1694, not current main 4b6d1d25. Before final merge, synchronize/retrigger so the final merge result is exercised under the current repository CI configuration. The stale PR-body sentence about the old aggregate-count baseline should disappear or be updated at the same time.

No additional ownership/dependency violations, parser/emitter/source-preservation regressions, unrelated refactors, or test-hygiene issues were found in this pass.

Establish the Workshop-owned setting identity, scope, target, domain, applicability, provenance, and authored-versus-effective numeric contracts as a projection of the existing settings table. Preserve the source carrier and defer full catalog and editing APIs to #110 and #111. Fixes #109
Normalize the projected ability concepts, preserve unknown numeric bounds, distinguish known exclusions from unknown heroes, and report accurate locale, target, slot, variant, and evidence semantics. Related to #109
Complete applicability, canonical identity, partial-bound, and documentation follow-ups for #109.
Keep applicability, topology, and unresolved identity explicit until the reviewed settings catalog is available in #110.
Add team ability targets, explicit unknown identities, and fallible borrowed gameplay topology queries for #109.
Support common-to-concrete target narrowing and preserve fallible presentation and identity semantics for #109.
@Teakowa
Teakowa force-pushed the codex/issue-109-settings-semantic branch from c2ba062 to 368999c Compare August 28, 2026 07:07
@Teakowa

Teakowa commented Aug 28, 2026

Copy link
Copy Markdown
Contributor Author

Final review follow-up is complete in 368999c9529556dd0514b27c84cae34e1987cc0c.

Addressed the remaining findings:

  1. Effective applicability now narrows common Team definitions to concrete Hero targets and common TeamAbility definitions to concrete HeroAbility targets. Team, logical-slot, and canonical gameplay topology mismatches are NotApplicable; topology-valid but unevidenced applicability remains Unknown.
  2. None on an ability variant is an unconstrained variant. Explicit variants still match exactly, and missing slots/variants are rejected by topology.
  3. Target-aware localized_name now returns Result<Option<_>, GameplayDataError>, so canonical gameplay-data failures are not silently converted into missing presentation.
  4. ADR-0006 now distinguishes reviewed source evidence from resolved SettingIdentity; unresolved concepts remain SettingIdentity::Unknown with id() == None.
  5. gameplay_data::builtin_ref() is pub(crate), since it is only an internal borrowed lookup needed by settings applicability.
  6. The branch is rebased onto current main at 4b6d1d25; the PR body was refreshed and no longer contains the stale aggregate-count statement.

Verification on the rebased head:

  • cargo fmt --all --check
  • cargo clippy --workspace --all-targets -- -D warnings
  • cargo test --workspace --all-targets
  • cargo run -p workshop-rs --bin workshop-catalog-gen -- check
  • git diff --check

All passed.

@Teakowa Teakowa left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changes required. Full re-audit of the rebased head 368999c against #109.

The previous round is materially fixed: common Team / TeamAbility definitions now narrow to concrete hero targets, None variants are non-restrictive, target-aware presentation propagates gameplay-data failures, unresolved identity is explicit and separate from provenance, the borrowed gameplay lookup is crate-private, ADR-0006 reflects the new identity contract, and the branch is rebased onto current main. Current CI is fully green.

Blocker

  1. Known missing logical slots are still downgraded to Unknown when no variant is supplied. hero_ability_exists() currently returns catalog.hero(hero).and_then(|hero| (!hero.abilities_in_slot(slot).is_empty()).then_some(true)) for variant == None. For a known hero whose topology does not contain the requested slot, abilities_in_slot() is empty, so this yields None; both TeamAbility -> HeroAbility and HeroAbility -> HeroAbility then map that to Applicability::Unknown. That contradicts #109 and ADR-0006, which require kit topology to disprove impossible targets and state that missing slots/variants are NotApplicable.

    Preserve the distinction between an unknown hero (None) and a known hero with a missing slot (Some(false)). A representative regression should cover a known hero + absent slot with variant: None (the existing tests cover an explicit missing variant, which takes the other branch and therefore misses this case).

Minor

  1. settings::table::is_generated_entry() is exposed publicly even though it is only an implementation helper for the schema projection. settings::table is public, so the new pub fn becomes a consumer-visible API that classifies entries by pointer identity against the current generated-table storage. #110 is explicitly allowed to replace/reconcile that storage strategy; consumers should obtain evidence provenance through SettingDefinition::provenance(), not depend on this helper. Prefer pub(crate) unless there is an independent public contract for it.

Verified / no additional findings

  • common/team effective narrowing and optional-variant matching are now structurally correct;
  • localized_name() no longer swallows GameplayDataError on hero-aware resolution;
  • SettingIdentity::Unknown / id() -> None correctly separates unresolved concept identity from reviewed provenance;
  • numeric unknown/partial-bound behavior remains sound;
  • source-preserving Settings / SettingsNode, parser/emitter ownership, and cross-repo boundaries remain intact;
  • no unrelated refactor or test-count/snapshot padding found;
  • head 368999c is based on current main (4b6d1d25) and CI passes stable, Rust 1.85, Catalog check, and all real-project scenarios.

@Teakowa

Teakowa commented Aug 28, 2026

Copy link
Copy Markdown
Contributor Author

Follow-up for the remaining blocker is complete in 92c7c96ce225df1ad6da11349f75a32e0f8a6b5c.

  • Fixed known-hero topology handling when variant: None: a known hero with a missing logical slot now returns Some(false) and therefore Applicability::NotApplicable.
  • Unknown heroes still return None and remain Applicability::Unknown.
  • Added a regression test for Ana + missing ability3 + unconstrained variant.
  • Reduced settings::table::is_generated_entry() from public to pub(crate); consumers continue to receive the semantic fact through SettingDefinition::provenance().

Focused settings tests, fmt, Clippy, and diff checks pass.

@Teakowa Teakowa left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Re-audited the full diff at 92c7c96 against #109 and the #109/#110 ownership boundary. No remaining findings.

The latest follow-up resolves both findings from the previous round:

  • hero_ability_exists() now preserves the required distinction between an unknown hero (None) and a known hero with a missing logical slot (Some(false)), so a known missing slot with variant: None resolves to NotApplicable. The added regression exercises that exact branch.
  • settings::table::is_generated_entry() is now pub(crate), avoiding an unnecessary public pointer-identity/table-organization contract.

I also rechecked the rest of the PR rather than only the follow-up commit: common Team / TeamAbility narrowing, unconstrained None variants, explicit missing variants, gameplay-data error propagation, locale presentation behavior, unresolved identity vs provenance, unknown/partial numeric bounds, gamemodes.general, source-preserving Settings/SettingsNode boundaries, and ADR/docs alignment remain consistent with #109. I found no new ownership, dependency-direction, parser/emitter, test-quality, or scope regressions.

Current base is still main@4b6d1d25; the PR is mergeable, and all four CI jobs on 92c7c96 are green (stable quality, Rust 1.85 quality, catalog check, and five real-project scenarios).

The authenticated account is the PR author, so this is submitted as a COMMENT review rather than an approval. From the code-review side, this head is ready to merge.

@Teakowa
Teakowa merged commit 2c4df60 into main Aug 28, 2026
4 checks passed
@Teakowa
Teakowa deleted the codex/issue-109-settings-semantic branch August 28, 2026 07:39
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