1# active-level-identity Specification23## Purpose4TBD - created by archiving change stabilize-active-level-identity. Update Purpose after archive.5## Requirements6### Requirement: The active level SHALL use a stable catalog identity78The application SHALL use the existing `${packId}:${localId}` level ID as the9canonical active-level identity. Numeric indexes MAY select a level but SHALL10not be used as the durable identity across catalog mutations.1112#### Scenario: Selection survives unrelated registration1314- **WHEN** the active level is selected and another pack is registered before15 it16- **THEN** the same stable active level ID remains selected and resolves to the17 same level1819#### Scenario: Replacement preserves an existing local level2021- **WHEN** a pack is replaced with the same pack ID and still contains the22 active local level ID23- **THEN** the active stable ID is preserved and resolves to the replacement24 level2526#### Scenario: Replacement removes the active local level2728- **WHEN** a pack replacement omits the active local level29- **THEN** selection falls back to the replacement pack's first level or the30 first available catalog level, and no stale ID remains active3132### Requirement: Catalog reads SHALL preserve clone safety without repeated full clones3334Single-level and active-level reads SHALL resolve directly from normalized35catalog records. Full-list compatibility methods SHALL continue returning36fresh caller-owned data, while internal derived views SHALL be invalidated on37pack register, replacement, and removal.3839#### Scenario: A caller cannot mutate a cached catalog record4041- **WHEN** a caller mutates a level returned by a catalog read42- **THEN** a later read returns the normalized authored value4344#### Scenario: Replacement invalidates level and pack views4546- **WHEN** a pack is replaced with changed metadata or levels47- **THEN** subsequent reads return only replacement data and no stale cached48 level, pack, or level count4950#### Scenario: Removal invalidates active and list views5152- **WHEN** an imported pack is removed53- **THEN** subsequent reads omit its levels and the active ID resolves to a54 remaining level or `null`5556### Requirement: Progress and win handoffs SHALL resolve by stable identity5758Progress marking, win promotion, continue navigation, and pack refresh SHALL59re-resolve the captured stable level ID before acting. A catalog mutation or60level transition SHALL not allow an old numeric index or reused authored ID to61promote the wrong level.6263#### Scenario: A win callback cannot promote a replaced level6465- **WHEN** a level is won and its pack is replaced before the delayed win66 handoff runs67- **THEN** the callback promotes only the still-resolved stable level ID or68 safely abandons the stale handoff6970#### Scenario: Progress remains attached to pack and local IDs7172- **WHEN** levels are reordered by registration or an unrelated pack changes73- **THEN** solved progress remains attached to the original `packId` and74 `localId`7576### Requirement: Cache invalidation SHALL be observable at mutation boundaries7778Catalog mutations SHALL invalidate all derived active-level, pack-summary, and79level-lookup views that can contain changed records. The invalidation SHALL80occur before the mutation result is returned to application callers.8182#### Scenario: Replace refreshes summaries immediately8384- **WHEN** a pack replacement changes its name, world metadata, or level count85- **THEN** the next pack and level reads expose replacement summaries without a86 refresh-only workaround