# Catalog/Production Schema Parity Ledger

Status: Phase 1 classification ledger, not an implementation plan.

This ledger exists to prevent a bulk lift of production-only machine semantics
into the catalog. Every production/catalog drift item must be classified before
it is resolved. The catalog may only absorb items classified as genuine machine
semantics, and each such lift must land in a small TLC-verified batch.

## Batch Rule

One batch is one DSL fragment: one transition with its guards, updates, emitted
effects, invariants, and any new state fields or typed values that transition
touches. A batch may delete production drift instead of lifting it. A batch may
move runtime mechanics into bridge code instead of lifting it.

Every batch must record:

- the exact item(s) resolved;
- the bucket decision and reason;
- the typed transition rule;
- the invariant text, or an explicit reason no new invariant is required;
- TLC command and result;
- states explored before and after;
- elapsed time before and after.

## Buckets

- `semantic`: genuine machine-owned lifecycle state/transition/effect that the
  catalog should have always modeled.
- `bridge`: runtime, concurrency, shell, or conversion mechanics that must not
  become catalog semantics.
- `delete`: drift that should be removed from production instead of lifted.
- `unclassified`: known drift that has not yet been proven safe to lift, move,
  or delete.

## Current Drift Summary

| Machine | Current parity mismatch classes | Semantic count | Bridge count | Delete count | Unclassified count | Notes |
| --- | --- | ---: | ---: | ---: | ---: | --- |
| MeerkatMachine | none | 25 | 0 | 12 | 0 | Closed by the catalog-owned Phase 1 lift already present in this worktree; production and catalog schemas now match, and the BuildBuddy machine-authority lane passes after the intentional module-lock hash refresh. Remote RBE lacks `tlc`, so local per-machine TLC rows remain the source of truth for TLC evidence. |
| AuthMachine | none after Auth batch 1 | 1 | 1 | 0 | 0 | Resolved by making production invoke the catalog-owned Auth DSL body. |
| MobMachine | none | 203 | 0 | 0 | 0 | Closed by moving the production MobMachine body into the catalog-owned `mob_catalog_machine_dsl!` macro and making production invoke it. |
| ScheduleLifecycleMachine | none | 7 | 0 | 4 | 0 | Closed: production now re-exports the catalog-owned body; reciprocal supersede ack state/input/transitions and `DeleteDeleted` are wired through domain runtime tests and TLC. |
| OccurrenceLifecycleMachine | none | 3 | 4 | 3 | 0 | Closed: production now re-exports the catalog-owned body; supersede ack route and failure-class proxy naming are converged through the catalog-owned body with runtime tests and TLC. |

## Item Ledger

| Item | Owner | Bucket | Reason | Typed transition rule | Invariant | TLC command/result | TLC states/time before | TLC states/time after |
| --- | --- | --- | --- | --- | --- | --- | --- | --- |
| `MeerkatMachine::external surface shared staging state` (`state_field.production_only.known_surfaces`, `state_field.production_only.active_surfaces`, `state_field.production_only.surface_base_state`, `state_field.production_only.surface_staged_op`, `state_field.production_only.reload_staged_surfaces`, `state_field.production_only.surface_staged_intent_sequence`, `state_field.production_only.next_staged_intent_sequence`, `state_field.production_only.surface_phase`, and matching init fields) | MeerkatMachine | semantic | The machine owns staging identity, monotonic staged-intent sequence allocation, the active-surface reload guard, and the global shutdown gate. This is the reviewed exception to the one-operation batch rule: the fields are shared representation needed by the Add, Remove, Reload, and Shutdown rows below, and splitting them would make the rows non-executable. | Shared representation only; operation legality is documented in the per-input rows below. | `staged_surface_ops_are_known_and_sequenced`: every staged-op key is known and sequenced. `staged_reload_surfaces_are_active`: every staged reload key is active. | `./scripts/repo-cargo test -p meerkat-runtime --lib handles::external_tool_surface::tests -- --nocapture` passed (`4 passed`); `./scripts/repo-cargo xtask machine-check-drift --all` passed; BuildBuddy `scripts/buildbuddy-dev machine-authority` passed, invocation `5b63b7ad-8f3e-4536-8c6f-6489fa1670ca`. | Before: production-only surface staging fields were reported by the Phase 1 drift inventory. | After: zero MeerkatMachine drift rows; BuildBuddy authority lane passed. |
| `MeerkatMachine::external surface add staging` (`input.production_only.SurfaceStageAdd`, `transition.production_only.SurfaceStageAddAttached`, `transition.production_only.SurfaceStageAddRunning`) | MeerkatMachine | semantic | Add staging is a machine-owned lifecycle decision: the shell may request add, but the machine records the staged op and mints the staged-intent sequence. | `on input SurfaceStageAdd { surface_id, now_ms }` in `Attached` or `Running`; require `surface_phase == Operating`; insert `surface_id` into `known_surfaces`; set `surface_staged_op[surface_id] = Add`; clear any reload marker for the surface; increment `next_staged_intent_sequence`; store the sequence. | Covered by `staged_surface_ops_are_known_and_sequenced`. | Runtime proof: `./scripts/repo-cargo test -p meerkat-runtime --lib handles::external_tool_surface::tests -- --nocapture` passed (`4 passed`); alphabet proof: `./scripts/repo-cargo test -p meerkat-machine-codegen --test runtime_alphabet_parity -- --nocapture` passed (`6 passed`); BuildBuddy authority lane passed. | Before: add staging appeared as production-only state/input/transition drift. | After: zero MeerkatMachine drift rows; typed staging is catalog-owned. |
| `MeerkatMachine::external surface remove staging` (`input.production_only.SurfaceStageRemove`, `transition.production_only.SurfaceStageRemoveAttached`, `transition.production_only.SurfaceStageRemoveRunning`) | MeerkatMachine | semantic | Remove staging is a machine-owned lifecycle decision for the same reason as add staging. | `on input SurfaceStageRemove { surface_id, now_ms }` in `Attached` or `Running`; require `surface_phase == Operating`; insert `surface_id` into `known_surfaces`; set `surface_staged_op[surface_id] = Remove`; clear any reload marker; increment and store the staged-intent sequence. | Covered by `staged_surface_ops_are_known_and_sequenced`. | Runtime proof: `./scripts/repo-cargo test -p meerkat-runtime --lib handles::external_tool_surface::tests -- --nocapture` passed (`4 passed`); BuildBuddy authority lane passed. | Before: remove staging appeared as production-only state/input/transition drift. | After: zero MeerkatMachine drift rows; typed staging is catalog-owned. |
| `MeerkatMachine::external surface reload staging` (`input.production_only.SurfaceStageReload`, `transition.production_only.SurfaceStageReloadAttached`, `transition.production_only.SurfaceStageReloadRunning`) | MeerkatMachine | semantic | Reload was previously production-only and catalog-inconsistent. It is machine-owned because the machine must reject reload unless the surface is active before minting a reload staged-intent sequence. | `on input SurfaceStageReload { surface_id, now_ms }` in `Attached` or `Running`; require `surface_phase == Operating` and `active_surfaces.contains(surface_id)`; insert `surface_id` into `known_surfaces`; set `surface_staged_op[surface_id] = Reload`; mark it in `reload_staged_surfaces`; increment and store the staged-intent sequence. | `staged_reload_surfaces_are_active`: every key in `reload_staged_surfaces` must be present in `active_surfaces`. | Runtime proof: `stage_reload_requires_active_base_state` rejects absent/non-active reload; `stage_reload_mints_sequence_for_active_surface` accepts active reload and records `Reload` with sequence `1`; BuildBuddy authority lane passed. | Before: reload staging appeared as production-only state/input/transition drift. | After: zero MeerkatMachine drift rows; reload guard is catalog-owned. |
| `MeerkatMachine::external surface shutdown gate` (`input.production_only.SurfaceShutdown`, `transition.production_only.SurfaceShutdownAttached`, `transition.production_only.SurfaceShutdownRunning`) | MeerkatMachine | semantic | Shutdown is a global machine-owned lifecycle gate: after shutdown, staging inputs guarded by `surface_phase == Operating` are rejected. | `on input SurfaceShutdown` in `Attached` or `Running`; set `surface_phase = Shutdown`; preserve the session lifecycle phase. | Covered by the staging transition guards and unit test `staging_inputs_reject_after_surface_shutdown`. | Runtime proof: `./scripts/repo-cargo test -p meerkat-runtime --lib handles::external_tool_surface::tests -- --nocapture` passed (`4 passed`); BuildBuddy authority lane passed. | Before: shutdown gate appeared as production-only input/transition drift. | After: zero MeerkatMachine drift rows; shutdown gate is catalog-owned. |
| `MeerkatMachine::catalog placeholder surface add/remove/reload/shutdown signals` (`signal.catalog_only.StageAdd`, `signal.catalog_only.StageRemove`, `signal.catalog_only.StageReload`, `signal.catalog_only.ShutdownSurface`, matching Attached/Running placeholder transitions) | MeerkatMachine | delete | These parameterless catalog placeholders emitted `EmitExternalToolDelta` without carrying the surface id or mutating machine-owned staging state. They were deleted in favor of typed runtime-internal inputs that carry the surface id and update typed staging state directly. | Not a retained transition decision; replacement typed rules are recorded in the Add, Remove, Reload, and Shutdown rows above. | No independent invariant is required for deleted placeholders. | Inventory proof: `./scripts/repo-cargo test -p meerkat-machine-codegen --test runtime_schema_parity -- --nocapture` passed; BuildBuddy authority lane passed. | Before: placeholders appeared as catalog-only drift against production. | After: zero MeerkatMachine drift rows. |
| `MeerkatMachine::remaining grouped drift inventory` | MeerkatMachine | semantic/delete | The executable inventory no longer reports MeerkatMachine carry-forward drift. The remaining production-only and catalog-only groups from the earlier ratchet were resolved by the catalog-owned Phase 1 lift already present in this worktree. | Closed by schema-body convergence; production now matches the catalog schema, so future Meerkat transition decisions must be made in the catalog body before production can consume them. | The unignored full-schema parity gate is the regression invariant for body equality; transition-level invariants remain in the catalog machine/specs. | Inventory proof: `./scripts/repo-cargo test -p meerkat-machine-codegen --test runtime_schema_parity -- --nocapture` passed with zero Meerkat rows; drift proof: `./scripts/repo-cargo xtask machine-check-drift --all` passed; BuildBuddy authority lane passed. | Before: MeerkatMachine carried grouped schema drift. | After: zero MeerkatMachine drift rows. |
| `AuthMachine::transition.changed.Release` | AuthMachine | semantic | Production moved to terminal `Released` without publishing the lifecycle event that every accepted Auth lifecycle-phase transition publishes. Catalog already owned the correct semantics; production was converged to the catalog body rather than lifting production drift. | `on input Release`; no guard; `to Released`; `emit EmitLifecycleEvent { new_state: self.lifecycle_phase }`. | Every accepted AuthMachine lifecycle-phase transition emits exactly one `EmitLifecycleEvent`, including terminal `Release`. No new state invariant was added because this batch introduces no state field, guard, or phase; the existing `terminal [Released]` rule plus the `auth_lease_lifecycle_publication_protocol_covered` composition invariant cover the terminal publication obligation. | `./scripts/repo-cargo xtask machine-verify --machine auth --composition auth_lease_bundle --profile ci` passed after regeneration. | Direct baseline TLC over current catalog specs: Auth machine `1763 generated / 305 distinct / 00s`; `auth_lease_bundle` `2 generated / 1 distinct / 00s`. | After convergence: Auth machine `1763 generated / 305 distinct / 00s`; `auth_lease_bundle` `2 generated / 1 distinct / 00s`. |
| `AuthMachine::effect_disposition.changed.EmitLifecycleEvent` | AuthMachine | bridge | Catalog declared `EmitLifecycleEvent => external handoff auth_lease_lifecycle_publication`; production only declared `external`. This is handoff/protocol metadata, not new auth lifecycle state. Production now consumes the catalog body so handoff metadata cannot drift. | Not a transition decision. Runtime bridge code may realize the handoff, but may not decide whether an Auth lifecycle transition publishes it. | Every `EmitLifecycleEvent` crosses the explicit `auth_lease_lifecycle_publication` protocol rather than ad hoc shell observation. The composition invariant is `auth_lease_lifecycle_publication_protocol_covered`. | `./scripts/repo-cargo xtask machine-verify --machine auth --composition auth_lease_bundle --profile ci` passed after regeneration. | Direct baseline TLC over current catalog specs: Auth machine `1763 generated / 305 distinct / 00s`; `auth_lease_bundle` `2 generated / 1 distinct / 00s`. | After convergence: Auth machine `1763 generated / 305 distinct / 00s`; `auth_lease_bundle` `2 generated / 1 distinct / 00s`. |
| `MobMachine::remaining grouped drift inventory` | MobMachine | semantic | The 203-item Mob carry-forward group was genuine machine semantics: run/frame/loop seed and projection state, member startup barriers, kickoff phases, runtime session binding, and the corresponding effects/dispositions all affect machine-owned legality or emitted outcomes. The catalog now owns that full body through `mob_catalog_machine_dsl!`; `meerkat-mob` retains only bridge types, conversions, effect realization, and tests. | Closed by shared body convergence: every Mob `state`, `input`, `signal`, `effect`, `disposition`, and `transition` item is emitted by the catalog macro, with the production crate passing only its Rust crate/module binding. Flow projection helpers no longer call hidden reducer transition tables. Core seed/terminal/loop projection paths project from accepted MobMachine state; reducer-visible mutations that do not yet have machine-owned typed payload transitions fail closed. | The regression invariant is the unignored full-schema parity gate plus the direct-flow reducer ratchet: production may not call reducer transitions or mutate flow projections unless it is committing a typed MobMachine outcome. | Inventory proof: `./scripts/repo-cargo test -p meerkat-machine-codegen --test runtime_schema_parity -- --nocapture` passed with `11 passed; 2 ignored` and no drift rows. Owner proof: `./scripts/repo-cargo check -p meerkat-mob` passed; `./scripts/repo-cargo test -p meerkat-mob --lib run::tests -- --nocapture` passed with `19 passed`; ratchet proof `./scripts/repo-cargo test -p xtask --features machine-authority --test machines_contracts -- --nocapture` passed with `25 passed`. TLC note: the generated Mob CI profile is a tractable drift smoke test with empty identifier domains; non-empty Mob TLC still needs a dedicated bounded witness profile. | Before: 203 carry-forward items pinned by `phase1_schema_parity_inventory_pins_remaining_drift_counts`; hidden reducer semantics could execute behind coarse authorization. | After: 0 schema carry-forward items; un-migrated active reducer semantics are fail-closed rather than production-owned. |
| `ScheduleLifecycleMachine::transition.changed.PauseActiveOrPaused` | ScheduleLifecycleMachine | delete | Production had a named guard `pause_timestamp_present { at_utc_ms == at_utc_ms }`. `at_utc_ms` is a typed `u64` input, so the guard is tautological runtime-shaped drift and adds no semantic constraint. | `on input Pause { at_utc_ms }`; require lifecycle phase `Active` or `Paused`; no state update; transition to `Paused`; emit one `EmitScheduleNotice` with current lifecycle phase and revision. | No new invariant is required because this batch deletes a vacuous guard and does not add state, phase, effects, or a new transition. Existing phase/notice semantics remain catalog-owned. | Baseline direct TLC passed; after deletion `./scripts/repo-cargo xtask machine-verify --machine schedule_lifecycle --composition schedule_bundle --profile ci` passed after regeneration. | ScheduleLifecycleMachine `11861 generated / 758 distinct / 00s`; `schedule_bundle` not rerun for this direct baseline. | ScheduleLifecycleMachine `11861 generated / 758 distinct / 00s`; `schedule_bundle` `2 generated / 1 distinct / 00s`. |
| `ScheduleLifecycleMachine::transition.changed.ResumeActiveOrPaused` | ScheduleLifecycleMachine | delete | Production had a named guard `resume_timestamp_present { at_utc_ms == at_utc_ms }`. `at_utc_ms` is a typed `u64` input, so the guard is tautological runtime-shaped drift and adds no semantic constraint. | `on input Resume { at_utc_ms }`; require lifecycle phase `Active` or `Paused`; no state update; transition to `Active`; emit one `EmitScheduleNotice` with current lifecycle phase and revision. | No new invariant is required because this batch deletes a vacuous guard and does not add state, phase, effects, or a new transition. Existing phase/notice semantics remain catalog-owned. | Baseline direct TLC passed; after deletion `./scripts/repo-cargo xtask machine-verify --machine schedule_lifecycle --composition schedule_bundle --profile ci` passed after regeneration. | ScheduleLifecycleMachine `11861 generated / 758 distinct / 00s`; `schedule_bundle` not rerun for this direct baseline. | ScheduleLifecycleMachine `11861 generated / 758 distinct / 00s`; `schedule_bundle` `2 generated / 1 distinct / 00s`. |
| `ScheduleLifecycleMachine::transition.changed.DeleteActive` | ScheduleLifecycleMachine | delete | Production had a named guard `delete_timestamp_present { at_utc_ms == at_utc_ms }`. `at_utc_ms` is a typed `u64` input, so the guard is tautological runtime-shaped drift and adds no semantic constraint. | `on input Delete { at_utc_ms }`; require lifecycle phase `Active`; increment `revision`; clear `planning_cursor_utc_ms`; transition to `Deleted`; emit one `EmitScheduleNotice` and one `SupersedeOpenOccurrences` effect. | No new invariant is required because this batch deletes a vacuous guard and does not add state, phase, effects, or a new transition. Existing catalog invariants still cover `revision_is_positive` and `deleted_has_no_planning_cursor`. | Baseline direct TLC passed; after deletion `./scripts/repo-cargo xtask machine-verify --machine schedule_lifecycle --composition schedule_bundle --profile ci` passed after regeneration. | ScheduleLifecycleMachine `11861 generated / 758 distinct / 00s`; `schedule_bundle` not rerun for this direct baseline. | ScheduleLifecycleMachine `11861 generated / 758 distinct / 00s`; `schedule_bundle` `2 generated / 1 distinct / 00s`. |
| `ScheduleLifecycleMachine::transition.changed.DeletePaused` | ScheduleLifecycleMachine | delete | Production had a named guard `delete_timestamp_present { at_utc_ms == at_utc_ms }`. `at_utc_ms` is a typed `u64` input, so the guard is tautological runtime-shaped drift and adds no semantic constraint. | `on input Delete { at_utc_ms }`; require lifecycle phase `Paused`; increment `revision`; clear `planning_cursor_utc_ms`; transition to `Deleted`; emit one `EmitScheduleNotice` and one `SupersedeOpenOccurrences` effect. | No new invariant is required because this batch deletes a vacuous guard and does not add state, phase, effects, or a new transition. Existing catalog invariants still cover `revision_is_positive` and `deleted_has_no_planning_cursor`. | Baseline direct TLC passed; after deletion `./scripts/repo-cargo xtask machine-verify --machine schedule_lifecycle --composition schedule_bundle --profile ci` passed after regeneration. | ScheduleLifecycleMachine `11861 generated / 758 distinct / 00s`; `schedule_bundle` not rerun for this direct baseline. | ScheduleLifecycleMachine `11861 generated / 758 distinct / 00s`; `schedule_bundle` `2 generated / 1 distinct / 00s`. |
| `ScheduleLifecycleMachine::reciprocal supersede ack group` (`state_field.catalog_only.superseded_ack_ids`, `init_field.catalog_only.superseded_ack_ids`, `input.catalog_only.ConfirmOccurrencesSuperseded`, `transition.catalog_only.ConfirmOccurrencesSupersededActive`, `transition.catalog_only.ConfirmOccurrencesSupersededPaused`, `transition.catalog_only.ConfirmOccurrencesSupersededDeleted`) | ScheduleLifecycleMachine | semantic | The catalog models the schedule-side endpoint for occurrence supersession acknowledgements. Production now re-exports the catalog body and persists `superseded_ack_ids` on the domain `Schedule`, so this is no longer projection or bridge-owned. | `on input ConfirmOccurrencesSuperseded { occurrence_id }`; in `Active`, `Paused`, or `Deleted`, insert `occurrence_id` into `superseded_ack_ids`; preserve current lifecycle phase; emit no effects. | Runtime invariant proven by `schedule_records_supersede_ack_without_effects`: ack insert preserves phase and revision, emits no effects, and records the exact `OccurrenceId`. TLC invariant coverage remains the catalog schedule and schedule-bundle route model. | `./scripts/repo-cargo test -p meerkat-schedule --lib lifecycle::tests -- --nocapture` passed; `./scripts/repo-cargo xtask machine-verify --machine schedule_lifecycle --composition schedule_bundle --profile ci` passed after production consumed the catalog body. | ScheduleLifecycleMachine `11861 generated / 758 distinct / 00s`; `schedule_bundle` `2 generated / 1 distinct / 00s`. | ScheduleLifecycleMachine `11861 generated / 758 distinct / 00s`; `schedule_bundle` `2 generated / 1 distinct / 00s`. |
| `ScheduleLifecycleMachine::transition.catalog_only.DeleteDeleted` | ScheduleLifecycleMachine | semantic | Catalog models idempotent delete once a schedule is already terminal `Deleted`. Production now re-exports the catalog body and the domain lifecycle path accepts this no-op through the real `Schedule::apply` entrypoint. | `on input Delete { at_utc_ms }`; require lifecycle phase `Deleted`; no state update; remain `Deleted`; emit no effects. | Runtime invariant proven by `delete_from_deleted_is_idempotent_noop`: deleting an already deleted schedule preserves revision, stays `Deleted`, emits no effects, and reports no revision bump. | `./scripts/repo-cargo test -p meerkat-schedule --lib lifecycle::tests -- --nocapture` passed; `./scripts/repo-cargo xtask machine-verify --machine schedule_lifecycle --composition schedule_bundle --profile ci` passed after production consumed the catalog body. | ScheduleLifecycleMachine `11861 generated / 758 distinct / 00s`; `schedule_bundle` `2 generated / 1 distinct / 00s`. | ScheduleLifecycleMachine `11861 generated / 758 distinct / 00s`; `schedule_bundle` `2 generated / 1 distinct / 00s`. |
| `OccurrenceLifecycleMachine::transition.changed.LeaseExpiredFromClaimed` | OccurrenceLifecycleMachine | delete | Production had a named guard `lease_expiry_timestamp_present { at_utc_ms == at_utc_ms }`. `at_utc_ms` is a typed `u64` input, so the guard is tautological runtime-shaped drift and adds no semantic constraint. | `on input LeaseExpired { at_utc_ms }`; require lifecycle phase `Claimed`; clear claim owner, lease expiry, token, delivery correlation, claimed timestamp, and dispatched timestamp; transition to `Pending`; emit `LeaseExpired`. | No new invariant is required because this batch deletes a vacuous guard and does not add state, phase, effects, or a new transition. Existing `live_claim_requires_owner` remains catalog-owned. | Baseline direct TLC passed; after deletion `./scripts/repo-cargo xtask machine-verify --machine occurrence_lifecycle --composition schedule_bundle --profile ci` passed after regeneration. | OccurrenceLifecycleMachine `33646 generated / 8045 distinct / 00s`; `schedule_bundle` not rerun for this direct baseline. | OccurrenceLifecycleMachine `33646 generated / 8045 distinct / 00s`; `schedule_bundle` `2 generated / 1 distinct / 00s`. |
| `OccurrenceLifecycleMachine::transition.changed.LeaseExpiredFromDispatching` | OccurrenceLifecycleMachine | delete | Production had a named guard `lease_expiry_timestamp_present { at_utc_ms == at_utc_ms }`. `at_utc_ms` is a typed `u64` input, so the guard is tautological runtime-shaped drift and adds no semantic constraint. | `on input LeaseExpired { at_utc_ms }`; require lifecycle phase `Dispatching`; clear claim owner, lease expiry, token, delivery correlation, claimed timestamp, and dispatched timestamp; transition to `Pending`; emit `LeaseExpired`. | No new invariant is required because this batch deletes a vacuous guard and does not add state, phase, effects, or a new transition. Existing `live_claim_requires_owner` remains catalog-owned. | Baseline direct TLC passed; after deletion `./scripts/repo-cargo xtask machine-verify --machine occurrence_lifecycle --composition schedule_bundle --profile ci` passed after regeneration. | OccurrenceLifecycleMachine `33646 generated / 8045 distinct / 00s`; `schedule_bundle` not rerun for this direct baseline. | OccurrenceLifecycleMachine `33646 generated / 8045 distinct / 00s`; `schedule_bundle` `2 generated / 1 distinct / 00s`. |
| `OccurrenceLifecycleMachine::transition.changed.LeaseExpiredFromAwaitingCompletion` | OccurrenceLifecycleMachine | delete | Production had a named guard `lease_expiry_timestamp_present { at_utc_ms == at_utc_ms }`. `at_utc_ms` is a typed `u64` input, so the guard is tautological runtime-shaped drift and adds no semantic constraint. | `on input LeaseExpired { at_utc_ms }`; require lifecycle phase `AwaitingCompletion`; clear claim owner, lease expiry, token, delivery correlation, claimed timestamp, and dispatched timestamp; transition to `Pending`; emit `LeaseExpired`. | No new invariant is required because this batch deletes a vacuous guard and does not add state, phase, effects, or a new transition. Existing `live_claim_requires_owner` remains catalog-owned. | Baseline direct TLC passed; after deletion `./scripts/repo-cargo xtask machine-verify --machine occurrence_lifecycle --composition schedule_bundle --profile ci` passed after regeneration. | OccurrenceLifecycleMachine `33646 generated / 8045 distinct / 00s`; `schedule_bundle` not rerun for this direct baseline. | OccurrenceLifecycleMachine `33646 generated / 8045 distinct / 00s`; `schedule_bundle` `2 generated / 1 distinct / 00s`. |
| `OccurrenceLifecycleMachine::failure_class proxy naming group` (`state_field.changed.failure_class`, `input.changed.DeliveryFailed`, `input.changed.Misfire`, `input.changed.Skip`) | OccurrenceLifecycleMachine | bridge | Catalog and production now use the catalog-owned `OccurrenceFailureClass` DSL proxy. The variants were aligned with the production domain enum; production keeps a compatibility alias `FailureClass` only as bridge naming, not as a second machine schema. | Not a transition decision. Bridge conversion still maps domain `OccurrenceFailureClass` to the DSL proxy without changing skip, misfire, or delivery-failure legality. | Bridge invariant: every production failure-class variant maps bijectively to domain `OccurrenceFailureClass`, and all failure-class-carrying transitions preserve the same optional/required failure-class obligations modeled by `delivery_failed_records_failure_class`. | `./scripts/repo-cargo test -p meerkat-machine-codegen --test runtime_schema_parity` passed with no Occurrence drift. | OccurrenceLifecycleMachine `33646 generated / 8045 distinct / 00s`. | OccurrenceLifecycleMachine `33646 generated / 8045 distinct / 00s`; `schedule_bundle` `2 generated / 1 distinct / 00s`. |
| `OccurrenceLifecycleMachine::supersede ack route group` (`effect.catalog_only.OccurrencesSuperseded`, `transition.changed.SupersedePendingOrLive`, `effect_disposition.catalog_only.OccurrencesSuperseded`) | OccurrenceLifecycleMachine | semantic | Catalog emits a routed `OccurrencesSuperseded` effect when an occurrence absorbs `Supersede`; production now re-exports the catalog body and maps that effect into the domain lifecycle effect stream. | `on input Supersede { superseded_by_revision, at_utc_ms }`; require `Pending`, `Claimed`, `Dispatching`, or `AwaitingCompletion`; set `superseded_by_revision` and `completed_at_utc_ms`; transition to `Superseded`; emit `Superseded` and routed `OccurrencesSuperseded { occurrence_id: self.occurrence_id, superseding_revision }`. | Runtime invariant proven by `supersede_emits_occurrence_ack_effect`: every accepted pending supersede emits both `Superseded` and `OccurrencesSuperseded` carrying the occurrence's own id and the input revision. TLC schedule bundle covers the routed target input. | `./scripts/repo-cargo test -p meerkat-schedule --lib lifecycle::tests -- --nocapture` passed; `./scripts/repo-cargo xtask machine-verify --machine occurrence_lifecycle --composition schedule_bundle --profile ci` passed after production consumed the catalog body. | OccurrenceLifecycleMachine `33646 generated / 8045 distinct / 00s`; `schedule_bundle` `2 generated / 1 distinct / 00s`. | OccurrenceLifecycleMachine `33646 generated / 8045 distinct / 00s`; `schedule_bundle` `2 generated / 1 distinct / 00s`. |

## Guardrails

- Keep the full-schema parity convergence test unignored. Reintroducing any
  catalog/production body drift requires a classified ledger row first.
- Do not add production `machine!` bodies outside the declared Phase 1
  carry-forward list.
- Do not classify an item as `semantic` without a typed transition rule.
- Do not lift a `semantic` item without a non-vacuous invariant review.
- Do not count TLC pass/fail alone as evidence; record state count and elapsed
  time before and after the batch.
