# Timetabler to Resource Booking Phase 1 Contract

Status: canonical provider contract v2 is live for continuous Phase 1 staging projection; reverse delivery remains disabled and Phase 2 production implementation remains hard-blocked.

## Ownership and invariants

Timetabler is authoritative for Staff, Locations, activities, and final allocations in Phase 1. It emits canonical source data only. The Resource Booking-owned adapter owns Resource Booking transformation, source-ID mapping, validation/application of Timetabler's absolute occurrence facts, downstream retries, echo/idempotency handling, and reconciliation against Resource Booking.

Every canonical event is inserted in the same database transaction as its authoritative mutation. Timetabler never calls Resource Booking or the adapter from that transaction. Resource Booking must keep Timetabler-sourced records mirror-only and reject conflicting Resource Booking writes until the Phase 2 shared reservation authority is enabled.

## Canonical transport envelope version 2

Each finalized Timetabler `change_set_id` becomes exactly one transport envelope and one Kafka record. No member outbox row is published as an independently complete transaction. The outer JSON object contains:

- `event_id` (the stable change-set transport identity), `schema_version=2`, `event_type=timetabler.change_set.final_state`, and `source_system=timetabler`;
- the configured `source_scope`, the same value as `ordering_key`, and one positive globally commit-ordered `source_sequence` allocated while the authoritative transaction holds the scope cursor;
- `source_transaction_id`, `transaction_count`, and `complete=true`;
- aware UTC `committed_at`, one canonical `events_hash`, and a bounded `events` member array.

Each member retains its stable aggregate identity/version and provenance but is marked `transport.member_only=true`. A member contains `aggregate.deployment_id`, `aggregate.aggregate_type`, `aggregate.source_id`, its aggregate `event_version`, change-set index/count, audit/correlation metadata, an aware commit time, `committed_state_hash`, and canonical `committed_state`.

Activity final state includes week IDs plus explicit calendar facts and deterministic absolute occurrences: stable week/occurrence references, week start/local date, IANA timezone, selected DST fold, local start/end with offsets, and UTC start/end. Resource Booking still owns schema mapping and orchestration; it does not have to infer Timetabler's calendar.

Member events for an aggregate retain a monotonically increasing aggregate version. Transport envelopes have a separate gap-free source-scope sequence, and every record uses the single source-scope Kafka key so one partition preserves order. Resource Booking must verify both the outer events hash and member state hashes, deduplicate by outer event/transaction ID, apply the full member array atomically, and advance its one scope watermark/hash only after success. A failed/dead-letter sequence blocks every later sequence. Operator replay retries the same immutable transaction ID and sequence as one unit.

The exact provider fixtures are under `tests/contracts/resource_booking_phase1_v2/`. `change_set.json` has SHA-256 `bf4ae32e685af60415f768fd567fab0423086d5c67ec41dc262142a51e2f2d92`. The matching receiver foundation is Resource Booking commit `a3dcecd0c50daff88bcd030dd5dc039b09d98feb`, with consumer event-loop correction `ea01f2a`. The shared staging source scope is `default` and topic is `timetabler.activity.events`. Following fixed-watermark reconciliation, Timetabler activation run `31115855194` and Resource Booking validation run `31115992828` proved ordered live projection through source sequence 61 with zero lag, gap, quarantine, dead letter, mirror-only violation, or projection backlog. Reverse delivery remains disabled.

## Fixed-watermark snapshot

`GET /api/integration/resource-booking/v1/snapshot` requires a service bearer token and the exact configured `source_scope`. The response has only `schema_version`, `snapshot_watermark`, `next_cursor`, `complete`, and `envelopes`. The first request fixes the latest committed source sequence; every continuation supplies that same watermark and the preceding cursor.

At the fence, Timetabler selects the latest canonical member for every Staff, Location, and activity. It groups those latest members by their originating `change_set_id`/`source_sequence` into the same outer v2 transport contract, returns at most one independently hashed envelope per sequence, orders envelopes strictly by sequence, and never returns a member whose sequence exceeds the watermark. A snapshot composite may contain only the members of its originating change set that remain latest at the fence. Its deterministic snapshot event ID avoids colliding with a live composite whose member set differs.

## Decisions and approvals still required for Phase 1 completion

Publication defaults to disabled and additionally requires `RB_INTEGRATION_ACTIVATION_APPROVED=True`. Staging now has an explicitly approved Phase 1 activation, but service owners must still complete and record the broader Phase 1 decisions below before the Phase 2 entry gate can open:

1. Resource Booking acceptance of the v2 fixtures, adapter acknowledgement classes, retention, maximum payload, and schema evolution policy.
2. Stable deployment/source-scope namespace and academic-term/calendar identifiers.
3. IANA timezone and DST-fold policy; nonexistent local scheduled times fail the source transaction rather than producing ambiguous occurrences.
4. First-arrival late-response quarantine policy. Matching redelivery is never discarded solely by age.
5. Kafka topic/partition/retention/message-size values and producer/consumer ACL/TLS/SASL identities.

Neither an acknowledged transport publish nor the legacy `confirmed_schedule` message proves Resource Booking mirror application. Adapter receipt/application and reconciliation are separate observable states.

## Compatibility statement

Atomic application is compatible with the current engine protocol because the engine returns a complete result and does not query Timetabler SQL while Timetabler applies it. This remains conditional on exception propagation, manual Kafka offsets, idempotent response receipts, post-commit external effects, deterministic locking, transaction timeouts, and measured engine-reservation/max-poll timing. It is not a zero-risk claim.
