venue hierarchy steps 6-7 #2

Closed
Somehuman wants to merge 0 commits from venue-hierarchy-steps-6-7 into main
Owner

API ref update; direct push rejected.

API ref update; direct push rejected.
Step 6 (docs/specs/2026-08-18): places.{rooms,calendars,availability}
procedures with output schemas + strict inputs (availability is full-PUT
— isOpen required); getCalendarEvents split admin/public with the mode
server-pinned; bookingRequests.messages.{post,list,markRead} (post
rate-limited 30/h per actor, proven to write nothing on exhaustion) +
attachments.{add,list,delete} with storageKey claim-once (interim guard
until the Step-8 upload mint); notify composed at the router layer
mirroring the received-notification exactly. Embed subjectType
"calendar": authz via the calendar's place across upsert/list/revoke,
widget payloads structurally leak-free (serialized-payload pins),
checkoutEligible false fail-closed. Public read collapses place-gate
codes to CALENDAR_NOT_FOUND (no existence oracle). ROOM_NOT_FOUND /
CALENDAR_NOT_FOUND / CANNOT_RETIRE_LAST_ACTIVE_ROOM /
CALENDAR_SLUG_TAKEN promoted to USER_FACING with en+es copy (parity
gates green). Hold layer untouched (deferred).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
b7c80321 landed the five ledger-cutover blockers, but the file set was grown by
typechecking a clean tree until it closed — which is a compiler-shaped boundary,
not a behaviour-shaped one. These eight files are what that boundary left behind:
each one is load-bearing at runtime and invisible to `tsc`.

1. **The resale claim refund was outside the refund mutex.** b7c80321 wrapped
   refund-full-order, refund-order-items, cancel-event's per-order loop and both
   reconcile arms, so `refund-op:{orderId}` looked complete — but
   `confirmApprovedClaimRefund` refunds a RESALE order and acquired nothing. A
   buyer refund and an approved-claim refund on the same order could therefore
   both pass their pre-checks, both call Stripe, and both commit: the exact
   double-refund the mutex exists to prevent, through the one door left open.
   The mutex now spans the Stripe refund AND the mark-refunded tx; a conflict
   rides the existing reviewApprovedClaim catch, so the claim's own idempotency
   record is failed and an admin retries via allowApprovedResume.

2. **`markItemsPaidOut` restamped history on every heal replay.** The payout
   batch's ledger-heal sweep legitimately replays the stamp for an item whose
   other ledger effects were lost to a crash; without a `paidOutAt: null` guard
   the replay rewrote the record of when money actually moved to tonight. Now
   write-once, with the port doc stating the semantics and why they matter (the
   stamp gates whether a later refund attempts a Stripe transfer reversal — a
   lost stamp silently skips the clawback).

3. **A regression pin for the composition blocker.** The starvation fix itself
   (headroom-paired reversal credits) is already in b7c80321; what was missing is
   the test that holds it there. cancel-event releases its mutex before its batch
   ledger tx, so the `charge.refunded` webhook its own refund fires can commit
   debits in that gap — after which cancel's tx found nothing left to debit and
   never wrote the payee's TRANSFER_REVERSAL_CREDIT, billing them for money they
   had already returned. The new test drives the real reconcile use case and the
   real writers through that exact interleaving and asserts the credit survives.

4/5. `NoopIdempotency.release` in the transcoder container (fourth implementor of
   the owner-checked release primitive), and a PROCESS SCOPE note on the e2e
   run-batch route: its claims live under the `api` idempotency namespace and are
   invisible to the jobs-process cron, so an item it abandons mid-flight is not
   recoverable by the nightly sweep. Money is safe either way (Stripe keys and
   PAYOUT-entry uniqueness are namespace-blind); the note exists so the pattern
   is not copied into a prod path.

Verified in a clean worktree at cfd93666 with ONLY these eight files applied, so
none of it is propped up by the ~150 other uncommitted files in this checkout:
`pnpm typecheck` plus apps/{api,jobs} `tsc -p tsconfig.json --noEmit` all clean;
@th/core 610 files / 7677 tests, the adapters money+infra suites 12 files / 133
tests, and the two new specs green.

Deliberately excluded: the memberships/promos, entity-reports, venue-rooms and
CAB workstreams are also uncommitted here. Notably packages/errors/src/codes.ts
and transport/trpc/src/utils.ts are dirty with THEIR hunks, not mine — my error
code shipped in b7c80321, so staging those files would have swept their work.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Step 7 (docs/specs/2026-08-18): /admin/[slug]/places/[placeId]/calendars —
calendar list (default badge, read-only slug, room chips, no-active-rooms
warning), create/edit with room assignment, and a rooms section where
retire is disabled-with-reason on the last ACTIVE room (server error still
surfaces if raced). The place-level hours/blackout editors are ABSORBED and
re-targeted to Calendar per FR-007: full-PUT (isOpen always sent), dayOfWeek
XOR date, scope immutable, and calendarId null = a single place-wide row
presented as "All calendars" (chip-marked when viewed inside a calendar).
Old editors deleted and unwired from the place profile page. Driven at 1440
and 320: zero console errors, scrollWidth == clientWidth. Two 320px overflow
defects found and fixed during the drive — the hours column's 0%-basis flex,
and a note field whose containerWidth prop belongs to AmountInput and was
silently ignored by LabeledTextInput (dead prop removed at all 5 sites).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Somehuman force-pushed venue-hierarchy-steps-6-7 from 6d921d6ca8 to d0bd5126a7
Some checks failed
CI / build (pull_request) Failing after 34m53s
Deploy dev / Detect changed services (push) Successful in 24s
Deploy dev / CI coverage guard (push) Successful in 51s
Deploy dev / Server-side quality gate (push) Failing after 25m53s
Deploy dev / Build + push web:dev (push) Has been skipped
Deploy dev / Seed platform admin (dev) (push) Has been skipped
Deploy dev / Post-deploy verification (push) Has been skipped
Deploy dev / Notify deploy result (push) Has been skipped
Deploy dev / Build + push api:dev (push) Has been skipped
Deploy dev / Build + push tileserver:dev (push) Has been skipped
Deploy dev / Build + push jobs:dev (push) Has been skipped
Deploy dev / Build + push audio-transcoder:dev (push) Has been skipped
Deploy dev / Run Prisma migrations (dev) (push) Has been skipped
Deploy dev / Terraform apply (dev) (push) Has been skipped
Deploy dev / Deploy dev (roll Cloud Run) (push) Has been skipped
CI / secret-scan (pull_request) Successful in 5m39s
2026-08-19 17:38:20 +00:00
Compare
Somehuman closed this pull request 2026-08-20 17:44:31 +00:00
Some checks failed
CI / build (pull_request) Failing after 34m53s
Deploy dev / Detect changed services (push) Successful in 24s
Deploy dev / CI coverage guard (push) Successful in 51s
Deploy dev / Server-side quality gate (push) Failing after 25m53s
Deploy dev / Build + push web:dev (push) Has been skipped
Deploy dev / Seed platform admin (dev) (push) Has been skipped
Deploy dev / Post-deploy verification (push) Has been skipped
Deploy dev / Notify deploy result (push) Has been skipped
Deploy dev / Build + push api:dev (push) Has been skipped
Deploy dev / Build + push tileserver:dev (push) Has been skipped
Deploy dev / Build + push jobs:dev (push) Has been skipped
Deploy dev / Build + push audio-transcoder:dev (push) Has been skipped
Deploy dev / Run Prisma migrations (dev) (push) Has been skipped
Deploy dev / Terraform apply (dev) (push) Has been skipped
Deploy dev / Deploy dev (roll Cloud Run) (push) Has been skipped
CI / secret-scan (pull_request) Successful in 5m39s

Pull request closed

Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
Somehuman/hearthfire!2
No description provided.