@topy-ai/maggie 0.7.36 → 0.7.38

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,68 @@
1
+ {
2
+ "$id": "https://maggiedash.topy.ai/contracts/stripe-booking-capabilities-v1.json",
3
+ "schemaVersion": "maggiedash-stripe-booking-capabilities.v1",
4
+ "provider": "stripe",
5
+ "scope": "booking-and-business-operations",
6
+ "x-source": "MaggieDash/contracts/stripe-booking-capabilities-v1.json",
7
+ "x-last-verified-at": "2026-09-20",
8
+ "x-api-version-policy": {
9
+ "accountScoped": true,
10
+ "pinServerVersion": true,
11
+ "upgradeInSandbox": true,
12
+ "avoidChargesForNewFlows": true
13
+ },
14
+ "x-api-matrix": [
15
+ { "id": "customers", "status": "core", "resource": "Customer", "operations": ["POST /v1/customers", "GET /v1/customers/:id", "POST /v1/customers/:id"], "bookingUse": "customer mapping and receipts", "officialDocs": ["https://docs.stripe.com/api/customers"] },
16
+ { "id": "checkout_sessions", "status": "core", "resource": "Checkout Session", "operations": ["POST /v1/checkout/sessions", "GET /v1/checkout/sessions/:id", "POST /v1/checkout/sessions/:id/expire"], "bookingUse": "hosted or embedded one-off booking payments", "officialDocs": ["https://docs.stripe.com/api/checkout/sessions"] },
17
+ { "id": "payment_intents", "status": "core", "resource": "PaymentIntent", "operations": ["POST /v1/payment_intents", "GET /v1/payment_intents/:id", "POST /v1/payment_intents/:id/confirm", "POST /v1/payment_intents/:id/cancel", "POST /v1/payment_intents/:id/capture"], "bookingUse": "custom payment UI, SCA, capture, reconciliation", "officialDocs": ["https://docs.stripe.com/api/payment_intents"] },
18
+ { "id": "setup_intents", "status": "optional", "resource": "SetupIntent", "operations": ["POST /v1/setup_intents", "GET /v1/setup_intents/:id", "POST /v1/setup_intents/:id/confirm"], "bookingUse": "consented saved payment methods", "officialDocs": ["https://docs.stripe.com/api/setup_intents"] },
19
+ { "id": "refunds", "status": "core", "resource": "Refund", "operations": ["POST /v1/refunds", "GET /v1/refunds/:id", "POST /v1/refunds/:id/cancel"], "bookingUse": "full and partial refunds", "officialDocs": ["https://docs.stripe.com/api/refunds"] },
20
+ { "id": "events", "status": "core", "resource": "Event", "operations": ["GET /v1/events/:id", "GET /v1/events"], "bookingUse": "event identity and reconciliation", "officialDocs": ["https://docs.stripe.com/api/events", "https://docs.stripe.com/api/events/types"] },
21
+ { "id": "promotion_codes", "status": "optional", "resource": "Promotion Code", "operations": ["POST /v1/promotion_codes", "GET /v1/promotion_codes/:id", "GET /v1/promotion_codes"], "bookingUse": "campaign and referral discount codes", "officialDocs": ["https://docs.stripe.com/api/promotion_codes"] },
22
+ { "id": "coupons", "status": "optional", "resource": "Coupon", "operations": ["POST /v1/coupons", "GET /v1/coupons/:id", "DELETE /v1/coupons/:id"], "bookingUse": "reusable discounts", "officialDocs": ["https://docs.stripe.com/api/coupons"] },
23
+ { "id": "customer_balance_transactions", "status": "optional", "resource": "Customer Balance Transaction", "operations": ["POST /v1/customers/:id/balance_transactions", "GET /v1/customers/:id/balance_transactions"], "bookingUse": "invoice credit, not gift-card redemption", "officialDocs": ["https://docs.stripe.com/api/customer_balance_transactions"] },
24
+ { "id": "subscriptions", "status": "future", "resource": "Subscription", "operations": ["POST /v1/subscriptions", "GET /v1/subscriptions/:id", "POST /v1/subscriptions/:id"], "bookingUse": "future memberships", "officialDocs": ["https://docs.stripe.com/api/subscriptions"] },
25
+ { "id": "invoices", "status": "future", "resource": "Invoice", "operations": ["POST /v1/invoices", "GET /v1/invoices/:id", "POST /v1/invoices/:id/finalize"], "bookingUse": "future business invoicing", "officialDocs": ["https://docs.stripe.com/api/invoices"] },
26
+ { "id": "customer_portal_sessions", "status": "future", "resource": "Billing Portal Session", "operations": ["POST /v1/billing_portal/sessions"], "bookingUse": "future customer self-service billing", "officialDocs": ["https://docs.stripe.com/api/customer_portal/sessions"] },
27
+ { "id": "tax", "status": "optional", "resource": "Tax Calculation/Transaction", "operations": ["POST /v1/tax/calculations", "POST /v1/tax/transactions/create_from_calculation", "POST /v1/tax/transactions/create_reversal"], "bookingUse": "optional VAT/tax calculation", "officialDocs": ["https://docs.stripe.com/tax/payment_intent", "https://docs.stripe.com/api/tax/transactions"] },
28
+ { "id": "terminal", "status": "future", "resource": "Terminal PaymentIntent", "operations": ["POST /v1/terminal/readers", "POST /v1/terminal/readers/:id/process_payment_intent"], "bookingUse": "future in-person payments", "officialDocs": ["https://docs.stripe.com/terminal/payments"] },
29
+ { "id": "connect", "status": "future", "resource": "Connected Account", "operations": ["POST /v1/accounts", "POST /v1/account_links", "POST /v1/transfers"], "bookingUse": "future multi-merchant platforms only", "officialDocs": ["https://docs.stripe.com/connect"] }
30
+ ],
31
+ "x-webhook-events": [
32
+ { "eventType": "checkout.session.completed", "requiredFor": "hosted checkout completion", "officialDocs": ["https://docs.stripe.com/api/events/types"] },
33
+ { "eventType": "checkout.session.async_payment_succeeded", "requiredFor": "delayed payment completion", "officialDocs": ["https://docs.stripe.com/api/events/types"] },
34
+ { "eventType": "checkout.session.async_payment_failed", "requiredFor": "delayed payment failure", "officialDocs": ["https://docs.stripe.com/api/events/types"] },
35
+ { "eventType": "payment_intent.processing", "requiredFor": "payment pending", "officialDocs": ["https://docs.stripe.com/api/events/types"] },
36
+ { "eventType": "payment_intent.payment_failed", "requiredFor": "payment failure", "officialDocs": ["https://docs.stripe.com/api/events/types"] },
37
+ { "eventType": "payment_intent.succeeded", "requiredFor": "payment confirmation", "officialDocs": ["https://docs.stripe.com/api/events/types"] },
38
+ { "eventType": "payment_intent.requires_action", "requiredFor": "customer action", "officialDocs": ["https://docs.stripe.com/api/events/types"] },
39
+ { "eventType": "payment_intent.canceled", "requiredFor": "payment cancellation", "officialDocs": ["https://docs.stripe.com/api/events/types"] },
40
+ { "eventType": "refund.created", "requiredFor": "refund projection", "officialDocs": ["https://docs.stripe.com/api/events/types"] },
41
+ { "eventType": "refund.updated", "requiredFor": "refund status changes", "officialDocs": ["https://docs.stripe.com/api/events/types"] },
42
+ { "eventType": "refund.failed", "requiredFor": "refund failure", "officialDocs": ["https://docs.stripe.com/api/events/types"] },
43
+ { "eventType": "setup_intent.succeeded", "requiredFor": "saved method setup", "officialDocs": ["https://docs.stripe.com/api/events/types"] },
44
+ { "eventType": "setup_intent.setup_failed", "requiredFor": "saved method failure", "officialDocs": ["https://docs.stripe.com/api/events/types"] },
45
+ { "eventType": "invoice.paid", "requiredFor": "future recurring billing", "officialDocs": ["https://docs.stripe.com/api/events/types"] },
46
+ { "eventType": "invoice.payment_failed", "requiredFor": "future recurring billing failure", "officialDocs": ["https://docs.stripe.com/api/events/types"] },
47
+ { "eventType": "customer.subscription.updated", "requiredFor": "future membership state", "officialDocs": ["https://docs.stripe.com/api/events/types"] },
48
+ { "eventType": "customer.subscription.deleted", "requiredFor": "future membership cancellation", "officialDocs": ["https://docs.stripe.com/api/events/types"] }
49
+ ],
50
+ "x-native-capabilities": {
51
+ "giftCards": "not-native",
52
+ "referrals": "not-native",
53
+ "discountCodes": "native",
54
+ "customerBalanceCredits": "native",
55
+ "subscriptions": "native",
56
+ "tax": "native",
57
+ "terminal": "native",
58
+ "connect": "native"
59
+ },
60
+ "x-ecosystem-integrations": [
61
+ { "id": "gift-up", "category": "gift-cards", "kind": "stripe-app", "status": "evaluate", "officialDocs": ["https://marketplace.stripe.com/apps/gift-up"], "maggieDecision": "separate partner gift-card system; retain local reference and reconciliation" },
62
+ { "id": "partnerstack", "category": "affiliate-and-referrals", "kind": "stripe-app", "status": "evaluate", "officialDocs": ["https://marketplace.stripe.com/apps/partnerstack"], "maggieDecision": "affiliate payout candidate; local referral attribution remains authoritative" },
63
+ { "id": "stripe-apps", "category": "business-operations", "kind": "stripe-product", "status": "recommended", "officialDocs": ["https://stripe.com/apps"], "maggieDecision": "prefer reviewed apps for accounting, CRM, support, analytics, and automation" },
64
+ { "id": "stripe-tax", "category": "tax", "kind": "stripe-product", "status": "evaluate", "officialDocs": ["https://docs.stripe.com/tax"], "maggieDecision": "enable only after jurisdiction and tax ownership review" },
65
+ { "id": "stripe-terminal", "category": "in-person-payments", "kind": "stripe-product", "status": "future", "officialDocs": ["https://docs.stripe.com/terminal"], "maggieDecision": "future reader operations" },
66
+ { "id": "stripe-connect", "category": "multi-merchant", "kind": "stripe-product", "status": "future", "officialDocs": ["https://docs.stripe.com/connect"], "maggieDecision": "future platform-only capability" }
67
+ ]
68
+ }
@@ -6,6 +6,7 @@ Skills are the agent-facing workflows. They compose with the tools in
6
6
  | Skill | Primary job | Dependencies |
7
7
  |---|---|---|
8
8
  | `maggie-blog-bootstrap` | Build a complete blog in an existing project | site audit, analytics, API Pull |
9
+ | `maggie-booking` | Install and validate the MaggieDash manager workspace, public customer-flow boundary, lifecycle, payment, notification, and runtime gates | MaggieDash Booking contracts, host database/provider adapter |
9
10
  | `maggie-dash` | Initialize and operate the provider-neutral MaggieDash foundation, content store, and approval lifecycle | MaggieDash contracts, project adapter |
10
11
  | `maggie-clone` | Reverse-engineer authorized URLs into namespaced blog-project pages | browser MCP, clone planner CLI, bootstrap state |
11
12
  | `maggie-clone-to-template` | Turn an authorized URL and prompt into a validated marketplace package | marketplace CLI, screenshot QA, design contract |
@@ -14,6 +14,10 @@
14
14
  "name": "maggie-blog-bootstrap",
15
15
  "description": "Establish a MaggieDash-backed Astro blog or complete a small SEO/GEO-ready blog in an existing project."
16
16
  },
17
+ {
18
+ "name": "maggie-booking",
19
+ "description": "Plan, install, validate, and release the MaggieDash provider-neutral Booking workspace at _maggie/booking. Use for manager booking operations, availability/hold contracts, payment reconciliation, or integrating a host adapter."
20
+ },
17
21
  {
18
22
  "name": "maggie-clone",
19
23
  "description": "Reverse-engineer an authorized website homepage and create the Maggie homepage foundation inside an existing blog project. Use with /maggie-clone plus a homepage URL when the user wants to replicate the homepage structure, header, footer, assets, responsive behavior, and interactions."
@@ -0,0 +1,464 @@
1
+ ---
2
+ name: maggie-booking
3
+ description: Plan, install, validate, and release the MaggieDash provider-neutral Booking workspace at _maggie/booking. Use for manager booking operations, availability/hold contracts, payment reconciliation, or integrating a host adapter.
4
+ ---
5
+
6
+ # Maggie Booking
7
+
8
+ Use this skill for the MaggieDash transactional Booking workspace, not only
9
+ for importing a provider's service catalogue. The existing
10
+ [`maggie-service-booking`](../maggie-service-booking/SKILL.md) skill remains the
11
+ right choice for provider-backed services, prices, durations, booking links,
12
+ and factual provider validation.
13
+
14
+ ## Contract and safety boundary
15
+
16
+ Read [`../../docs/maggiedash-booking/README.md`](../../docs/maggiedash-booking/README.md),
17
+ [`TASKS.md`](../../docs/maggiedash-booking/TASKS.md), and the task's linked
18
+ reference before changing code. MaggieDash owns the manager UI and versioned
19
+ contracts. The host owns routes, traditional email/password sessions,
20
+ PostgreSQL connection/transaction lifetime, tenant authorization, provider
21
+ credentials, payment webhooks, notifications, and deployment. MaggieDash also
22
+ ships a tenant-scoped PostgreSQL manager repository for catalogue, availability,
23
+ booking, customer, configuration, schedule, health, audit, hold, and lifecycle
24
+ persistence; it does not replace host connection ownership or payment/public-
25
+ checkout adapters.
26
+
27
+ For implementation work, use the leaf board and runbook:
28
+ [`EXECUTION-BOARD.json`](../../docs/maggiedash-booking/EXECUTION-BOARD.json)
29
+ and [`TASK-RUNBOOK.md`](../../docs/maggiedash-booking/TASK-RUNBOOK.md). Do not
30
+ mark a task passed until its implementation gate, docs update, and evidence
31
+ record are complete.
32
+
33
+ Never put provider secrets, database credentials, payment card data, or a
34
+ customer export in UI props, browser bundles, fixtures, screenshots, or
35
+ feedback. A payment return URL is not proof of payment; only a verified
36
+ provider webhook or server-side provider read may advance payment state.
37
+
38
+ ## Stable commands
39
+
40
+ Run read-only checks before any host migration or external mutation:
41
+
42
+ ```bash
43
+ maggie booking inspect --project .
44
+ maggie booking contract
45
+ maggie booking runtime-validate --project . \
46
+ --evidence .maggie/booking-runtime.json
47
+ maggie booking release-gate --project . --require-runtime \
48
+ --evidence .maggie/booking-runtime.json
49
+ maggie booking stripe-audit --project .
50
+ maggie booking access-contract
51
+ maggie booking email-templates-contract
52
+ maggie booking tasks validate
53
+ maggie booking tasks list
54
+ ```
55
+
56
+ The final gate must include every evidence class:
57
+
58
+ ```bash
59
+ maggie booking release-gate --project . --require-runtime \
60
+ --evidence .maggie/booking/runtime-evidence.json \
61
+ --require-browser --browser-evidence .maggie/booking/browser/evidence.json \
62
+ --require-ops --ops-evidence .maggie/booking/ops-evidence.json \
63
+ --require-board
64
+ ```
65
+
66
+ `--require-ops` validates sanitized backup/restore, alert-routing, and
67
+ recovery-rehearsal evidence together with the runtime/browser/board gates.
68
+ It applies the host-only evidence rule; fixture, test, and local environments
69
+ cannot satisfy a required operational release artifact.
70
+
71
+ For local CLI regression, the sanitized no-customer fixture is
72
+ [`fixtures/maggiedash/booking-runtime.v1.json`](../../fixtures/maggiedash/booking-runtime.v1.json).
73
+
74
+ Install the first-party workspace and the host integration through the stable
75
+ Booking installer:
76
+
77
+ ```bash
78
+ maggie booking install --project . --confirm
79
+ ```
80
+
81
+ For a host project that does not already contain the Booking runtime packages,
82
+ let the installer add only the missing dependencies through the package manager
83
+ already declared by the project (`npm`, `pnpm`, `yarn`, or `bun`):
84
+
85
+ ```bash
86
+ maggie booking install --project . --confirm --install-dependencies
87
+ ```
88
+
89
+ This is an explicit dependency mutation. It preserves existing package
90
+ versions, updates the matching lockfile, and never installs a second copy of a
91
+ package already present in `dependencies` or `devDependencies`. For a blank
92
+ Astro host it also adds the compatible `@astrojs/node` SSR adapter and the
93
+ portable server config; an existing `astro.config.*` and deployment adapter
94
+ remain host-owned.
95
+
96
+ The default installer source is the public MaggieDash `0.2.8` distribution. It
97
+ contains the released Astro `hostBootstrap` manifest and 40-route Booking
98
+ source. A fresh public-source install, `maggie booking inspect`, and
99
+ `astro build` are the release evidence; a local-source result alone is not.
100
+
101
+ For a new host with a reviewed `DATABASE_URL`, the explicit one-command
102
+ bootstrap also applies the idempotent Booking schema:
103
+
104
+ ```bash
105
+ maggie booking install --project . --confirm --bootstrap
106
+ ```
107
+
108
+ `--bootstrap` is intentionally opt-in: take the host backup/migration decision
109
+ first. It runs the installed `scripts/maggie-booking-schema.mjs` with
110
+ `--apply --confirm`. When the host uses a local `.env`, it also generates
111
+ `BOOKING_TOKEN_SECRET` when the key is absent. The value is never printed and
112
+ an existing value is never replaced; a configured value shorter than 32
113
+ characters fails closed. External secret stores must provision the same key
114
+ separately. Bootstrap still does not create the owner, configure Stripe, send
115
+ email, or deploy.
116
+
117
+ Before changing host configuration, run the safe first-run checklist:
118
+
119
+ ```bash
120
+ maggie booking setup --project .
121
+ ```
122
+
123
+ The checklist reports whether the installed workspace, Astro middleware, and
124
+ required environment keys are present. It also lists the explicit schema,
125
+ first-owner, Stripe webhook, and Resend-worker steps. It never prints secret
126
+ values, connects to PostgreSQL, applies migrations, creates an account, sends
127
+ email, configures Stripe, or deploys. A `configured-pending-verification`
128
+ result means static configuration is present but host runtime evidence is still
129
+ required. The checklist also rejects a `BOOKING_TOKEN_SECRET` shorter than 32
130
+ characters and marks a one-sided Stripe key pair as degraded, so a
131
+ present-but-unusable secret cannot be mistaken for a ready installation.
132
+
133
+ Validate the installable staff-login boundary independently before touching a
134
+ database or provider:
135
+
136
+ ```bash
137
+ maggie booking access-contract
138
+ ```
139
+
140
+ This checks the versioned access contract for administrator-only account
141
+ creation, email/password invitations through Resend, `manager`/`staff`/
142
+ `finance` roles, granular permissions, password reset, revocation, audit
143
+ actions, tenant scoping, and disabled-account protection. It does not create
144
+ accounts or send email.
145
+
146
+ Validate the tenant-scoped Resend template boundary independently:
147
+
148
+ ```bash
149
+ maggie booking email-templates-contract
150
+ ```
151
+
152
+ This freezes the seven supported Booking events, approved `{{variable}}`
153
+ placeholders, owner/admin/manager access, audit and idempotency requirements,
154
+ and the server-only provider/recipient privacy boundary. It does not connect to
155
+ Resend, send a message, or expose customer data.
156
+
157
+ After the first owner signs in, the Booking Overview includes the same
158
+ secret-safe readiness path: Stripe connection status, test/live mode, and a
159
+ copyable `/api/maggie/booking/webhooks/stripe` URL. It gives the operator the
160
+ next secret-store action without accepting or storing Stripe credentials in the
161
+ browser or Booking database.
162
+
163
+ For an Astro host, framework detection installs missing email/password login,
164
+ first-owner registration, manager/public Booking routes, PostgreSQL/Stripe
165
+ adapter wiring, the `/_maggie` rewrite, a schema command, Resend worker, and
166
+ `.env.maggie-booking.example`. It also installs the Booking access API: an
167
+ owner or administrator can create email/password staff, manager, and finance
168
+ accounts, link them to staff profiles, and set explicit route capabilities.
169
+ The permissions are enforced in the server route handler, not only hidden in
170
+ the UI. Use `--host astro` to make detection explicit;
171
+ use `--host none` when only the source distribution is wanted. The
172
+ distribution keeps the content admin at `./_maggie/admin` and Booking source at
173
+ `./_maggie/booking`. Existing files, including an existing `src/middleware.ts`,
174
+ are preserved unless `--force` is explicitly supplied. A conventional Astro
175
+ middleware receives the rewrite block automatically; unsupported middleware is
176
+ preserved and reported by `maggie booking inspect` until integrated manually.
177
+ Installation does not apply SQL, create an owner, or enable a payment provider.
178
+ The standard Booking migration must create
179
+ `maggiedash_booking_user_roles`; do not move that table into a project-only
180
+ extension, or a fresh install can authenticate the owner but cannot resolve a
181
+ Booking session.
182
+
183
+ The Schedule screen manages working hours, staff shifts, time off, and location
184
+ closures through the audited schedule contract. Managers can search and edit
185
+ existing rules by stable ID; each write preserves the full schedule snapshot
186
+ and removes only the selected row before applying its replacement, so unrelated
187
+ availability rows cannot be deleted by an ordinary edit.
188
+
189
+ The Catalogue screen supports searchable service/treatment management. Select
190
+ an existing service to edit its stable variant ID, label, duration, price,
191
+ currency, payment policy, or active/inactive state. These writes still go
192
+ through the same idempotent, audited `catalog.json` endpoint; the UI does not
193
+ create replacement records for ordinary edits.
194
+
195
+ Staff and Resources have the same edit-safe workflow. Managers can select an
196
+ existing profile or resource, update assignments, bookable/active state,
197
+ capacity, and identity fields, then save through the existing audited
198
+ `staff.json` or `resources.json` endpoint without changing the record ID.
199
+
200
+ Manager-created bookings use live tenant configuration: the form selects an
201
+ active service variant, location, staff member, or resource, and refuses to
202
+ submit without a staff/resource allocation. Do not reintroduce free-text ID
203
+ fields as the primary path; PostgreSQL booking creation requires the allocation
204
+ to be explicit.
205
+
206
+ The installer must supply those selectors from tenant data, not fixture IDs.
207
+ The manager catalogue response includes active locations, the schedule response
208
+ includes staff-visible locations, new forms default from the first bootstrapped
209
+ location/service, and resource writes persist their service assignments. Keep
210
+ the UI regression gate that rejects `location-1` and `service-1` fallbacks.
211
+
212
+ For a blank project, configure the first location before services, staff,
213
+ resources, or schedules. The Booking Locations screen uses the audited
214
+ `GET/POST /api/maggie/booking/locations.json` boundary to create or edit a
215
+ tenant location, including IANA timezone, locale, address, active state, and
216
+ online-booking availability. Do not require a seeded venue ID or make the host
217
+ invent one in the browser; the location write must remain tenant-scoped,
218
+ permission-checked, idempotent, and validated server-side.
219
+
220
+ A booking can contain up to 12 treatment segments. Each segment carries its
221
+ own service variant, start/end interval, price, currency, and staff/resource
222
+ allocation; all segments must use one location and currency, pass server-side
223
+ availability checks, and persist with their allocations in the same transaction.
224
+ The manager form exposes this with `Add treatment` and `Remove treatment`. A
225
+ direct host request uses `segments: [{ serviceVariantId, startAt, endAt,
226
+ staffId|resourceId, priceMinor, currency }]` and remains idempotent.
227
+
228
+ The public customer page supports the same bounded multi-treatment shape. It
229
+ requests availability for the combined duration, shows the estimated total,
230
+ creates one hold with ordered segments, and converts it into one booking. A
231
+ token-bound customer or manager reschedule shifts every segment and its
232
+ allocation by the same time delta, checks each staff/resource interval, and
233
+ preserves the original total duration. Single-treatment requests and legacy
234
+ rows remain compatible. Public DTOs expose only safe segment fields.
235
+
236
+ Every `staff` account must also be linked to a Booking staff profile. The host
237
+ passes that `staff_id` into the server context, and the PostgreSQL repository
238
+ filters bookings, calendar, availability, customer summaries, staff/resources,
239
+ schedule, and lifecycle mutations by that assignment. An unlinked staff
240
+ session fails closed. This is a data-security rule, not a UI convention.
241
+ The bootstrap now uses the host's server-side Resend configuration to send
242
+ single-use invitation and password-reset links; raw tokens are never stored.
243
+ The Resend worker separately delivers booking notifications. The default
244
+ worker cycle includes `match-waitlist` and `enqueue-reminders`. The waitlist
245
+ matcher orders eligible entries by priority/FIFO, requires explicit service and
246
+ location scope, persists the offered slot, and queues one
247
+ `booking.waitlist.available` notification. It never auto-creates or reserves a
248
+ booking. The reminder job queues one `booking.reminder`
249
+ email for each confirmed booking in the next 24 hours; the existing Resend
250
+ dispatcher resolves the customer address at delivery time. Reminder queueing
251
+ is idempotent, skips bookings without a customer email, and does not itself
252
+ prove delivery until the host worker reports Resend acceptance. Booking
253
+ checkout creates a short-lived payment-return token and a separate 30-day
254
+ sealed customer-manage token. The worker reuses or creates the manage token
255
+ for confirmation, reschedule, cancellation, and reminder emails, and places
256
+ only the safe URL in the email HTML; raw tokens are never written to the
257
+ outbox.
258
+
259
+ The Booking workspace also provides an Email templates screen. Owner,
260
+ administrator, and Booking manager sessions can edit a tenant draft, preview it
261
+ with sandboxed sample data, publish a new audited version, or roll back to a
262
+ prior version. Templates are restricted to the event-specific allowlist and
263
+ values are escaped before the Resend worker renders them. A published template
264
+ is resolved by `project_id` and event; if none exists, the safe provider-neutral
265
+ fallback is used. Resend credentials and recipient addresses remain
266
+ host/worker-only.
267
+
268
+ Payment policy is configured on the service variant and carried onto the
269
+ booking snapshot. The supported modes are `full`, `deposit`, `pay_later`, and
270
+ `no_payment`. Payment attempts must declare `deposit`, `balance`, `full`, or
271
+ `adjustment` purpose. The worker and payment adapter aggregate captured
272
+ attempts plus refunds into paid and balance-due amounts; never infer payment
273
+ truth from a browser redirect or only the latest attempt. A duplicate command
274
+ must replay before current-balance validation.
275
+
276
+ After the setup checklist is clear, the intended first-run path for an already
277
+ installed host is:
278
+
279
+ ```bash
280
+ node scripts/maggie-booking-schema.mjs --dry-run
281
+ node scripts/maggie-booking-schema.mjs --apply --confirm
282
+ # open /_maggie/register and create the first owner
283
+ # add Stripe and Resend secrets through the deployment secret store
284
+ node scripts/maggie-booking-worker.mjs --write --confirm
285
+ ```
286
+
287
+ The dry-run executes the complete additive migration in a transaction and
288
+ rolls it back, which verifies PostgreSQL permissions without changing data.
289
+ The apply command commits the migration as one transaction.
290
+
291
+ The worker should run from a scheduler with a single-flight lock. Do not run
292
+ the schema command or worker against production until the host's backup,
293
+ migration, Stripe, Resend, and release-evidence gates have passed.
294
+
295
+ It also installs the reviewable runtime starter under `maggiedash/backend/booking`,
296
+ the runtime/browser contracts, `maggiedash/ops/`, safe fixtures, and protected
297
+ GitHub workflow templates. Run the local worker in dry-run mode:
298
+
299
+ ```bash
300
+ npm run booking:worker -- --job all
301
+ ```
302
+
303
+ To preview only the reminder decision without sending email:
304
+
305
+ ```bash
306
+ npm run booking:worker -- --job enqueue-reminders
307
+ ```
308
+
309
+ To preview only the automatic waitlist matching decision:
310
+
311
+ ```bash
312
+ npm run booking:worker -- --job match-waitlist
313
+ ```
314
+
315
+ The source-level domain and host-boundary checks are also available:
316
+
317
+ ```bash
318
+ npm run booking:domain:test
319
+ npm run booking:service:test
320
+ npm run booking:schema:validate
321
+ npm run booking:postgres-core-repository:test
322
+ npm run booking:postgres-manager-methods:test
323
+ npm run booking:postgres-payment-adapter:test
324
+ npm run booking:postgres-backup-restore:integration -- \
325
+ --source-database-url "$DATABASE_URL" \
326
+ --restore-database-url "$RESTORE_DATABASE_URL" \
327
+ --pg-bin-dir "$PG_BIN_DIR" --confirm
328
+ ```
329
+
330
+ The backup/restore command requires two explicitly named disposable or
331
+ approved databases and an empty restore database with no public application
332
+ tables. It verifies the PostgreSQL client tools before `pg_dump`: `pg_dump`
333
+ and `pg_restore` must be the same major version and match or exceed both
334
+ server major versions. Use `--pg-bin-dir` when the matching client is not the
335
+ default `PATH`; otherwise a version-mismatch error provides the corrective
336
+ command. It compares protected Booking table counts afterward, never drops
337
+ either database, and rejects a non-empty target before restore.
338
+
339
+ The real PostgreSQL cycle is intentionally not read-only. Run it only against
340
+ an explicitly disposable or approved staging database:
341
+
342
+ ```bash
343
+ npm run booking:postgres-core-repository:integration -- \
344
+ --database-url "$DATABASE_URL" --confirm
345
+ ```
346
+
347
+ For local contract/browser development, MaggieDash also provides a fixture-only
348
+ reference host. It is never a production adapter:
349
+
350
+ ```bash
351
+ npm run booking:reference-host:test
352
+ npm run booking:reference-host -- --port 4175
353
+ ```
354
+
355
+ For production, pass a host module with `loadState`, `saveState`, and
356
+ `dispatchNotification` to `run-booking-jobs.mjs --adapter`; do not point a
357
+ production service at the JSON fixture.
358
+
359
+ ## Implementation order
360
+
361
+ Follow the task board rather than building a disconnected calendar first:
362
+
363
+ 1. Confirm the host adapter, tenant, role, timezone, error, idempotency, and
364
+ capability decisions.
365
+ 2. Run migration preflight; review backup/restore and `btree_gist` privileges.
366
+ 3. Install the Booking workspace and expose the host route with the same
367
+ email/password session as the admin workspace.
368
+ 4. Compose `createPostgresBookingCoreRepository` for tenant-scoped manager
369
+ reads, configuration writes, and booking/hold/lifecycle writes; run its
370
+ manager-method gate and explicit disposable/staging PostgreSQL integration
371
+ command. Verify staff assignment scope with a linked staff session before
372
+ exposing the dashboard to staff users.
373
+ 5. Compose `createPostgresBookingPaymentRepository` with an injected gateway
374
+ for payment start/reconcile/refund persistence. Keep provider calls outside
375
+ its short database transaction; inject verified webhook, public checkout,
376
+ notification, and runtime-evidence adapters separately. The MaggieDash
377
+ repositories must not call provider SDKs or read secrets.
378
+ 6. Add the provider-neutral payment port and a test-mode adapter. Reconcile
379
+ signed duplicate/out-of-order webhooks before enabling capture.
380
+ 7. Run the UI source gate, contract gate, host runtime conformance, browser
381
+ journeys, and release gate. Record each command in
382
+ [`PROGRESS.md`](../../docs/maggiedash-booking/PROGRESS.md).
383
+
384
+ 8. Verify the scheduled worker queues and delivers a reminder once for a
385
+ confirmed booking inside the 24-hour window. Check the notification
386
+ idempotency key, Resend provider response, retry/dead-letter state, and
387
+ absence of duplicate sends before enabling customer reminders.
388
+
389
+ For a production-like staff rollout, verify the account lifecycle end to end:
390
+ Resend invitation email, first-login password setup, password reset, session
391
+ revocation after reset/deactivation, and an authenticated browser test for each
392
+ permission profile. The host still owns delivery retries and production
393
+ evidence.
394
+
395
+ ## Runtime evidence
396
+
397
+ `runtime-validate` accepts sanitized observations only. Each endpoint must
398
+ report its contract ID, method, route, 2xx status, and whether required fields
399
+ were present. The validator also requires exact endpoint cardinality, an ISO
400
+ timestamp, a dedicated tenant, and no sensitive evidence keys. Do not include
401
+ request bodies, tokens, customer PII, provider payloads, local paths, or
402
+ database details in evidence.
403
+
404
+ The MaggieDash source package also provides `npm run booking:runtime:plan:test`
405
+ to validate a host runtime plan before credentials are used. It covers all
406
+ contract endpoint IDs, concrete path parameters, required query parameters,
407
+ explicit mutation bodies, and unknown-route rejection.
408
+
409
+ The UI must visibly handle loading, empty, error, permission-denied,
410
+ provider-outage, and capability-disabled states. A static JSON contract is not
411
+ runtime proof; capture browser evidence after the host route and session are
412
+ actually wired.
413
+
414
+ ## Customer surface
415
+
416
+ When the product needs customer self-booking, use the separate
417
+ `MaggieDash/contracts/booking-customer-surface-v1.json` contract and
418
+ `backend/booking/customer-route-handler.mjs`. It covers public-noindex
419
+ catalogue/availability, short-lived holds, checkout start, payment-return
420
+ recovery, token-bound customer-manage history, customer cancellation, and
421
+ basic waitlist joining when no suitable slot is available. Use
422
+ `backend/booking/customer-flow.mjs` for redacted public
423
+ DTOs, input validation, bounded payment-return states, and allowlisted
424
+ notification intents.
425
+
426
+ The installed `booking/src/customer.tsx` and
427
+ `maggiedash/adapters/astro/customer-booking-page.astro` provide the optional
428
+ public `/_maggie/booking/book` UI for catalog, availability, hold, checkout,
429
+ pending payment-return recovery, token-bound rescheduling, token-bound
430
+ cancellation, customer-manage history, and waitlist joining when availability
431
+ is empty. Validate it with
432
+ `npm run booking:customer-ui:test`; the host still owns the public route,
433
+ tenant binding, abuse controls, and payment/email adapters.
434
+
435
+ The public handler does not use the manager session. The host must inject a
436
+ tenant-bound context and enforce origin/CSRF, rate-limit, bot, token-codec,
437
+ provider, and notification controls. Compose the installable
438
+ `postgres-customer-adapter.mjs` with the manager/payment repositories when
439
+ PostgreSQL persistence is needed; it stores token hashes, token purpose, and
440
+ sealed ciphertext, never raw bearer tokens. Payment-return tokens are
441
+ short-lived while customer-manage tokens are 30-day links. A redirect is never
442
+ payment proof.
443
+ Run `npm run booking:customer-flow:test` in MaggieDash before wiring the
444
+ adapter, and retain real host/runtime/browser evidence as a separate gate.
445
+ The manager Booking UI also exposes the tenant-scoped waitlist queue. Managers
446
+ can filter entries, notify/cancel/reprioritize them, or select a concrete slot
447
+ and staff/resource allocation to create a real booking transaction linked by
448
+ `convertedBookingId`. The worker also performs bounded priority/FIFO matching
449
+ for explicitly scoped slots and queues a Resend availability notification; the
450
+ customer must still claim the live slot, so it never auto-reserves a booking.
451
+
452
+ For a deterministic end-to-end public journey before host wiring, also run
453
+ `npm run booking:customer-reference-host:test`. This validates the fixture
454
+ reference host only; it is not production tenant, payment, or notification
455
+ evidence.
456
+ The host's idempotency store must compare a normalized request hash: identical
457
+ retries replay the original result, while reusing a key with a different
458
+ payload fails with a conflict and never replays the wrong booking.
459
+
460
+ ## Completion
461
+
462
+ A Booking task is complete only when implementation, its gate, docs, and the
463
+ dated progress entry all pass. The release gate is intentionally read-only: it
464
+ does not run migrations, create bookings, capture/refund payments, or deploy.
@@ -963,6 +963,13 @@ def command_feedback(args: argparse.Namespace) -> int:
963
963
  return completed.returncode
964
964
 
965
965
 
966
+ def command_booking(args: argparse.Namespace) -> int:
967
+ """Dispatch Booking contract checks through the canonical Booking CLI."""
968
+ cli = Path(__file__).with_name("maggie_booking.py")
969
+ completed = subprocess.run([sys.executable, str(cli), *args.booking_args], cwd=Path.cwd(), check=False)
970
+ return completed.returncode
971
+
972
+
966
973
  def parser() -> argparse.ArgumentParser:
967
974
  p = argparse.ArgumentParser(prog="maggie", description=__doc__)
968
975
  sub = p.add_subparsers(dest="command", required=True)
@@ -1017,6 +1024,9 @@ def parser() -> argparse.ArgumentParser:
1017
1024
  feedback = sub.add_parser("feedback", help="collect, review, and submit privacy-safe feedback")
1018
1025
  feedback.add_argument("feedback_args", nargs=argparse.REMAINDER, help="arguments for maggie_feedback.py")
1019
1026
  feedback.set_defaults(func=command_feedback)
1027
+ booking = sub.add_parser("booking", help="inspect and validate the MaggieDash Booking workspace")
1028
+ booking.add_argument("booking_args", nargs=argparse.REMAINDER, help="arguments for maggie_booking.py")
1029
+ booking.set_defaults(func=command_booking)
1020
1030
  doctor = sub.add_parser("doctor", help="check blog routes and SEO output invariants")
1021
1031
  doctor.add_argument("project", nargs="?", default=".")
1022
1032
  doctor.add_argument("--require-bootstrap", action="store_true")