## Context

Phase 2 starts only after Phase 1 provides atomic Timetabler writes, a durable TT-to-RB outbox, stable source identities/versions, adapter mirrors, reconciliation, and approved cross-service test evidence.

The verified Timetabler booking representation is native rather than a separate external-calendar model: `api/models/tt_activity_type.py::TtActivityType.booking` marks booking activity types and `api/models/tt_activity.py::TtActivity.is_booking` marks booking activities. Current UI endpoints create a booking activity in `api/views/admin/booking_create.py`, then `api/views/admin/booking_schedule.py` accepts a `type` of Staff or Location and mutates that resource relation independently. `api/views/admin/booking_swap.py` is another direct resource replacement path, and `api/views/admin/import_table.py::import_tt_booking()` can insert an already-scheduled Location booking. The RB integration must not orchestrate the create endpoint followed by separate Staff and Location calls because partial bookings would be externally visible.

The Phase 1 writer audit also identifies scheduled-resource replacement in `resources_update_requirement.py`, a routed simulation writer, a legacy schedule-response consumer, and cascade/pattern deletion paths. Phase 2 enforcement must cover each live path or prove it is decommissioned/blocked; a rarely used admin or legacy path is not an exception to conflict authority.

Resource Booking remains owner of the sync adapter and the shared reservation/availability authority. Timetabler exposes a minimal canonical command and validates reservation evidence; it does not absorb RB schema transformations, recurrence expansion, RB identity mapping, retry orchestration, or the reservation ledger.

## Cross-Repository Planning Baseline

The matching Resource Booking plan is `resource-booking-be` change `integrate-resource-booking-to-timetabler-phase-2` at commit `88c3cd5eaff085c70ab84f4e446b68268c87008b` (26 requirements, 72 scenarios, 109 tasks; strict validation passed). This Timetabler change uses that immutable commit as its planning counterpart. Later RB plan changes require an explicit compatibility review and an updated pin in `traceability.md`; branch names alone are not a contract version.

## Goals / Non-Goals

**Goals:**

- Apply each Resource Booking booking occurrence to one native Timetabler booking activity in real time with atomic Staff + Location + time/week visibility.
- Make create/update/cancel idempotent, monotonic, auditable, echo-safe, and recoverable after ambiguous outcomes.
- Prevent, rather than eventually discover, conflicts for every shared Staff/Location occurrence committed in Timetabler or Resource Booking.
- Reserve all resources/occurrences for a transaction or bulk engine result as one all-or-none authority operation, with deterministic idempotency and fencing.
- Keep network reservation acquisition outside Timetabler database transactions while validating the held reservation scope/expiry/fencing evidence immediately before commit.
- Fail closed when reservation authority is unavailable, timed out, expired, stale, or cannot prove the complete assignment.
- Cut over from Phase 1 mirror-only resources to bidirectional operation only after seeding mappings/ledger and proving zero conflicts/divergence.

**Non-Goals:**

- Starting Phase 2 production-code implementation before the Phase 1 hard gate passes.
- Allowing periodic bulk sync or reconciliation to be the primary transaction path.
- Allowing eventual conflict detection, partial bulk acceptance, partial Staff/Location visibility, or conflict-breaking modes to bypass the shared authority.
- Holding Timetabler database locks across an adapter/authority network request.
- Blind deletion or rollback of an RB/TT booking after an ambiguous timeout.
- Embedding RB recurrence, resource, or persistence schemas in Timetabler.
- Using Timetabler's existing split create/schedule endpoints as the adapter workflow.

## Hard Phase 1 Gate

Before any Phase 2 production code or migration is started, both service owners MUST verify and approve the versioned Phase 1 completion report required by `phase-1-outbound-resource-booking-sync` tasks 9.5-9.7. The report must show complete and passing Timetabler plus Resource Booking/adapter unit, integration, regression, failure-injection, concurrency, security, reconciliation, and performance testing, including engine reservation-window and Kafka max-poll evidence. If the report is missing, stale relative to the deployment commits, incomplete, or has a failed required suite, every Phase 2 implementation task remains blocked.

Resource Booking Phase 1 release `fc9ecf670d8a1a542cba8b3efe7d1b057779285d` and successful deployment run `31078563334` are recorded as partial evidence. The coordinated broker, end-to-end, race/load/soak, reconciliation/cutover, disaster-recovery, immutable-evidence, and named RB, Timetabler, QA, product, and operations approval gates remain incomplete. Protocol planning and artifact alignment may continue; production models, migrations, services, consumers, routes, or enablement changes may not.

## Decisions

### Transport contract: synchronous authority/status plus Kafka command/echo

The cross-service transport split is fixed by the matching Resource Booking plan:

- Resource Booking atomically records its booking state, authoritative fenced reservation, audit/workflow state, and a dedicated Timetabler command outbox in one RB transaction.
- The dedicated typed create/update/cancel command is delivered per occurrence over Kafka. It is distinct from generic RB lifecycle events, keyed by stable occurrence identity, consumed with `read_committed` isolation, and acknowledged/offset-committed only after a durable Timetabler outcome.
- Timetabler atomically applies the native booking, mapping/receipt, resource maps, audit state, ordinary Phase 1 final-state event, and required durable follow-up intent. That Phase 1 event returns over Kafka as the origin/causation-bound acknowledgement; adapter inbox matching advances the RB workflow without producing another command.
- Timetabler calls the RB-owned authority/status interface synchronously before a shared-resource commit for all-or-none reserve/status/renew/release decisions. Timetabler holds no database locks during that network call.
- Authenticated bounded status lookup resolves ambiguous command or authority outcomes without treating Kafka delay or a missing response as failure.

Exact topic names, schemas, headers, payload limits, partition/retention/DLQ/ACL values, internal authentication mechanism, endpoint paths, TTL and retry budgets remain explicit cross-service decision tasks. The selected interface roles are no longer open for an API-versus-consumer redesign.

### 1. Use one canonical occurrence command and one native booking activity

The adapter sends an authenticated idempotent command for `create`, `update`, or `cancel` of a single RB occurrence. One native `TtActivity` with `is_booking=1` and a configured `TtActivityType.booking=True` represents one occurrence initially. This avoids ambiguous recurrence semantics in the current Timetabler model. The adapter owns recurrence expansion before invoking Timetabler.

The canonical command includes:

- `source_system`, stable `source_ref`, stable `occurrence_ref`, operation/status, monotonic `source_version`, and deterministic payload hash/idempotency key;
- integration deployment/tenant, academic term/calendar reference, and correlation/causation IDs;
- start/end in UTC, local start/end plus IANA timezone and UTC offsets, duration, and occurrence date;
- canonical Timetabler source IDs for all Staff and Locations;
- reservation ID, deterministic reservation idempotency key, fencing token/version, scope hash, expiry/renewal metadata, and authority protocol version;
- actor/service identity, originating RB audit actor, committed/pending RB transaction reference, and audit timestamp.

The command is transported through the dedicated Kafka role/topic and applied by a Timetabler consumer. Timetabler exposes bounded authenticated receipt/status lookup for ambiguous delivery outcomes, while the RB-owned authority/status operations use the synchronous internal API. Exact topic/endpoint names, authentication mechanism, timeouts, and maximum sizes are explicit decision tasks; the semantic command and failure contract are shared fixtures across both repositories.

### 2. Persist a monotonic source mapping/receipt

A new model (planned under `api/models/`) uniquely identifies `(integration deployment/tenant, source_system, source_ref, occurrence_ref)` and records the current source version, payload hash, mapped `TtActivity`, status, origin, correlation/causation, last operation/outcome, reservation/fencing identity, and audit timestamps.

Rules:

- The same version/hash is an idempotent no-op that returns the recorded result.
- A lower version is rejected as stale without mutation.
- The same version with a different hash is rejected and alerted.
- A later update atomically replaces the entire prior Staff/Location/time/week allocation; it is not a patch exposing intermediate state.
- Cancellation is idempotent and tombstones/marks the mapping according to retention policy.
- A later create/update after cancellation follows an explicitly agreed reactivation/version policy.

### 3. Apply the booking through one atomic domain service

A new service under `api/services/booking/` reuses the native booking model but replaces integration orchestration through existing endpoints. It validates that the configured activity type has `booking=True`, resolves tenant/term/week/timezone/resources, and accepts the complete Staff + Location assignment at once.

After external reservation acquisition and command validation, it executes:

1. Open `transaction.atomic()`.
2. Lock the mapping/receipt, mapped activity (if any), affected old/new resources, weeks, and resource-map rows in deterministic ID order.
3. Recheck source version/hash and locally validate signed/opaque reservation evidence: protocol version, assignment scope hash, fencing token, tenant, and sufficient unexpired lease margin. The authority's hold guarantees exclusivity until expiry; no network call occurs under these locks.
4. Create or update exactly one native booking activity, its week/time/scheduled state, and all Staff/Location relations as one unit; or cancel it idempotently and release its resource-map occupancy.
5. Update receipt/mapping/version and audit state.
6. Recalculate Timetabler database resource maps.
7. Insert the ordinary Phase 1 TT outbound event with `origin=resource_booking`, causation/source identity, replacement scope, and durable acknowledgement/reservation follow-up metadata.
8. Commit, then trigger durable acknowledgement/confirmation work.

The current create-then-two-schedule endpoint sequence is never called by the adapter. Existing UI booking behavior is refactored to reuse this domain service where practical so native semantics stay compatible and partial resource visibility is removed.

### 4. Make the reservation authority the non-negotiable conflict decision

Before either system confirms a shared-resource allocation, the RB-owned authority atomically checks and holds the union of all Staff, Locations, occurrences, and half-open time ranges `[start, end)` as one operation. Its contract provides:

- `reserve(assignments, idempotency_key, expected_versions)` returning reservation ID, full scope hash, fencing token/version, lease expiry, renewal information, and conflict evidence;
- `confirm/consume(reservation, fencing_token, committed_refs)` idempotently binding committed TT/RB records;
- `renew(reservation, fencing_token)` when the agreed workflow can exceed safe TTL margin;
- `release(reservation, fencing_token, reason)` for failed/abandoned operations; and
- automatic `expire` with auditable state and fencing so a stale holder cannot later confirm.

Reservations are serialized/atomically checked across all resources and occurrences. A rejection identifies the conflicting reservation/booking using safe canonical evidence. Exact TTL, renewal threshold, transport, auth, clock-skew allowance, and authority storage are decision tasks, but all-or-none scope, exclusivity, fencing, and fail-closed behavior cannot be weakened.

The authority also governs RB-native booking writes. The invariant is: no confirmed shared-resource allocation exists in either system without a current authoritative reservation/fencing record covering its complete scope.

### 5. Acquire externally, commit locally, confirm durably

For a Timetabler-originated change:

1. Parse/build the complete proposed change set outside a DB transaction.
2. Send the canonical assignment to the RB-owned adapter/authority and acquire one all-or-none reservation before taking Timetabler locks.
3. Enter Timetabler's Phase 1 atomic application service and validate the reservation evidence/scope/fencing/lease margin immediately before commit without a network round trip under locks.
4. Commit TT state + Phase 1 outbox + durable reservation-confirm intent.
5. Confirm/consume the reservation after commit through durable delivery.
6. Commit/ack the originating API/consumer/engine response only according to the Phase 1 durable rules.

If Timetabler commit fails, release the reservation or allow it to expire safely. If confirmation fails after Timetabler commit, durable outbox retry/reconciliation confirms idempotently; the authority keeps the reservation/committed-pending-confirmation scope blocking conflicting new writes until resolved. Timetabler state is not rolled back or blindly deleted after an ambiguous timeout.

For an update, the new complete scope is reserved while the old committed scope remains protected; atomic TT replacement and authority confirmation transition protection without a gap. For cancellation, TT commits the cancellation/tombstone, then durable follow-up releases/marks the authoritative record; repeated cancellation is a no-op.

### 6. Reserve successful bulk engine assignments as one batch

Pre-schedule remains advisory. It should include current RB bookings/reservations in candidate availability through a defined adapter feed/query, but the mandatory authority check is repeated against the final complete engine assignment immediately before Timetabler commit.

For a bulk result, Timetabler builds the union of all occurrences for all successful (`start_slot` present) assignments and requests one atomic reservation. No-slot items remain unchanged. If any requested assignment conflicts, the authority rejects the entire successful batch and releases/creates no subset holds. Timetabler commits no subset. The adapter/orchestrator follows a bounded retry/reschedule policy or returns a complete rejection with conflict evidence to the user/engine workflow; exact retry counts are a decision task.

This protocol supersedes local constraint-breaking as a conflict bypass. Manual constraint-break may relax Timetabler scheduling preferences, but it cannot bypass shared-resource reservation.

### 7. Treat RB-originated outbound events as acknowledgements, not new work

An RB command still creates the ordinary TT outbound replacement event so Resource Booking mirrors, audit, and reconciliation see all Timetabler mutations uniformly. The event carries `origin=resource_booking`, `source_ref`, `occurrence_ref`, source version, correlation/causation, receipt identity, and reservation identity. The adapter inbox recognizes the matching source/version/change as acknowledgement/echo and updates its workflow state without issuing another TT command or duplicate RB booking.

### 8. Resolve ambiguous outcomes by status and reconciliation

The adapter tracks a durable workflow state machine across RB transaction, reservation, TT command/receipt, TT outbound acknowledgement, and reservation confirmation. On timeout it queries the Timetabler receipt/status and authority/RB state using stable identities and idempotency keys. It does not infer failure from timeout and does not blindly delete.

Key recovery cases:

- Reserve succeeds, TT has no receipt/commit: safely retry TT command or release/expire the reservation.
- TT commits, response/ack is lost: retry/query returns the mapping/version; confirm reservation idempotently.
- TT commits, confirmation is delayed: keep the ledger scope blocking, retry from durable outbox, and alert on age.
- RB transaction state is ambiguous: query RB and authority before compensation.
- Version/order conflict: reject stale work, retrieve current mapping, and reconcile the latest source state.
- Cancellation/update races: serialize on source mapping plus authority fencing; older fencing tokens cannot confirm or release newer scope.

### 9. Cut over only from a reconciled, paused baseline

Cutover order:

1. Verify the Phase 1 hard gate and freeze its approved builds/contracts.
2. Deploy additive receipt/schema, internal auth, adapter state machine, authority, and TT reservation client with enforcement disabled.
3. Pause shared-resource writes in both systems for the minimum agreed window.
4. Reconcile current TT/RB state; seed source mappings and authoritative reservation ledger for every confirmed allocation; resolve all conflicts and timezone/term mapping errors.
5. Verify zero conflicts and agreed zero/threshold divergence, exercise fail-closed and rollback controls, and synchronize clocks/TTL monitoring.
6. Atomically/coordinate-enable authority enforcement on RB and every Timetabler writer.
7. Enable RB-to-TT commands, validate end-to-end acknowledgements, then change Phase 1 mirror-only resources to bidirectional.
8. Resume writes gradually with race/lag/conflict dashboards watched by both owners.

Rollback after enforcement never simply disables the authority. Pause shared-resource writes, stop new ingress, keep the ledger enforcing existing scopes, drain/reconcile in-flight workflows, and either resume Phase 2 or deliberately return resources to Phase 1 mirror-only mode before writes resume. A rollback that permits uncoordinated TT and RB commits is prohibited.

## Authentication, Validation, and Audit

The internal command endpoint/consumer uses service authentication and authorization scoped to the configured integration tenant/environment, network policy, replay protection, request size/rate limits, and credential rotation. Exact mTLS, OAuth client credentials, signed message, or equivalent mechanism is a decision task. Timetabler validates tenant/term/resource ownership, canonical IDs, UTC/local consistency, IANA timezone, duration, slot alignment, booking activity type, command version/hash, and reservation evidence. Audit trails record the RB service and originating user/service actor without impersonating a local user.

## Observability and Configuration

New settings cover ingress enablement, auth/audience, authority endpoint/topic, request timeout, TTL/renewal/skew safety margins, enforcement mode, tenant/term/timezone mapping, retry policy, and cutover flags. Metrics/traces cover command rate/latency/outcomes, duplicate/stale/hash mismatch, mapping versions, reservation reserve/confirm/release/expire latency/state, conflicts by resource, fail-closed rejects, stale fencing tokens, TTL margin, post-commit confirmation age, echo suppression, workflow ambiguity, reconciliation divergence, and pre-schedule feed age.

## Risks / Trade-offs

- A network-owned authority adds availability dependency to shared writes; fail-closed safety intentionally favors integrity over availability.
- Lease/fencing correctness depends on clock discipline, sufficient safety margin, atomic authority storage, and comprehensive crash/race tests.
- Refactoring existing UI booking flows behind a complete atomic service may affect mature behavior; compatibility characterization and staged flags are required.
- One Timetabler activity per occurrence increases row/event volume but avoids ambiguous recurrence representation and simplifies idempotent updates/cancellations.
- Bulk all-or-none reservation can reject otherwise non-conflicting assignments when one item conflicts; this is the required consistency trade-off. Bounded rescheduling can recover capacity without silent partial commit.
- The post-commit confirm window requires the authority ledger to remain blocking during ambiguous state. Operational recovery is mandatory; eventual conflict discovery is never the fallback.

## Acceptance Criteria

- The Phase 1 completion/approval task is recorded complete before any Phase 2 implementation commit or migration begins.
- Authenticated create/update/cancel commands are idempotent; duplicate, stale, conflicting-hash, and out-of-order deliveries behave deterministically.
- One RB occurrence maps to one compatible native booking activity, and Staff + Location + time/week state is never partially visible.
- Updates atomically replace old allocation scope; cancellation is idempotent; audit and source mappings remain queryable.
- RB-originated TT outbound events are recognized as acknowledgements/echoes and do not loop.
- Every Timetabler and Resource Booking shared allocation writer fails closed without valid full-scope reservation/fencing evidence.
- Final engine assignments are rechecked after advisory pre-schedule; successful bulk assignments reserve/commit all-or-none.
- TT-vs-RB, TT-vs-TT, RB-vs-RB, stale fencing, timeout, crash, retry, timezone/DST, recurrence expansion, and load/TTL races cannot create conflicting confirmed allocations.
- Ambiguous outcomes resolve through receipt/status/reconciliation without blind deletion.
- Cutover seeds mappings/ledger from a paused reconciled baseline, proves zero conflicts, enables both sides before bidirectional writes, and has a tested fail-closed rollback.
