@topy-ai/maggie 0.7.36 → 0.7.37

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,462 @@
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`.
92
+
93
+ The default installer source is the public MaggieDash distribution. It must
94
+ contain the released Astro `hostBootstrap` manifest and 39-route Booking
95
+ source. While validating an unreleased local checkout, pass
96
+ `--source /home/balalior/Dev/MaggieDash`; do not present a local-source result
97
+ as proof that the public npm install path is released.
98
+
99
+ For a new host with a reviewed `DATABASE_URL`, the explicit one-command
100
+ bootstrap also applies the idempotent Booking schema:
101
+
102
+ ```bash
103
+ maggie booking install --project . --confirm --bootstrap
104
+ ```
105
+
106
+ `--bootstrap` is intentionally opt-in: take the host backup/migration decision
107
+ first. It runs the installed `scripts/maggie-booking-schema.mjs` with
108
+ `--apply --confirm`. When the host uses a local `.env`, it also generates
109
+ `BOOKING_TOKEN_SECRET` when the key is absent. The value is never printed and
110
+ an existing value is never replaced; a configured value shorter than 32
111
+ characters fails closed. External secret stores must provision the same key
112
+ separately. Bootstrap still does not create the owner, configure Stripe, send
113
+ email, or deploy.
114
+
115
+ Before changing host configuration, run the safe first-run checklist:
116
+
117
+ ```bash
118
+ maggie booking setup --project .
119
+ ```
120
+
121
+ The checklist reports whether the installed workspace, Astro middleware, and
122
+ required environment keys are present. It also lists the explicit schema,
123
+ first-owner, Stripe webhook, and Resend-worker steps. It never prints secret
124
+ values, connects to PostgreSQL, applies migrations, creates an account, sends
125
+ email, configures Stripe, or deploys. A `configured-pending-verification`
126
+ result means static configuration is present but host runtime evidence is still
127
+ required. The checklist also rejects a `BOOKING_TOKEN_SECRET` shorter than 32
128
+ characters and marks a one-sided Stripe key pair as degraded, so a
129
+ present-but-unusable secret cannot be mistaken for a ready installation.
130
+
131
+ Validate the installable staff-login boundary independently before touching a
132
+ database or provider:
133
+
134
+ ```bash
135
+ maggie booking access-contract
136
+ ```
137
+
138
+ This checks the versioned access contract for administrator-only account
139
+ creation, email/password invitations through Resend, `manager`/`staff`/
140
+ `finance` roles, granular permissions, password reset, revocation, audit
141
+ actions, tenant scoping, and disabled-account protection. It does not create
142
+ accounts or send email.
143
+
144
+ Validate the tenant-scoped Resend template boundary independently:
145
+
146
+ ```bash
147
+ maggie booking email-templates-contract
148
+ ```
149
+
150
+ This freezes the seven supported Booking events, approved `{{variable}}`
151
+ placeholders, owner/admin/manager access, audit and idempotency requirements,
152
+ and the server-only provider/recipient privacy boundary. It does not connect to
153
+ Resend, send a message, or expose customer data.
154
+
155
+ After the first owner signs in, the Booking Overview includes the same
156
+ secret-safe readiness path: Stripe connection status, test/live mode, and a
157
+ copyable `/api/maggie/booking/webhooks/stripe` URL. It gives the operator the
158
+ next secret-store action without accepting or storing Stripe credentials in the
159
+ browser or Booking database.
160
+
161
+ For an Astro host, framework detection installs missing email/password login,
162
+ first-owner registration, manager/public Booking routes, PostgreSQL/Stripe
163
+ adapter wiring, the `/_maggie` rewrite, a schema command, Resend worker, and
164
+ `.env.maggie-booking.example`. It also installs the Booking access API: an
165
+ owner or administrator can create email/password staff, manager, and finance
166
+ accounts, link them to staff profiles, and set explicit route capabilities.
167
+ The permissions are enforced in the server route handler, not only hidden in
168
+ the UI. Use `--host astro` to make detection explicit;
169
+ use `--host none` when only the source distribution is wanted. The
170
+ distribution keeps the content admin at `./_maggie/admin` and Booking source at
171
+ `./_maggie/booking`. Existing files, including an existing `src/middleware.ts`,
172
+ are preserved unless `--force` is explicitly supplied. A conventional Astro
173
+ middleware receives the rewrite block automatically; unsupported middleware is
174
+ preserved and reported by `maggie booking inspect` until integrated manually.
175
+ Installation does not apply SQL, create an owner, or enable a payment provider.
176
+ The standard Booking migration must create
177
+ `maggiedash_booking_user_roles`; do not move that table into a project-only
178
+ extension, or a fresh install can authenticate the owner but cannot resolve a
179
+ Booking session.
180
+
181
+ The Schedule screen manages working hours, staff shifts, time off, and location
182
+ closures through the audited schedule contract. Managers can search and edit
183
+ existing rules by stable ID; each write preserves the full schedule snapshot
184
+ and removes only the selected row before applying its replacement, so unrelated
185
+ availability rows cannot be deleted by an ordinary edit.
186
+
187
+ The Catalogue screen supports searchable service/treatment management. Select
188
+ an existing service to edit its stable variant ID, label, duration, price,
189
+ currency, payment policy, or active/inactive state. These writes still go
190
+ through the same idempotent, audited `catalog.json` endpoint; the UI does not
191
+ create replacement records for ordinary edits.
192
+
193
+ Staff and Resources have the same edit-safe workflow. Managers can select an
194
+ existing profile or resource, update assignments, bookable/active state,
195
+ capacity, and identity fields, then save through the existing audited
196
+ `staff.json` or `resources.json` endpoint without changing the record ID.
197
+
198
+ Manager-created bookings use live tenant configuration: the form selects an
199
+ active service variant, location, staff member, or resource, and refuses to
200
+ submit without a staff/resource allocation. Do not reintroduce free-text ID
201
+ fields as the primary path; PostgreSQL booking creation requires the allocation
202
+ to be explicit.
203
+
204
+ The installer must supply those selectors from tenant data, not fixture IDs.
205
+ The manager catalogue response includes active locations, the schedule response
206
+ includes staff-visible locations, new forms default from the first bootstrapped
207
+ location/service, and resource writes persist their service assignments. Keep
208
+ the UI regression gate that rejects `location-1` and `service-1` fallbacks.
209
+
210
+ For a blank project, configure the first location before services, staff,
211
+ resources, or schedules. The Booking Locations screen uses the audited
212
+ `GET/POST /api/maggie/booking/locations.json` boundary to create or edit a
213
+ tenant location, including IANA timezone, locale, address, active state, and
214
+ online-booking availability. Do not require a seeded venue ID or make the host
215
+ invent one in the browser; the location write must remain tenant-scoped,
216
+ permission-checked, idempotent, and validated server-side.
217
+
218
+ A booking can contain up to 12 treatment segments. Each segment carries its
219
+ own service variant, start/end interval, price, currency, and staff/resource
220
+ allocation; all segments must use one location and currency, pass server-side
221
+ availability checks, and persist with their allocations in the same transaction.
222
+ The manager form exposes this with `Add treatment` and `Remove treatment`. A
223
+ direct host request uses `segments: [{ serviceVariantId, startAt, endAt,
224
+ staffId|resourceId, priceMinor, currency }]` and remains idempotent.
225
+
226
+ The public customer page supports the same bounded multi-treatment shape. It
227
+ requests availability for the combined duration, shows the estimated total,
228
+ creates one hold with ordered segments, and converts it into one booking. A
229
+ token-bound customer or manager reschedule shifts every segment and its
230
+ allocation by the same time delta, checks each staff/resource interval, and
231
+ preserves the original total duration. Single-treatment requests and legacy
232
+ rows remain compatible. Public DTOs expose only safe segment fields.
233
+
234
+ Every `staff` account must also be linked to a Booking staff profile. The host
235
+ passes that `staff_id` into the server context, and the PostgreSQL repository
236
+ filters bookings, calendar, availability, customer summaries, staff/resources,
237
+ schedule, and lifecycle mutations by that assignment. An unlinked staff
238
+ session fails closed. This is a data-security rule, not a UI convention.
239
+ The bootstrap now uses the host's server-side Resend configuration to send
240
+ single-use invitation and password-reset links; raw tokens are never stored.
241
+ The Resend worker separately delivers booking notifications. The default
242
+ worker cycle includes `match-waitlist` and `enqueue-reminders`. The waitlist
243
+ matcher orders eligible entries by priority/FIFO, requires explicit service and
244
+ location scope, persists the offered slot, and queues one
245
+ `booking.waitlist.available` notification. It never auto-creates or reserves a
246
+ booking. The reminder job queues one `booking.reminder`
247
+ email for each confirmed booking in the next 24 hours; the existing Resend
248
+ dispatcher resolves the customer address at delivery time. Reminder queueing
249
+ is idempotent, skips bookings without a customer email, and does not itself
250
+ prove delivery until the host worker reports Resend acceptance. Booking
251
+ checkout creates a short-lived payment-return token and a separate 30-day
252
+ sealed customer-manage token. The worker reuses or creates the manage token
253
+ for confirmation, reschedule, cancellation, and reminder emails, and places
254
+ only the safe URL in the email HTML; raw tokens are never written to the
255
+ outbox.
256
+
257
+ The Booking workspace also provides an Email templates screen. Owner,
258
+ administrator, and Booking manager sessions can edit a tenant draft, preview it
259
+ with sandboxed sample data, publish a new audited version, or roll back to a
260
+ prior version. Templates are restricted to the event-specific allowlist and
261
+ values are escaped before the Resend worker renders them. A published template
262
+ is resolved by `project_id` and event; if none exists, the safe provider-neutral
263
+ fallback is used. Resend credentials and recipient addresses remain
264
+ host/worker-only.
265
+
266
+ Payment policy is configured on the service variant and carried onto the
267
+ booking snapshot. The supported modes are `full`, `deposit`, `pay_later`, and
268
+ `no_payment`. Payment attempts must declare `deposit`, `balance`, `full`, or
269
+ `adjustment` purpose. The worker and payment adapter aggregate captured
270
+ attempts plus refunds into paid and balance-due amounts; never infer payment
271
+ truth from a browser redirect or only the latest attempt. A duplicate command
272
+ must replay before current-balance validation.
273
+
274
+ After the setup checklist is clear, the intended first-run path for an already
275
+ installed host is:
276
+
277
+ ```bash
278
+ node scripts/maggie-booking-schema.mjs --dry-run
279
+ node scripts/maggie-booking-schema.mjs --apply --confirm
280
+ # open /_maggie/register and create the first owner
281
+ # add Stripe and Resend secrets through the deployment secret store
282
+ node scripts/maggie-booking-worker.mjs --write --confirm
283
+ ```
284
+
285
+ The dry-run executes the complete additive migration in a transaction and
286
+ rolls it back, which verifies PostgreSQL permissions without changing data.
287
+ The apply command commits the migration as one transaction.
288
+
289
+ The worker should run from a scheduler with a single-flight lock. Do not run
290
+ the schema command or worker against production until the host's backup,
291
+ migration, Stripe, Resend, and release-evidence gates have passed.
292
+
293
+ It also installs the reviewable runtime starter under `maggiedash/backend/booking`,
294
+ the runtime/browser contracts, `maggiedash/ops/`, safe fixtures, and protected
295
+ GitHub workflow templates. Run the local worker in dry-run mode:
296
+
297
+ ```bash
298
+ npm run booking:worker -- --job all
299
+ ```
300
+
301
+ To preview only the reminder decision without sending email:
302
+
303
+ ```bash
304
+ npm run booking:worker -- --job enqueue-reminders
305
+ ```
306
+
307
+ To preview only the automatic waitlist matching decision:
308
+
309
+ ```bash
310
+ npm run booking:worker -- --job match-waitlist
311
+ ```
312
+
313
+ The source-level domain and host-boundary checks are also available:
314
+
315
+ ```bash
316
+ npm run booking:domain:test
317
+ npm run booking:service:test
318
+ npm run booking:schema:validate
319
+ npm run booking:postgres-core-repository:test
320
+ npm run booking:postgres-manager-methods:test
321
+ npm run booking:postgres-payment-adapter:test
322
+ npm run booking:postgres-backup-restore:integration -- \
323
+ --source-database-url "$DATABASE_URL" \
324
+ --restore-database-url "$RESTORE_DATABASE_URL" \
325
+ --pg-bin-dir "$PG_BIN_DIR" --confirm
326
+ ```
327
+
328
+ The backup/restore command requires two explicitly named disposable or
329
+ approved databases and an empty restore database with no public application
330
+ tables. It verifies the PostgreSQL client tools before `pg_dump`: `pg_dump`
331
+ and `pg_restore` must be the same major version and match or exceed both
332
+ server major versions. Use `--pg-bin-dir` when the matching client is not the
333
+ default `PATH`; otherwise a version-mismatch error provides the corrective
334
+ command. It compares protected Booking table counts afterward, never drops
335
+ either database, and rejects a non-empty target before restore.
336
+
337
+ The real PostgreSQL cycle is intentionally not read-only. Run it only against
338
+ an explicitly disposable or approved staging database:
339
+
340
+ ```bash
341
+ npm run booking:postgres-core-repository:integration -- \
342
+ --database-url "$DATABASE_URL" --confirm
343
+ ```
344
+
345
+ For local contract/browser development, MaggieDash also provides a fixture-only
346
+ reference host. It is never a production adapter:
347
+
348
+ ```bash
349
+ npm run booking:reference-host:test
350
+ npm run booking:reference-host -- --port 4175
351
+ ```
352
+
353
+ For production, pass a host module with `loadState`, `saveState`, and
354
+ `dispatchNotification` to `run-booking-jobs.mjs --adapter`; do not point a
355
+ production service at the JSON fixture.
356
+
357
+ ## Implementation order
358
+
359
+ Follow the task board rather than building a disconnected calendar first:
360
+
361
+ 1. Confirm the host adapter, tenant, role, timezone, error, idempotency, and
362
+ capability decisions.
363
+ 2. Run migration preflight; review backup/restore and `btree_gist` privileges.
364
+ 3. Install the Booking workspace and expose the host route with the same
365
+ email/password session as the admin workspace.
366
+ 4. Compose `createPostgresBookingCoreRepository` for tenant-scoped manager
367
+ reads, configuration writes, and booking/hold/lifecycle writes; run its
368
+ manager-method gate and explicit disposable/staging PostgreSQL integration
369
+ command. Verify staff assignment scope with a linked staff session before
370
+ exposing the dashboard to staff users.
371
+ 5. Compose `createPostgresBookingPaymentRepository` with an injected gateway
372
+ for payment start/reconcile/refund persistence. Keep provider calls outside
373
+ its short database transaction; inject verified webhook, public checkout,
374
+ notification, and runtime-evidence adapters separately. The MaggieDash
375
+ repositories must not call provider SDKs or read secrets.
376
+ 6. Add the provider-neutral payment port and a test-mode adapter. Reconcile
377
+ signed duplicate/out-of-order webhooks before enabling capture.
378
+ 7. Run the UI source gate, contract gate, host runtime conformance, browser
379
+ journeys, and release gate. Record each command in
380
+ [`PROGRESS.md`](../../docs/maggiedash-booking/PROGRESS.md).
381
+
382
+ 8. Verify the scheduled worker queues and delivers a reminder once for a
383
+ confirmed booking inside the 24-hour window. Check the notification
384
+ idempotency key, Resend provider response, retry/dead-letter state, and
385
+ absence of duplicate sends before enabling customer reminders.
386
+
387
+ For a production-like staff rollout, verify the account lifecycle end to end:
388
+ Resend invitation email, first-login password setup, password reset, session
389
+ revocation after reset/deactivation, and an authenticated browser test for each
390
+ permission profile. The host still owns delivery retries and production
391
+ evidence.
392
+
393
+ ## Runtime evidence
394
+
395
+ `runtime-validate` accepts sanitized observations only. Each endpoint must
396
+ report its contract ID, method, route, 2xx status, and whether required fields
397
+ were present. The validator also requires exact endpoint cardinality, an ISO
398
+ timestamp, a dedicated tenant, and no sensitive evidence keys. Do not include
399
+ request bodies, tokens, customer PII, provider payloads, local paths, or
400
+ database details in evidence.
401
+
402
+ The MaggieDash source package also provides `npm run booking:runtime:plan:test`
403
+ to validate a host runtime plan before credentials are used. It covers all
404
+ contract endpoint IDs, concrete path parameters, required query parameters,
405
+ explicit mutation bodies, and unknown-route rejection.
406
+
407
+ The UI must visibly handle loading, empty, error, permission-denied,
408
+ provider-outage, and capability-disabled states. A static JSON contract is not
409
+ runtime proof; capture browser evidence after the host route and session are
410
+ actually wired.
411
+
412
+ ## Customer surface
413
+
414
+ When the product needs customer self-booking, use the separate
415
+ `MaggieDash/contracts/booking-customer-surface-v1.json` contract and
416
+ `backend/booking/customer-route-handler.mjs`. It covers public-noindex
417
+ catalogue/availability, short-lived holds, checkout start, payment-return
418
+ recovery, token-bound customer-manage history, customer cancellation, and
419
+ basic waitlist joining when no suitable slot is available. Use
420
+ `backend/booking/customer-flow.mjs` for redacted public
421
+ DTOs, input validation, bounded payment-return states, and allowlisted
422
+ notification intents.
423
+
424
+ The installed `booking/src/customer.tsx` and
425
+ `maggiedash/adapters/astro/customer-booking-page.astro` provide the optional
426
+ public `/_maggie/booking/book` UI for catalog, availability, hold, checkout,
427
+ pending payment-return recovery, token-bound rescheduling, token-bound
428
+ cancellation, customer-manage history, and waitlist joining when availability
429
+ is empty. Validate it with
430
+ `npm run booking:customer-ui:test`; the host still owns the public route,
431
+ tenant binding, abuse controls, and payment/email adapters.
432
+
433
+ The public handler does not use the manager session. The host must inject a
434
+ tenant-bound context and enforce origin/CSRF, rate-limit, bot, token-codec,
435
+ provider, and notification controls. Compose the installable
436
+ `postgres-customer-adapter.mjs` with the manager/payment repositories when
437
+ PostgreSQL persistence is needed; it stores token hashes, token purpose, and
438
+ sealed ciphertext, never raw bearer tokens. Payment-return tokens are
439
+ short-lived while customer-manage tokens are 30-day links. A redirect is never
440
+ payment proof.
441
+ Run `npm run booking:customer-flow:test` in MaggieDash before wiring the
442
+ adapter, and retain real host/runtime/browser evidence as a separate gate.
443
+ The manager Booking UI also exposes the tenant-scoped waitlist queue. Managers
444
+ can filter entries, notify/cancel/reprioritize them, or select a concrete slot
445
+ and staff/resource allocation to create a real booking transaction linked by
446
+ `convertedBookingId`. The worker also performs bounded priority/FIFO matching
447
+ for explicitly scoped slots and queues a Resend availability notification; the
448
+ customer must still claim the live slot, so it never auto-reserves a booking.
449
+
450
+ For a deterministic end-to-end public journey before host wiring, also run
451
+ `npm run booking:customer-reference-host:test`. This validates the fixture
452
+ reference host only; it is not production tenant, payment, or notification
453
+ evidence.
454
+ The host's idempotency store must compare a normalized request hash: identical
455
+ retries replay the original result, while reusing a key with a different
456
+ payload fails with a conflict and never replays the wrong booking.
457
+
458
+ ## Completion
459
+
460
+ A Booking task is complete only when implementation, its gate, docs, and the
461
+ dated progress entry all pass. The release gate is intentionally read-only: it
462
+ 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")