Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions BunkFy.slnx
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,8 @@
<File Path="docs/planning/reservations-mutation-serialization-task.md" />
<File Path="docs/planning/reservations-operational-surface-hardening-task.md" />
<File Path="docs/planning/reservations-pii-minimization-task.md" />
<File Path="docs/planning/reservations-property-operations-snapshot-task.md" />
<File Path="docs/planning/reservations-stay-amendment-convergence-task.md" />
<File Path="docs/planning/reservations-stay-lifecycle-task.md" />
<File Path="docs/planning/reservations-tenant-termination-owner-task.md" />
<File Path="docs/planning/retention-mutation-serialization-task.md" />
Expand Down
2 changes: 2 additions & 0 deletions docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,7 @@ Useful entry points:
- [Reservations Operational Surface Hardening Task](planning/reservations-operational-surface-hardening-task.md)
- [Reservations Mutation Serialization Task](planning/reservations-mutation-serialization-task.md)
- [Reservations Aggregate Query Shape Task](planning/reservations-aggregate-query-shape-task.md)
- [Reservations Property Operations Snapshot Task](planning/reservations-property-operations-snapshot-task.md)
- [Reservations Correction Assurance Task](planning/reservations-correction-assurance-task.md)
- [Reservations Management Create Idempotency Task](planning/reservations-management-create-idempotency-task.md)
- [Reservations Management Lifecycle Idempotency Task](planning/reservations-management-lifecycle-idempotency-task.md)
Expand All @@ -140,6 +141,7 @@ Useful entry points:
- [Reservation Guest Record Convergence Task](planning/reservation-guest-record-convergence-task.md)
- [Reservation Guest Record Resume Attribution Task](planning/reservation-guest-record-resume-attribution-task.md)
- [Reservations Management Inventory Amendment Replay Task](planning/reservations-management-inventory-amendment-replay-task.md)
- [Reservations Stay Amendment Convergence Task](planning/reservations-stay-amendment-convergence-task.md)
- [Reservations Data Rights Workflow Task](planning/reservations-data-rights-workflow-task.md)
- [Reservation Record Retention Task](planning/reservation-record-retention-task.md)
- [Executable Personal-Data Catalogue Task](planning/executable-personal-data-catalog-task.md)
Expand Down
82 changes: 78 additions & 4 deletions docs/planning/reservation-record-retention-task.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# Reservation Record Retention Task

Status: published; implementation and exact-candidate gates complete
Status: published baseline; current retry and scale hardening is local and
unpublished

## Outcome

Expand Down Expand Up @@ -91,7 +92,7 @@ guest links.

It writes:

- an append-only retention execution and fair-scan checkpoint;
- a durable, versioned retention execution and fair-scan checkpoint;
- an append-only retention anonymisation receipt containing only owner-local
coordinates, selected/resulting versions, terminal trigger, deadline,
policy digest, reduction counts, event id, actor, and completion time;
Expand All @@ -104,13 +105,78 @@ automatic retention decision cannot be restored through a rights-case replay.
The retention receipt raises the existing reservation-anonymised event so
owner projections and downstream module contracts remain coherent.

### Attempt fencing and failed-attempt recovery

Task Runtime and Retention use two deliberately different counters. Task
Runtime `Attempt` remains the per-run retry-budget counter and restarts when an
operator invokes `RetryAsync`. The persisted, monotonic `LeaseGeneration`
survives that operation and is the value Retention stores and forwards through
its execution and owner-contract field named `Attempt`. Reservations therefore
fences owner work to a lease generation, not to the resettable Task Runtime
budget counter.

If lease reclaim reaches a central Retention execution that is already
`Completed` or `Blocked`, Retention replays that terminal aggregate without
redispatching Reservations. If Reservations committed terminal owner evidence
before the worker committed central completion, a newer lease generation opens
a forward-only central recovery window and redispatches only to obtain the
owner's exact replay. The Reservations execution and receipt remain unchanged;
when their completion predates the new central start, only the central result's
completion time is normalized to that new start before central persistence.
This preserves immutable owner proof while satisfying the new central window.

The Retention request attempt is carried through every owner mutation and the
owner completion command. A mutation is admitted only when the matching
Reservations execution is `Running` for the same tenant, data class,
execution-policy version, and exact attempt. Completion is likewise fenced to
the active attempt. A late worker from an earlier attempt can therefore neither
anonymise another record nor terminalize or advance the cursor of a newer
attempt.

`Failed` is terminal evidence for the current attempt. Replaying that same
attempt returns the persisted status, counts, outcome code, completion time,
and hold timestamp exactly; correcting the underlying condition does not
silently reopen it. Recovery requires a strictly higher lease generation,
exposed to the owner as a new attempt, and its start cannot precede the prior
failed completion. Starting that attempt retains the cumulative affected count,
clears the current-attempt terminal result, and starts from the failed
execution's original cursor.
Already-applied records are recognized through their retention receipt and
tombstone, so the required rescan does not duplicate mutation or proof.

A newly failed completion never advances the fair-scan checkpoint. The retry
therefore normally observes the unchanged starting ordinal and leaves the
checkpoint untouched until a non-failed completion succeeds. Compatibility
recovery for a legacy row that advanced on failure is deliberately narrow: the
execution must still be `Failed`; tenant, data class, and policy version must
match; retry time must be at or after the persisted completion; the checkpoint
must name that exact execution; and its update timestamp must equal that exact
completion timestamp. Only then is it rewound to the execution's starting
ordinal and disassociated from the failed execution. A checkpoint belonging to
another execution, carrying a different timestamp, or otherwise newer or
ambiguous fails closed.

Owner mutation and completion timestamps are converted to UTC and truncated to
PostgreSQL's microsecond precision before persistence and response creation.
This includes non-UTC clocks and sub-microsecond .NET ticks, so the first result,
the durable execution/receipt, and exact replay expose the same completion
instant.

### Fairness and efficiency

- Scan the indexed, non-anonymised terminal set by monotonic
`ProjectionOrdinal`.
- Persist one cursor per tenant, data class, and execution-policy version.
- Read at most `ScanSize + 1` candidate heads and load bounded related facts
with set-based queries.
- Validate `ScanSize` independently at configuration and repository boundaries
as 1 through 1,000. Read at most `ScanSize + 1` candidate heads for end
detection, but fully materialize at most `ScanSize` candidates and load their
related facts with set-based queries.
- Materialize at most 64 governance acknowledgements per property from one
coherent, bounded policy statement. A 65th row visible to that statement
withholds the property's governance policy and produces a fail-closed
policy-unavailable result. A later policy change is observed by the
mutation-time reload or the next scan rather than mixed into the earlier
snapshot.
- Mutate at most `MutationBatchSize` records per occurrence.
- Reset the cursor only after reaching the end, so a large tenant cannot starve
later records and newly terminal earlier ordinals are picked up on the next
Expand Down Expand Up @@ -138,6 +204,14 @@ owner projections and downstream module contracts remain coherent.

## Evidence

- The current local hardening pass is not hosted or deployment evidence.
Focused retention tests pass 42/42, the complete Reservations and Retention
unit suites pass 365/365 and 50/50, and the strict touched-project builds
have zero warnings or errors. The exact PostgreSQL provider scenarios pass
2/2; the consolidated Reservations Docker admission passes 25/25; and both
saga facts pass twice consecutively. The post-rebase repository matrix,
hosted checks, and deployed proof remain separate release gates for this
amendment.
- Focused Reservations retention tests pass 24/24, including domain,
eligibility, contributor, mutation, policy-version cursor, append-only
receipt, tenant boundary, and model constraints.
Expand Down
4 changes: 3 additions & 1 deletion docs/planning/reservations-data-rights-workflow-task.md
Original file line number Diff line number Diff line change
Expand Up @@ -247,7 +247,9 @@ ungated.
2. [Complete] Add approved transactional correction and immutable owner
receipts through existing aggregate/history semantics.
3. [Complete] Add Reservations-owned processing restriction, enforcement
and rebuildable PII-free state.
and rebuildable PII-free state; register the Reservations restriction owner
with bounded exact release-target discovery, actor-bound proof replay, and
exactly-one legacy unbound recovery.
4. [Complete] Add holds and fail-closed destructive eligibility.
5. [Complete] Add irreversible aggregate/history/link/receipt redaction, owner
proof and terminal ordinary-surface enforcement.
Expand Down
131 changes: 131 additions & 0 deletions docs/planning/reservations-property-operations-snapshot-task.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
# Reservations Property Operations Snapshot Task

Status: implemented locally; deployment evidence is not part of this task

## Goal

Provide one authoritative Reservations read for the current operational state of
one property, coordinated by a property-local calendar date. The response gives
front-desk operators exact Reservation and guest counts, a disjoint attention
breakdown, and a small deterministic upcoming-arrivals list without requiring
clients to download and recompute the reservation directory.

## Ownership and Boundaries

- Reservations owns the read model, cohort definitions, ordinary-record
visibility rules, authorization contract, and response minimization.
- Properties remains authoritative for property lifecycle and IANA time-zone
data. Reservations uses its local property projection and fails closed when
that projection is missing, inactive, or has an invalid time zone.
- Inventory remains authoritative for allocation and release. In particular,
`CheckoutPending` remains physically in house until Inventory release
succeeds.
- The snapshot performs no cross-module runtime calls and introduces no
persistence migration.

## Calendar and Observation Contract

The handler captures `ObservedAtUtc` once. By default, the reader derives
`LocalDate` from that instant and the valid projected IANA `TimeZoneId`, and
returns `DateSource=PropertyTimeZone`. A caller may select an explicit
`localDate`, which returns `DateSource=Explicit`, but the property must still be
known, active, and have a valid projected IANA time zone.

An explicit past or future date changes only the calendar predicates evaluated
against current reservation state. It is not a historical as-of reconstruction.
`CurrentlyInHouse` is always a current-state cohort. Property, Guest restriction,
and reservation changes can be briefly delayed by projection lag; `ObservedAtUtc`
identifies when the snapshot was evaluated, not when every upstream fact was
committed.

## Exact Cohorts

Every count reports both `ReservationCount` and the exact sum of current
`GuestCount` values.

- `ConfirmedArrivalsOnLocalDate`: `Confirmed` and arrival equals `LocalDate`.
- `ScheduledDeparturesOnLocalDate`: `CheckedIn` or `CheckoutPending` and
departure equals `LocalDate`.
- `CurrentlyInHouse`: `CheckedIn` or `CheckoutPending`.

The top-level cohorts are not mutually exclusive. A scheduled departure can be
currently in house, and a currently-in-house reservation can also need
attention.

Attention is an exact total of seven mutually exclusive categories:

- `PendingAllocation`
- `AllocationRejected`
- `CancellationPending`
- `NoShowPending`
- `CheckoutPending`
- `ArrivalBeforeLocalDateStillConfirmed`
- `DepartureBeforeLocalDateStillInHouse`

The date-relative category names are intentionally descriptive. With an
explicit past or future date they do not assert that an item is operationally
overdue.

## Upcoming Contract

Upcoming items include only `PendingAllocation` and `Confirmed` reservations
whose arrival is on or after `LocalDate`. They sort by arrival, then non-null
expected arrival time before null, then expected arrival time, then reservation
id. The default limit is 25 and the strict range is 0 through 50. A zero limit
returns counts only while `HasMoreUpcoming` still reports whether any matching
item exists. The reader uses one-item lookahead and never returns more than the
echoed `UpcomingLimit`.

Upcoming rows reuse the ordinary minimized reservation list DTO, including its
catalogued `PrimaryGuestName`. Anonymised reservations and reservations without
a current supported, unrestricted Guest processing-restriction projection are
excluded by the same fail-closed rule as ordinary directory reads.

## Surfaces and Failures

- Public API:
`GET /api/reservations/properties/{propertyId}/operations-snapshot`, tenant
and property scoped with `reservations.read`.
- Admin API:
`GET /api/admin/reservations/properties/{propertyId}/operations-snapshot`,
operation `reservations.operations-snapshot`, `reservations.read`, and the
host's property resource scope.
- Admin CLI:
`reservations operations-snapshot --property-id <id>
[--local-date yyyy-MM-dd] [--upcoming-limit 0..50]` with equivalent
authorization, audit, and property resource scope.

All responses on the exact public and Admin Reservations path boundaries,
including authentication, authorization, and binding failures before endpoint
execution, receive `Cache-Control: no-store`, `Pragma: no-cache`, and
`Expires: 0`.

Stable failures are `Reservations.PropertyNotFound` (404),
`Reservations.PropertyInactive` (409),
`Reservations.PropertyTimeZoneUnavailable` (503 when the projected time-zone
identifier is missing, invalid, or not an IANA identifier), and
`Reservations.OperationsSnapshotLimitInvalid` (400). The CLI also returns
`Reservations.OperationsSnapshotLocalDateInvalid` for a non-ISO date.

## Scale Contract

This endpoint is an authoritative property-local operational read, not a fleet
aggregate. A regional or fleet dashboard must use a dedicated bounded rollup or
projection. It must not fan out this personal-data response across hundreds of
properties or widen this endpoint into an unbounded multi-property export.

## Verification

- Contract and handler tests freeze field names, limits, observation capture,
and stable error mapping.
- Reader tests prove time-zone validation, exact cohorts and disjoint attention
totals, ordinary-record visibility, deterministic lookahead, and limit zero.
- API surface tests freeze public property permission, Admin parity, and
no-store headers for 200, 400, 401, and 403 pipeline outcomes.
- Admin CLI tests cover the real executor, permission, property resource scope,
audit operation, JSON parity, the complete seven-category table breakdown,
and guest counts in upcoming rows.
- Personal-data catalogue v20 independently classifies public and Admin inputs,
public and Admin outputs, the application query, every snapshot wrapper, and
the nested ordinary upcoming DTO under the support boundary; the generated
inventory is checked in and deterministically verified.
Loading