@topy-ai/maggie 0.7.38 → 0.7.40
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.
- package/README-zh-TW.md +55 -4
- package/README.md +65 -7
- package/bin/maggie.js +10 -2
- package/bundled-contracts/maggie-design/css-utility-evidence-v1.schema.json +10 -0
- package/bundled-contracts/maggie-design/dashboard-surface-v1.schema.json +11 -0
- package/bundled-contracts/maggie-design/progressive-enhancement-v1.schema.json +12 -0
- package/bundled-contracts/maggie-design/sample-surface-v1.schema.json +13 -0
- package/bundled-contracts/maggie-seo/privacy-origin-evidence-v1.schema.json +11 -0
- package/bundled-contracts/maggiedash/booking-access-v1.json +5 -4
- package/bundled-contracts/maggiedash/booking-customer-surface-v1.json +12 -1
- package/bundled-contracts/maggiedash/booking-email-templates-v1.json +2 -0
- package/bundled-contracts/maggiedash/booking-host-adapter-v1.json +14 -2
- package/bundled-contracts/maggiedash/booking-runtime.v1.json +41 -0
- package/bundled-contracts/maggiedash/execution-board.json +526 -26
- package/bundled-contracts/maggiedash/host-capabilities-v1.schema.json +10 -0
- package/bundled-contracts/maggiedash/site-structure-v1.schema.json +13 -0
- package/bundled-references/maggiedash-booking/ARCHITECTURE.md +218 -0
- package/bundled-references/maggiedash-booking/CURRENT-STATE.md +92 -0
- package/bundled-references/maggiedash-booking/DATA-FLOW.md +143 -0
- package/bundled-references/maggiedash-booking/DATA-MODEL.md +367 -0
- package/bundled-references/maggiedash-booking/DECISIONS.md +94 -0
- package/bundled-references/maggiedash-booking/EXECUTION-BOARD.json +2387 -0
- package/bundled-references/maggiedash-booking/HOST-ADAPTER.md +314 -0
- package/bundled-references/maggiedash-booking/ORAWELLNESS-INTEGRATION-AUDIT.md +227 -0
- package/bundled-references/maggiedash-booking/PAYMENT-GATEWAY.md +267 -0
- package/bundled-references/maggiedash-booking/PRD.md +228 -0
- package/bundled-references/maggiedash-booking/PROGRESS.md +2434 -0
- package/bundled-references/maggiedash-booking/QA-TEST-PLAN.md +235 -0
- package/bundled-references/maggiedash-booking/README.md +271 -0
- package/bundled-references/maggiedash-booking/RUNTIME-OPERATIONS.md +152 -0
- package/bundled-references/maggiedash-booking/SECURITY-COMPLIANCE.md +158 -0
- package/bundled-references/maggiedash-booking/SKILLS-AND-CLI.md +542 -0
- package/bundled-references/maggiedash-booking/STRIPE-INTEGRATION.md +129 -0
- package/bundled-references/maggiedash-booking/TASK-RUNBOOK.md +107 -0
- package/bundled-references/maggiedash-booking/TASKS.md +137 -0
- package/bundled-references/maggiedash-booking/USER-JOURNEYS.md +224 -0
- package/bundled-references/maggiedash-booking/diagrams/booking-dfd.excalidraw +1 -0
- package/bundled-references/maggiedash-booking/diagrams/booking-dfd.mmd +16 -0
- package/bundled-references/maggiedash-booking/diagrams/booking-dfd.png +0 -0
- package/bundled-references/maggiedash-booking/diagrams/booking-dfd.svg +1 -0
- package/bundled-references/maggiedash-booking/diagrams/booking-state-machine.mmd +19 -0
- package/bundled-references/maggiedash-booking/diagrams/booking-state-machine.png +0 -0
- package/bundled-references/maggiedash-booking/diagrams/booking-state-machine.svg +1 -0
- package/bundled-references/maggiedash-booking/diagrams/manager-journey.excalidraw +1 -0
- package/bundled-references/maggiedash-booking/diagrams/manager-journey.mmd +11 -0
- package/bundled-references/maggiedash-booking/diagrams/manager-journey.png +0 -0
- package/bundled-references/maggiedash-booking/diagrams/manager-journey.svg +1 -0
- package/bundled-references/maggiedash-booking/diagrams/payment-sequence.mmd +20 -0
- package/bundled-references/maggiedash-booking/diagrams/payment-sequence.png +0 -0
- package/bundled-references/maggiedash-booking/diagrams/payment-sequence.svg +1 -0
- package/bundled-references/maggiedash-booking/diagrams/system-context.excalidraw +1 -0
- package/bundled-references/maggiedash-booking/diagrams/system-context.mmd +10 -0
- package/bundled-references/maggiedash-booking/diagrams/system-context.png +0 -0
- package/bundled-references/maggiedash-booking/diagrams/system-context.svg +1 -0
- package/bundled-skills/maggie-blog-bootstrap/SKILL.md +16 -0
- package/bundled-skills/maggie-booking/SKILL.md +97 -18
- package/bundled-skills/maggie-design/SKILL.md +35 -0
- package/bundled-skills/maggie-seo-geo/SKILL.md +12 -0
- package/bundled-skills/maggie-service-booking/SKILL.md +14 -0
- package/bundled-tools/clis/maggie_booking.py +161 -26
- package/bundled-tools/clis/maggie_contracts.py +282 -0
- package/bundled-tools/clis/maggie_dash.py +22 -6
- package/bundled-tools/clis/maggie_design.py +29 -12
- package/bundled-tools/clis/maggie_service_booking.py +50 -1
- package/bundled-tools/clis/site_audit.py +49 -0
- package/package.json +1 -1
|
@@ -11,22 +11,30 @@ for importing a provider's service catalogue. The existing
|
|
|
11
11
|
right choice for provider-backed services, prices, durations, booking links,
|
|
12
12
|
and factual provider validation.
|
|
13
13
|
|
|
14
|
+
## Project memory
|
|
15
|
+
|
|
16
|
+
Before changing a host project, follow the shared
|
|
17
|
+
[`memory-hook.md`](../../references/memory-hook.md) instructions for reading
|
|
18
|
+
relevant project preferences and known pitfalls. After a verified fix or a
|
|
19
|
+
durable host decision, record only the generalizable learning; never promote a
|
|
20
|
+
temporary project detail, secret, or unverified guess into active memory.
|
|
21
|
+
|
|
14
22
|
## Contract and safety boundary
|
|
15
23
|
|
|
16
|
-
Read [`../../
|
|
17
|
-
[`TASKS.md`](../../
|
|
24
|
+
Read [`../../bundled-references/maggiedash-booking/README.md`](../../bundled-references/maggiedash-booking/README.md),
|
|
25
|
+
[`TASKS.md`](../../bundled-references/maggiedash-booking/TASKS.md), and the task's linked
|
|
18
26
|
reference before changing code. MaggieDash owns the manager UI and versioned
|
|
19
27
|
contracts. The host owns routes, traditional email/password sessions,
|
|
20
28
|
PostgreSQL connection/transaction lifetime, tenant authorization, provider
|
|
21
29
|
credentials, payment webhooks, notifications, and deployment. MaggieDash also
|
|
22
|
-
ships a tenant-scoped PostgreSQL manager repository for catalogue, availability,
|
|
30
|
+
ships a tenant-scoped PostgreSQL manager repository for catalogue, treatment packages, availability,
|
|
23
31
|
booking, customer, configuration, schedule, health, audit, hold, and lifecycle
|
|
24
32
|
persistence; it does not replace host connection ownership or payment/public-
|
|
25
33
|
checkout adapters.
|
|
26
34
|
|
|
27
35
|
For implementation work, use the leaf board and runbook:
|
|
28
|
-
[`EXECUTION-BOARD.json`](../../
|
|
29
|
-
and [`TASK-RUNBOOK.md`](../../
|
|
36
|
+
[`EXECUTION-BOARD.json`](../../bundled-references/maggiedash-booking/EXECUTION-BOARD.json)
|
|
37
|
+
and [`TASK-RUNBOOK.md`](../../bundled-references/maggiedash-booking/TASK-RUNBOOK.md). Do not
|
|
30
38
|
mark a task passed until its implementation gate, docs update, and evidence
|
|
31
39
|
record are complete.
|
|
32
40
|
|
|
@@ -69,7 +77,7 @@ It applies the host-only evidence rule; fixture, test, and local environments
|
|
|
69
77
|
cannot satisfy a required operational release artifact.
|
|
70
78
|
|
|
71
79
|
For local CLI regression, the sanitized no-customer fixture is
|
|
72
|
-
[`fixtures/maggiedash/booking-runtime.v1.json`](../../
|
|
80
|
+
[`fixtures/maggiedash/booking-runtime.v1.json`](../../bundled-contracts/maggiedash/booking-runtime.v1.json).
|
|
73
81
|
|
|
74
82
|
Install the first-party workspace and the host integration through the stable
|
|
75
83
|
Booking installer:
|
|
@@ -93,9 +101,23 @@ Astro host it also adds the compatible `@astrojs/node` SSR adapter and the
|
|
|
93
101
|
portable server config; an existing `astro.config.*` and deployment adapter
|
|
94
102
|
remain host-owned.
|
|
95
103
|
|
|
104
|
+
For a new host with an approved database migration, `--ready` combines the two
|
|
105
|
+
explicit setup mutations into one command:
|
|
106
|
+
|
|
107
|
+
```bash
|
|
108
|
+
maggie booking install --project . --confirm --ready
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
It installs only missing host packages, generates `BOOKING_TOKEN_SECRET` when
|
|
112
|
+
needed, and applies the installed idempotent Booking schema. It still does not
|
|
113
|
+
create the owner, configure Stripe, send email, enable a scheduler, or deploy.
|
|
114
|
+
Use `--schedule systemd` or `--schedule cron` with `--ready` only to generate a
|
|
115
|
+
reviewable scheduler artifact; enabling it remains an operator action after
|
|
116
|
+
backup, migration, and release evidence checks.
|
|
117
|
+
|
|
96
118
|
The default installer source is the public MaggieDash `0.2.8` distribution. It
|
|
97
|
-
contains the released Astro `hostBootstrap` manifest and
|
|
98
|
-
source. A fresh public-source install, `maggie booking inspect`, and
|
|
119
|
+
contains the released Astro `hostBootstrap` manifest and 47-route Booking
|
|
120
|
+
source, including the tenant-scoped treatment package catalogue. A fresh public-source install, `maggie booking inspect`, and
|
|
99
121
|
`astro build` are the release evidence; a local-source result alone is not.
|
|
100
122
|
|
|
101
123
|
For a new host with a reviewed `DATABASE_URL`, the explicit one-command
|
|
@@ -114,6 +136,21 @@ characters fails closed. External secret stores must provision the same key
|
|
|
114
136
|
separately. Bootstrap still does not create the owner, configure Stripe, send
|
|
115
137
|
email, or deploy.
|
|
116
138
|
|
|
139
|
+
Generate the reviewed worker scheduler artifact as part of the same install,
|
|
140
|
+
or after an existing install:
|
|
141
|
+
|
|
142
|
+
```bash
|
|
143
|
+
maggie booking install --project . --confirm --bootstrap --schedule systemd
|
|
144
|
+
# or, for a host without systemd:
|
|
145
|
+
maggie booking worker-schedule --project . --scheduler cron --confirm
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
This writes `deploy/maggie-booking-worker.service` plus `.timer`, or a
|
|
149
|
+
reviewable `deploy/maggie-booking-worker.cron`. It does not edit the crontab,
|
|
150
|
+
enable systemd, start a process, or expose secrets. Review the generated
|
|
151
|
+
artifact, set an unprivileged service user, then enable it only after the
|
|
152
|
+
schema, backup/restore, and release evidence gates pass.
|
|
153
|
+
|
|
117
154
|
Before changing host configuration, run the safe first-run checklist:
|
|
118
155
|
|
|
119
156
|
```bash
|
|
@@ -149,16 +186,19 @@ Validate the tenant-scoped Resend template boundary independently:
|
|
|
149
186
|
maggie booking email-templates-contract
|
|
150
187
|
```
|
|
151
188
|
|
|
152
|
-
This freezes the
|
|
189
|
+
This freezes the ten supported Booking events, approved `{{variable}}`
|
|
153
190
|
placeholders, owner/admin/manager access, audit and idempotency requirements,
|
|
154
191
|
and the server-only provider/recipient privacy boundary. It does not connect to
|
|
155
192
|
Resend, send a message, or expose customer data.
|
|
156
193
|
|
|
157
194
|
After the first owner signs in, the Booking Overview includes the same
|
|
158
195
|
secret-safe readiness path: Stripe connection status, test/live mode, and a
|
|
159
|
-
copyable `/api/maggie/booking/webhooks/stripe` URL.
|
|
160
|
-
|
|
161
|
-
|
|
196
|
+
copyable `/api/maggie/booking/webhooks/stripe` URL. The preferred path is still
|
|
197
|
+
the deployment secret store. For a single-business host, an owner or manager
|
|
198
|
+
may submit both Stripe values over HTTPS from this screen; the server verifies
|
|
199
|
+
them first, stores only authenticated AES-256-GCM ciphertext derived from
|
|
200
|
+
`BOOKING_TOKEN_SECRET`, and never returns the raw values. Deployment
|
|
201
|
+
`STRIPE_SECRET_KEY` takes precedence over dashboard-managed credentials.
|
|
162
202
|
|
|
163
203
|
For an Astro host, framework detection installs missing email/password login,
|
|
164
204
|
first-owner registration, manager/public Booking routes, PostgreSQL/Stripe
|
|
@@ -171,9 +211,11 @@ the UI. Use `--host astro` to make detection explicit;
|
|
|
171
211
|
use `--host none` when only the source distribution is wanted. The
|
|
172
212
|
distribution keeps the content admin at `./_maggie/admin` and Booking source at
|
|
173
213
|
`./_maggie/booking`. Existing files, including an existing `src/middleware.ts`,
|
|
174
|
-
are preserved unless `--force` is explicitly supplied.
|
|
175
|
-
middleware
|
|
176
|
-
|
|
214
|
+
are preserved unless `--force` is explicitly supplied. Conventional Astro
|
|
215
|
+
middleware using either `defineMiddleware((context, next) => {})` or a
|
|
216
|
+
destructured context such as `defineMiddleware(({ url, request }, next) => {})`
|
|
217
|
+
receives the rewrite block automatically; unsupported middleware is preserved
|
|
218
|
+
and reported by `maggie booking inspect` until integrated manually.
|
|
177
219
|
Installation does not apply SQL, create an owner, or enable a payment provider.
|
|
178
220
|
The standard Booking migration must create
|
|
179
221
|
`maggiedash_booking_user_roles`; do not move that table into a project-only
|
|
@@ -233,18 +275,48 @@ allocation by the same time delta, checks each staff/resource interval, and
|
|
|
233
275
|
preserves the original total duration. Single-treatment requests and legacy
|
|
234
276
|
rows remain compatible. Public DTOs expose only safe segment fields.
|
|
235
277
|
|
|
278
|
+
When a tenant has multiple online-bookable locations, the public catalogue must
|
|
279
|
+
return a safe `locations` list and the customer page must let the customer pick
|
|
280
|
+
one. Include the selected `locationId` in availability, waitlist, hold,
|
|
281
|
+
checkout, and reschedule requests. Availability must validate that the
|
|
282
|
+
location belongs to the tenant, is active and online-bookable, use its
|
|
283
|
+
timezone, and filter conflicts by that location. Keep the legacy first
|
|
284
|
+
`locationId` field during migration so older hosts remain compatible; never
|
|
285
|
+
fall back to `location-1` or another fixture ID.
|
|
286
|
+
|
|
287
|
+
After a confirmed package purchase, the public page may carry the opaque
|
|
288
|
+
`packageEntitlementId` into the hold and checkout requests. The server resolves
|
|
289
|
+
the customer from the submitted email, validates tenant ownership, active
|
|
290
|
+
status, expiry, currency, and every selected treatment balance, then locks the
|
|
291
|
+
entitlement and item rows while creating the booking and redemption ledger.
|
|
292
|
+
Public package redemption is all-or-nothing for a multi-treatment selection;
|
|
293
|
+
it confirms a fully covered booking at zero price and does not trust the
|
|
294
|
+
browser to decide the balance.
|
|
295
|
+
|
|
236
296
|
Every `staff` account must also be linked to a Booking staff profile. The host
|
|
237
297
|
passes that `staff_id` into the server context, and the PostgreSQL repository
|
|
238
298
|
filters bookings, calendar, availability, customer summaries, staff/resources,
|
|
239
299
|
schedule, and lifecycle mutations by that assignment. An unlinked staff
|
|
240
300
|
session fails closed. This is a data-security rule, not a UI convention.
|
|
301
|
+
The administrator access registry must stay identical to the route-handler
|
|
302
|
+
capabilities: calendar, bookings, customers, waitlist, catalogue, packages,
|
|
303
|
+
staff, resources, schedule, locations, payments, refunds, notifications,
|
|
304
|
+
settings, reports, and audit. If `permissions_json` is absent on a legacy role,
|
|
305
|
+
apply the safe role defaults server-side; never treat a missing object as full
|
|
306
|
+
read access. Explicitly granted staff capabilities must be honored by the
|
|
307
|
+
route handler, while staff mutations remain limited to approved lifecycle
|
|
308
|
+
transitions. Keep the access contract, Astro host, reference host, ORA mirror,
|
|
309
|
+
and CLI validator synchronized whenever a route capability changes.
|
|
241
310
|
The bootstrap now uses the host's server-side Resend configuration to send
|
|
242
311
|
single-use invitation and password-reset links; raw tokens are never stored.
|
|
243
312
|
The Resend worker separately delivers booking notifications. The default
|
|
244
313
|
worker cycle includes `match-waitlist` and `enqueue-reminders`. The waitlist
|
|
245
314
|
matcher orders eligible entries by priority/FIFO, requires explicit service and
|
|
246
315
|
location scope, persists the offered slot, and queues one
|
|
247
|
-
`booking.waitlist.available` notification.
|
|
316
|
+
`booking.waitlist.available` notification. Package purchase fulfillment and
|
|
317
|
+
provider-confirmed package refunds also queue the allowlisted
|
|
318
|
+
`package.purchase.confirmed`/`package.purchase.refunded` Resend events linked to
|
|
319
|
+
the package purchase, without appointment manage links. It never auto-creates or reserves a
|
|
248
320
|
booking. The reminder job queues one `booking.reminder`
|
|
249
321
|
email for each confirmed booking in the next 24 hours; the existing Resend
|
|
250
322
|
dispatcher resolves the customer address at delivery time. Reminder queueing
|
|
@@ -280,7 +352,9 @@ installed host is:
|
|
|
280
352
|
node scripts/maggie-booking-schema.mjs --dry-run
|
|
281
353
|
node scripts/maggie-booking-schema.mjs --apply --confirm
|
|
282
354
|
# open /_maggie/register and create the first owner
|
|
283
|
-
# add Stripe and Resend secrets through the deployment secret store
|
|
355
|
+
# preferred: add Stripe and Resend secrets through the deployment secret store
|
|
356
|
+
# alternative: owner/manager can enter the Stripe key pair in Booking Overview;
|
|
357
|
+
# the host verifies it and stores only encrypted ciphertext
|
|
284
358
|
node scripts/maggie-booking-worker.mjs --write --confirm
|
|
285
359
|
```
|
|
286
360
|
|
|
@@ -379,7 +453,7 @@ Follow the task board rather than building a disconnected calendar first:
|
|
|
379
453
|
signed duplicate/out-of-order webhooks before enabling capture.
|
|
380
454
|
7. Run the UI source gate, contract gate, host runtime conformance, browser
|
|
381
455
|
journeys, and release gate. Record each command in
|
|
382
|
-
[`PROGRESS.md`](../../
|
|
456
|
+
[`PROGRESS.md`](../../bundled-references/maggiedash-booking/PROGRESS.md).
|
|
383
457
|
|
|
384
458
|
8. Verify the scheduled worker queues and delivers a reminder once for a
|
|
385
459
|
confirmed booking inside the 24-hour window. Check the notification
|
|
@@ -442,6 +516,11 @@ short-lived while customer-manage tokens are 30-day links. A redirect is never
|
|
|
442
516
|
payment proof.
|
|
443
517
|
Run `npm run booking:customer-flow:test` in MaggieDash before wiring the
|
|
444
518
|
adapter, and retain real host/runtime/browser evidence as a separate gate.
|
|
519
|
+
If the host already has a legacy `/book` request form, keep it as a fallback
|
|
520
|
+
until the Booking schema is ready; expose the new customer route only after a
|
|
521
|
+
schema-aware readiness check, and preserve any existing treatment hint when
|
|
522
|
+
linking into the new flow. Do not replace a working host booking form with a
|
|
523
|
+
route that can only show an unconfigured Booking error.
|
|
445
524
|
The manager Booking UI also exposes the tenant-scoped waitlist queue. Managers
|
|
446
525
|
can filter entries, notify/cancel/reprioritize them, or select a concrete slot
|
|
447
526
|
and staff/resource allocation to create a real booking transaction linked by
|
|
@@ -182,6 +182,20 @@ unconfirmed writes. Generated copy and media must be marked authored rather
|
|
|
182
182
|
than source-observed. Implementation must still capture responsive screenshots
|
|
183
183
|
and run accessibility/build validation before publication.
|
|
184
184
|
|
|
185
|
+
Author jobs are resumable design jobs even though their plan is stored beside
|
|
186
|
+
the author brief at `.maggie/design/author-<id>.json`. Use the same progress
|
|
187
|
+
commands as an in-place job; `status` and `step` resolve both supported job
|
|
188
|
+
locations:
|
|
189
|
+
|
|
190
|
+
```bash
|
|
191
|
+
maggie design status author-<id> --project .
|
|
192
|
+
maggie design step author-<id> --project . \
|
|
193
|
+
--step inspect-shell --evidence .maggie/evidence/inspect-shell.json
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
An author plan is not complete until every `requiredSteps` entry has matching
|
|
197
|
+
evidence and the explicit approval step is recorded.
|
|
198
|
+
|
|
185
199
|
## Content UI initialization
|
|
186
200
|
|
|
187
201
|
Use content UI initialization when `maggie-blog` or
|
|
@@ -496,3 +510,24 @@ Follow the shared [Maggie Decision Loop](../../references/decision-loop.md) for
|
|
|
496
510
|
6. Require explicit final confirmation before any mutation or external write.
|
|
497
511
|
|
|
498
512
|
If a decision is not relevant, record it as `skipped` with a reason. Do not silently assume a missing choice, and do not treat an existing output as permission to skip required work.
|
|
513
|
+
|
|
514
|
+
## Contract checks for dashboards, samples, and progressive enhancement
|
|
515
|
+
|
|
516
|
+
Run these stable evidence checks after implementation:
|
|
517
|
+
|
|
518
|
+
```bash
|
|
519
|
+
maggie design progressive-check --evidence .maggie/progressive.json
|
|
520
|
+
maggie design css-check --html dist/index.html --css dist/assets/app.css
|
|
521
|
+
maggie design css-cascade-check --evidence .maggie/css-cascade.json
|
|
522
|
+
maggie design dash-init --project . --route /_maggie/admin --confirm
|
|
523
|
+
maggie design dash-validate --plan .maggie/design/dashboard/*/plan.json --evidence .maggie/dashboard-evidence.json
|
|
524
|
+
maggie design sample-init --project . --route /layout-sample --purpose "layout exploration" --confirm
|
|
525
|
+
maggie design sample-validate --plan .maggie/design/sample/*/plan.json --evidence .maggie/sample-evidence.json
|
|
526
|
+
```
|
|
527
|
+
|
|
528
|
+
The checks cover smallest-region swaps, stable DOM identities, third-party
|
|
529
|
+
requests, stale responsive Tailwind utilities, and unlayered CSS winning over
|
|
530
|
+
utilities. Dashboard plans require operator journeys and desktop/tablet/mobile,
|
|
531
|
+
collapsed-rail, mobile-drawer, and modal evidence. Sample plans require
|
|
532
|
+
noindex, sitemap exclusion, a visible sample banner, placeholders, and a
|
|
533
|
+
finish checklist.
|
|
@@ -381,3 +381,15 @@ backed by a content-change/source-revision event; operational sync, pull,
|
|
|
381
381
|
deploy, or `updated_at` timestamps are rejected. The agent-file command emits
|
|
382
382
|
locale-aware `llms.txt`, `sitemap.md`, and `insights.md` from the same route
|
|
383
383
|
inventory; non-indexable routes are omitted.
|
|
384
|
+
|
|
385
|
+
For every release, audit third-party privacy origins:
|
|
386
|
+
|
|
387
|
+
```bash
|
|
388
|
+
maggie seo privacy-check --evidence .maggie/privacy-origin-evidence.json
|
|
389
|
+
```
|
|
390
|
+
|
|
391
|
+
The site audit also records `primary_navigation_indexability` and flags a
|
|
392
|
+
same-origin page linked from primary navigation when it is `noindex`,
|
|
393
|
+
`nofollow`, or `none`. The privacy check fails in both directions: an embedded
|
|
394
|
+
origin missing from the policy, or a policy origin never observed in rendered
|
|
395
|
+
pages, requires review.
|
|
@@ -502,3 +502,17 @@ later sync are marked `archived`, retained for reconciliation, and excluded
|
|
|
502
502
|
from active category context and public fact gates. After every sync, run
|
|
503
503
|
`fact-audit`, `category-context`, the category hash/render/audit sequence, and
|
|
504
504
|
the aggregate `maggie_release.py` gate.
|
|
505
|
+
|
|
506
|
+
### Treatment groups
|
|
507
|
+
|
|
508
|
+
Keep one treatment catalogue entry and put length/price choices in its
|
|
509
|
+
`variants` array. Imports emit `treatmentGroup` with variant count, duration
|
|
510
|
+
range, and a from-price summary. Run the gate before rendering service cards:
|
|
511
|
+
|
|
512
|
+
```bash
|
|
513
|
+
maggie service treatment-group-audit --project .
|
|
514
|
+
```
|
|
515
|
+
|
|
516
|
+
This prevents a 30/60/90/120-minute treatment from becoming four unrelated
|
|
517
|
+
products and gives catalogue, detail, booking, and pricing surfaces one stable
|
|
518
|
+
identity.
|
|
@@ -8,6 +8,7 @@ import json
|
|
|
8
8
|
import os
|
|
9
9
|
import re
|
|
10
10
|
import secrets
|
|
11
|
+
import shlex
|
|
11
12
|
import shutil
|
|
12
13
|
import subprocess
|
|
13
14
|
import sys
|
|
@@ -200,7 +201,7 @@ def contract_checks(contract: dict) -> list[str]:
|
|
|
200
201
|
if not isinstance(service_methods, dict) or set(service_methods) != set(endpoint_ids or []): errors.append("serviceMethods must map every endpoint exactly once")
|
|
201
202
|
elif not all(isinstance(value, str) and value for value in service_methods.values()): errors.append("serviceMethods values must be non-empty strings")
|
|
202
203
|
customer_methods = contract.get("customerServiceMethods")
|
|
203
|
-
if customer_methods != ["readPublicCatalog", "readPublicAvailability", "createCustomerHold", "startCustomerCheckout", "readCustomerPaymentReturn", "readCustomerManage", "cancelCustomerBooking", "rescheduleCustomerBooking", "joinCustomerWaitlist"]: errors.append("customerServiceMethods must declare the
|
|
204
|
+
if customer_methods != ["readPublicCatalog", "readPublicAvailability", "createCustomerHold", "startCustomerCheckout", "startCustomerPackageCheckout", "readCustomerPaymentReturn", "readCustomerPackagePaymentReturn", "readCustomerManage", "cancelCustomerBooking", "rescheduleCustomerBooking", "joinCustomerWaitlist"]: errors.append("customerServiceMethods must declare the eleven public customer methods")
|
|
204
205
|
return errors
|
|
205
206
|
|
|
206
207
|
|
|
@@ -235,7 +236,17 @@ def access_contract_checks(contract: dict) -> list[str]:
|
|
|
235
236
|
if auth.get("passkeys") is not False: errors.append("passkeys must be disabled")
|
|
236
237
|
if auth.get("resendInvitation") is not True: errors.append("resendInvitation must be enabled")
|
|
237
238
|
if roles != ["manager", "staff", "finance"]: errors.append("roles must be manager, staff, finance")
|
|
238
|
-
|
|
239
|
+
expected_permissions = (
|
|
240
|
+
"calendar.read", "bookings.read", "bookings.manage", "customers.read",
|
|
241
|
+
"waitlist.read", "waitlist.manage", "catalog.read", "catalog.manage",
|
|
242
|
+
"packages.read", "packages.manage", "staff.read", "staff.manage",
|
|
243
|
+
"resources.read", "resources.manage", "schedule.read", "schedule.manage",
|
|
244
|
+
"locations.read", "locations.manage", "payments.read", "payments.manage",
|
|
245
|
+
"refunds.manage", "notifications.read", "notifications.manage",
|
|
246
|
+
"settings.read", "settings.manage", "reports.read", "audit.read",
|
|
247
|
+
)
|
|
248
|
+
if permissions != list(expected_permissions): errors.append("access permissions do not match the complete route capability registry")
|
|
249
|
+
for permission in expected_permissions:
|
|
239
250
|
if permission not in permissions: errors.append(f"missing permission: {permission}")
|
|
240
251
|
if endpoint_ids != ["booking.access.read", "booking.access.create", "booking.access.update", "booking.access.revoke"]: errors.append("access endpoint IDs are incomplete or out of order")
|
|
241
252
|
expected = {"booking.access.read": "GET /access.json", "booking.access.create": "POST /access.json", "booking.access.update": "PATCH /access.json", "booking.access.revoke": "DELETE /access.json"}
|
|
@@ -273,12 +284,12 @@ def email_templates_contract_checks(contract: dict) -> list[str]:
|
|
|
273
284
|
if auth.get("passkeys") is not False: errors.append("passkeys must be disabled")
|
|
274
285
|
if roles != ["owner", "admin", "manager"]: errors.append("roles must be owner, admin, manager")
|
|
275
286
|
events = contract.get("events")
|
|
276
|
-
if not isinstance(events, list) or len(events) !=
|
|
287
|
+
if not isinstance(events, list) or len(events) != 9: errors.append("exactly nine Booking email events are required")
|
|
277
288
|
event_ids = [item.get("id") for item in events if isinstance(item, dict)] if isinstance(events, list) else []
|
|
278
|
-
expected_events = ["booking.confirmed", "booking.rescheduled", "booking.cancelled", "booking.reminder", "payment.action_required", "payment.refunded", "booking.waitlist.available"]
|
|
289
|
+
expected_events = ["booking.confirmed", "booking.rescheduled", "booking.cancelled", "booking.reminder", "payment.action_required", "payment.refunded", "package.purchase.confirmed", "package.purchase.refunded", "booking.waitlist.available"]
|
|
279
290
|
if event_ids != expected_events: errors.append("email event IDs are incomplete or out of order")
|
|
280
291
|
if len(event_ids) != len(set(event_ids)): errors.append("email event IDs must be unique")
|
|
281
|
-
allowed = {"customerName", "serviceName", "when", "locationName", "bookingId", "manageUrl", "bookingUrl", "reason", "actionUrl", "siteName"}
|
|
292
|
+
allowed = {"customerName", "serviceName", "when", "locationName", "bookingId", "manageUrl", "bookingUrl", "reason", "actionUrl", "packageName", "amount", "expiresAt", "siteName"}
|
|
282
293
|
for event in events if isinstance(events, list) else []:
|
|
283
294
|
variables = event.get("variables") if isinstance(event, dict) else None
|
|
284
295
|
if not isinstance(variables, list) or not variables: errors.append(f"{event.get('id', 'unknown')}: variables must be a non-empty list")
|
|
@@ -331,7 +342,7 @@ def customer_contract_checks(contract: dict) -> list[str]:
|
|
|
331
342
|
if noindex is not True: errors.append("customer surface must be noindex")
|
|
332
343
|
if properties == {} and response_policy.get("mutationPolicy") != "all-public-mutations-require-idempotency-key-and-replay-original-result": errors.append("customer mutationPolicy must require idempotent replay")
|
|
333
344
|
if properties == {} and set(response_policy.get("holdInputPolicy", [])) != {"valid-email", "booking-update-consent"}: errors.append("customer holdInputPolicy must require valid email and booking-update-consent")
|
|
334
|
-
if len(endpoints) !=
|
|
345
|
+
if len(endpoints) != 11: errors.append("customer surface must declare eleven endpoints")
|
|
335
346
|
if len(ids) != len(set(ids)): errors.append("customer endpoint IDs must be unique")
|
|
336
347
|
if not all(str(item).startswith("booking.public.") for item in ids): errors.append("customer endpoint IDs must use the booking.public namespace")
|
|
337
348
|
for endpoint in endpoints:
|
|
@@ -630,7 +641,7 @@ def booking_setup_result(root: Path, environment: dict[str, str] | None = None)
|
|
|
630
641
|
setup_check("site", "Public site URL", ["PUBLIC_SITE_URL"], has("PUBLIC_SITE_URL"), detail="Used for login links, booking links, and email URLs."),
|
|
631
642
|
setup_check("booking-token", "Public booking token secret", ["BOOKING_TOKEN_SECRET"], token_valid, detail="Must be at least 32 characters; used to sign public manage-booking links." if token_valid or not token_present else "The configured secret is too short; use at least 32 characters.", status_override="passed" if token_valid else "invalid" if token_present else None),
|
|
632
643
|
setup_check("resend", "Resend email delivery", ["RESEND_API_KEY", "EMAIL_FROM|RESEND_FROM_EMAIL"], has("RESEND_API_KEY") and has_sender, detail="Required for password setup/reset, booking notifications, and reminders."),
|
|
633
|
-
setup_check("stripe", "Stripe online payments", ["STRIPE_SECRET_KEY", "STRIPE_WEBHOOK_SECRET"], stripe_complete, required=False, detail="Required only for deposit/full online payment flows; webhook URL is /api/maggie/booking/webhooks/stripe." if not stripe_partial else "Both Stripe secret and webhook secret are required for online payments.", status_override="passed" if stripe_complete else "degraded" if stripe_partial else "optional"),
|
|
644
|
+
setup_check("stripe", "Stripe online payments", ["STRIPE_SECRET_KEY", "STRIPE_WEBHOOK_SECRET"], stripe_complete, required=False, detail="Required only for deposit/full online payment flows; webhook URL is /api/maggie/booking/webhooks/stripe." if not stripe_partial and (stripe_secret or stripe_webhook) else "Both Stripe secret and webhook secret are required for online payments." if stripe_partial else "Optional at install: an owner or manager can connect Stripe from Booking Overview, or use the deployment secret store.", status_override="passed" if stripe_complete else "degraded" if stripe_partial else "optional"),
|
|
634
645
|
{"id": "schema", "label": "Booking database schema", "status": "not-verified", "required": True, "detail": "The CLI does not connect to PostgreSQL. Apply the schema after backup, then verify with the host runtime checks."},
|
|
635
646
|
{"id": "owner", "label": "First owner account", "status": "not-verified", "required": True, "detail": "Open /_maggie/register once the schema is applied. Registration closes after the first owner."},
|
|
636
647
|
{"id": "worker", "label": "Resend worker schedule", "status": "not-verified", "required": True, "detail": "Run the installed worker from a single-flight scheduler; setup does not create a cron or systemd job."},
|
|
@@ -647,15 +658,19 @@ def booking_setup_result(root: Path, environment: dict[str, str] | None = None)
|
|
|
647
658
|
if "stripe" in degraded_config:
|
|
648
659
|
next_steps.append("Configure both STRIPE_SECRET_KEY and STRIPE_WEBHOOK_SECRET before accepting online payments.")
|
|
649
660
|
if missing_dependencies:
|
|
650
|
-
next_steps.append("
|
|
651
|
-
if any(item["id"] == "schema" and item["status"] == "not-verified" for item in checks):
|
|
661
|
+
next_steps.append("For a new host, install missing packages and apply the schema together: maggie booking install --project . --confirm --ready")
|
|
662
|
+
if not missing_dependencies and any(item["id"] == "schema" and item["status"] == "not-verified" for item in checks):
|
|
652
663
|
next_steps.append("After backup, run: maggie booking install --project . --confirm --bootstrap")
|
|
653
664
|
if any(item["id"] == "owner" and item["status"] == "not-verified" for item in checks):
|
|
654
665
|
next_steps.append("Open /_maggie/register and create the first owner, then sign in at /_maggie/login")
|
|
655
666
|
if any(item["id"] == "worker" and item["status"] == "not-verified" for item in checks):
|
|
656
667
|
next_steps.append("Configure a single-flight scheduler for: node scripts/maggie-booking-worker.mjs --write --confirm")
|
|
657
|
-
if
|
|
658
|
-
next_steps.append("
|
|
668
|
+
if stripe_partial:
|
|
669
|
+
next_steps.append("Complete both Stripe deployment keys before accepting online payments; then POST /api/maggie/booking/webhooks/stripe and verify the connection.")
|
|
670
|
+
elif not stripe_complete:
|
|
671
|
+
next_steps.append("After signing in, an owner or manager can connect Stripe from Booking Overview; alternatively add both deployment keys, configure the webhook URL, and verify the connection.")
|
|
672
|
+
else:
|
|
673
|
+
next_steps.append("After signing in, open Booking Overview and run Verify Stripe connection; env-key presence alone is not connected state.")
|
|
659
674
|
status = "needs-configuration" if missing_required else "configured-pending-verification"
|
|
660
675
|
return {
|
|
661
676
|
"status": status,
|
|
@@ -663,7 +678,7 @@ def booking_setup_result(root: Path, environment: dict[str, str] | None = None)
|
|
|
663
678
|
"checks": checks,
|
|
664
679
|
"configuredKeys": sorted(key for key in sources if key in {"DATABASE_URL", "PUBLIC_SITE_URL", "BOOKING_TOKEN_SECRET", "RESEND_API_KEY", "EMAIL_FROM", "RESEND_FROM_EMAIL", "STRIPE_SECRET_KEY", "STRIPE_WEBHOOK_SECRET"}),
|
|
665
680
|
"verificationPending": verification_pending,
|
|
666
|
-
"paymentConfiguration": "online-payments" if
|
|
681
|
+
"paymentConfiguration": "online-payments-pending-verification" if stripe_complete else "incomplete-deployment-stripe" if stripe_partial else "dashboard-or-host-secret-pending",
|
|
667
682
|
"next": next_steps,
|
|
668
683
|
"note": "Read-only checklist. It does not print secrets, connect to PostgreSQL, apply migrations, create an owner, configure Stripe, send email, or deploy.",
|
|
669
684
|
}
|
|
@@ -714,10 +729,101 @@ def ensure_bootstrap_token_secret(root: Path) -> bool:
|
|
|
714
729
|
return True
|
|
715
730
|
|
|
716
731
|
|
|
732
|
+
def write_worker_schedule(root: Path, scheduler: str, *, force: bool = False) -> dict:
|
|
733
|
+
"""Generate a host-owned single-flight schedule for the installed worker.
|
|
734
|
+
|
|
735
|
+
The CLI writes deployment artifacts only. It never enables a systemd unit,
|
|
736
|
+
edits the host crontab, starts a process, or prints environment values.
|
|
737
|
+
The host operator remains responsible for reviewing the unit, selecting an
|
|
738
|
+
unprivileged service user, and enabling it after migration/evidence gates.
|
|
739
|
+
"""
|
|
740
|
+
if scheduler not in {"systemd", "cron"}:
|
|
741
|
+
raise RuntimeError("worker scheduler must be systemd or cron")
|
|
742
|
+
script = root / "scripts" / "maggie-booking-worker.mjs"
|
|
743
|
+
if not script.is_file():
|
|
744
|
+
raise RuntimeError(f"installed Booking worker is missing: {script}")
|
|
745
|
+
output_dir = root / "deploy"
|
|
746
|
+
output_dir.mkdir(parents=True, exist_ok=True)
|
|
747
|
+
env_file = root / ".env"
|
|
748
|
+
worker_log = root / ".maggie" / "booking-worker.log"
|
|
749
|
+
worker_log.parent.mkdir(parents=True, exist_ok=True)
|
|
750
|
+
root_text = root.as_posix()
|
|
751
|
+
script_text = script.as_posix()
|
|
752
|
+
env_text = env_file.as_posix()
|
|
753
|
+
output: dict[str, str] = {}
|
|
754
|
+
if scheduler == "systemd":
|
|
755
|
+
service = output_dir / "maggie-booking-worker.service"
|
|
756
|
+
timer = output_dir / "maggie-booking-worker.timer"
|
|
757
|
+
service_text = f"""[Unit]
|
|
758
|
+
Description=Maggie Booking worker and Resend outbox delivery
|
|
759
|
+
After=network-online.target
|
|
760
|
+
Wants=network-online.target
|
|
761
|
+
|
|
762
|
+
[Service]
|
|
763
|
+
Type=oneshot
|
|
764
|
+
# Review and set an unprivileged deployment user before enabling this unit.
|
|
765
|
+
WorkingDirectory={root_text}
|
|
766
|
+
EnvironmentFile=-{env_text}
|
|
767
|
+
ExecStart=/usr/bin/env node {script_text} --write --confirm
|
|
768
|
+
NoNewPrivileges=true
|
|
769
|
+
PrivateTmp=true
|
|
770
|
+
ProtectSystem=strict
|
|
771
|
+
ProtectHome=true
|
|
772
|
+
ReadWritePaths={root_text}/.maggie
|
|
773
|
+
|
|
774
|
+
[Install]
|
|
775
|
+
WantedBy=multi-user.target
|
|
776
|
+
"""
|
|
777
|
+
timer_text = """[Unit]
|
|
778
|
+
Description=Run Maggie Booking worker every minute
|
|
779
|
+
|
|
780
|
+
[Timer]
|
|
781
|
+
OnBootSec=1min
|
|
782
|
+
OnUnitActiveSec=1min
|
|
783
|
+
AccuracySec=10s
|
|
784
|
+
Persistent=true
|
|
785
|
+
Unit=maggie-booking-worker.service
|
|
786
|
+
|
|
787
|
+
[Install]
|
|
788
|
+
WantedBy=timers.target
|
|
789
|
+
"""
|
|
790
|
+
for path, text in ((service, service_text), (timer, timer_text)):
|
|
791
|
+
if path.exists() and not force:
|
|
792
|
+
output[path.name] = "unchanged"
|
|
793
|
+
continue
|
|
794
|
+
path.write_text(text, encoding="utf-8")
|
|
795
|
+
output[path.name] = "written"
|
|
796
|
+
else:
|
|
797
|
+
cron = output_dir / "maggie-booking-worker.cron"
|
|
798
|
+
command = "cd {root} && {worker}".format(
|
|
799
|
+
root=shlex.quote(root_text),
|
|
800
|
+
worker=" ".join([
|
|
801
|
+
"/usr/bin/flock -n /tmp/maggie-booking-worker.lock",
|
|
802
|
+
"/usr/bin/env node",
|
|
803
|
+
shlex.quote(script_text),
|
|
804
|
+
"--write --confirm",
|
|
805
|
+
]),
|
|
806
|
+
)
|
|
807
|
+
cron_text = f"""# Maggie Booking worker; install with the deploy user's crontab after review.
|
|
808
|
+
# The worker performs its own PostgreSQL migration preflight and tenant lock.
|
|
809
|
+
* * * * * {command} >> {shlex.quote(worker_log.as_posix())} 2>&1
|
|
810
|
+
"""
|
|
811
|
+
if cron.exists() and not force:
|
|
812
|
+
output[cron.name] = "unchanged"
|
|
813
|
+
else:
|
|
814
|
+
cron.write_text(cron_text, encoding="utf-8")
|
|
815
|
+
output[cron.name] = "written"
|
|
816
|
+
return {"status": "generated", "scheduler": scheduler, "files": output, "note": "Review and enable the host scheduler after migration and release evidence; no service or crontab was changed."}
|
|
817
|
+
|
|
818
|
+
|
|
717
819
|
def command_install(args: argparse.Namespace) -> int:
|
|
718
820
|
"""Install Booking, optionally adding missing host packages and schema."""
|
|
719
|
-
|
|
720
|
-
|
|
821
|
+
schedule = getattr(args, "schedule", "none")
|
|
822
|
+
ready = bool(getattr(args, "ready", False))
|
|
823
|
+
install_dependencies = bool(getattr(args, "install_dependencies", False) or ready)
|
|
824
|
+
bootstrap = bool(getattr(args, "bootstrap", False) or ready)
|
|
825
|
+
if (bootstrap or install_dependencies or schedule != "none") and (args.dry_run or args.diff):
|
|
826
|
+
print("INSTALL_MUTATION_INCOMPATIBLE: --ready, --bootstrap, --install-dependencies, and --schedule cannot be combined with --dry-run or --diff.", file=sys.stderr)
|
|
721
827
|
return 2
|
|
722
828
|
if not args.dry_run and not args.diff and not args.confirm:
|
|
723
829
|
print("CONFIRMATION_REQUIRED: add --confirm before installing Booking.", file=sys.stderr)
|
|
@@ -742,26 +848,47 @@ def command_install(args: argparse.Namespace) -> int:
|
|
|
742
848
|
completed = subprocess.run(command, cwd=root, check=False)
|
|
743
849
|
if completed.returncode != 0:
|
|
744
850
|
return completed.returncode
|
|
745
|
-
if
|
|
851
|
+
if install_dependencies:
|
|
746
852
|
try:
|
|
747
853
|
dependency_result = install_host_dependencies(root)
|
|
748
854
|
except RuntimeError as error:
|
|
749
855
|
print(f"DEPENDENCY_INSTALL_FAILED: {error}", file=sys.stderr)
|
|
750
856
|
return 1
|
|
751
857
|
print(json.dumps({"status": "host-dependencies", **dependency_result}, ensure_ascii=False))
|
|
752
|
-
if
|
|
753
|
-
|
|
858
|
+
if bootstrap:
|
|
859
|
+
try:
|
|
860
|
+
ensure_bootstrap_token_secret(root)
|
|
861
|
+
except RuntimeError as error:
|
|
862
|
+
print(f"BOOTSTRAP_FAILED: {error}", file=sys.stderr)
|
|
863
|
+
return 1
|
|
864
|
+
schema_script = root / "scripts" / "maggie-booking-schema.mjs"
|
|
865
|
+
if not schema_script.is_file():
|
|
866
|
+
print(f"BOOTSTRAP_FAILED: installed host schema script is missing: {schema_script}", file=sys.stderr)
|
|
867
|
+
return 1
|
|
868
|
+
schema = subprocess.run(["node", str(schema_script), "--apply", "--confirm"], cwd=root, check=False)
|
|
869
|
+
if schema.returncode != 0:
|
|
870
|
+
return schema.returncode
|
|
871
|
+
if schedule != "none":
|
|
872
|
+
try:
|
|
873
|
+
print(json.dumps({"status": "worker-schedule", **write_worker_schedule(root, schedule, force=args.force)}, ensure_ascii=False))
|
|
874
|
+
except (OSError, RuntimeError) as error:
|
|
875
|
+
print(f"SCHEDULE_FAILED: {error}", file=sys.stderr)
|
|
876
|
+
return 1
|
|
877
|
+
return 0
|
|
878
|
+
|
|
879
|
+
|
|
880
|
+
def command_worker_schedule(args: argparse.Namespace) -> int:
|
|
881
|
+
root = project_path(args)
|
|
882
|
+
if not args.confirm:
|
|
883
|
+
print("CONFIRMATION_REQUIRED: add --confirm before generating worker scheduler files.", file=sys.stderr)
|
|
884
|
+
return 2
|
|
754
885
|
try:
|
|
755
|
-
|
|
756
|
-
except RuntimeError as error:
|
|
757
|
-
print(f"
|
|
758
|
-
return 1
|
|
759
|
-
schema_script = root / "scripts" / "maggie-booking-schema.mjs"
|
|
760
|
-
if not schema_script.is_file():
|
|
761
|
-
print(f"BOOTSTRAP_FAILED: installed host schema script is missing: {schema_script}", file=sys.stderr)
|
|
886
|
+
result = write_worker_schedule(root, args.scheduler, force=args.force)
|
|
887
|
+
except (OSError, RuntimeError) as error:
|
|
888
|
+
print(f"SCHEDULE_FAILED: {error}", file=sys.stderr)
|
|
762
889
|
return 1
|
|
763
|
-
|
|
764
|
-
return
|
|
890
|
+
print(json.dumps(result, ensure_ascii=False))
|
|
891
|
+
return 0
|
|
765
892
|
|
|
766
893
|
|
|
767
894
|
def command_inspect(args: argparse.Namespace) -> int:
|
|
@@ -1073,8 +1200,16 @@ def parser() -> argparse.ArgumentParser:
|
|
|
1073
1200
|
install.add_argument("--existing-dir")
|
|
1074
1201
|
install.add_argument("--install-dependencies", action="store_true", help="add missing Astro Booking packages with the host package manager")
|
|
1075
1202
|
install.add_argument("--bootstrap", action="store_true", help="apply the installed host Booking schema after installation")
|
|
1203
|
+
install.add_argument("--ready", action="store_true", help="install missing host packages and apply the Booking schema after installation")
|
|
1204
|
+
install.add_argument("--schedule", choices=["none", "systemd", "cron"], default="none", help="generate a reviewed worker scheduler artifact after installation")
|
|
1076
1205
|
install.add_argument("--confirm", action="store_true")
|
|
1077
1206
|
install.set_defaults(func=command_install)
|
|
1207
|
+
worker_schedule = sub.add_parser("worker-schedule", help="generate a reviewed systemd or cron Booking worker schedule")
|
|
1208
|
+
worker_schedule.add_argument("--project", default=".")
|
|
1209
|
+
worker_schedule.add_argument("--scheduler", choices=["systemd", "cron"], required=True)
|
|
1210
|
+
worker_schedule.add_argument("--force", action="store_true")
|
|
1211
|
+
worker_schedule.add_argument("--confirm", action="store_true")
|
|
1212
|
+
worker_schedule.set_defaults(func=command_worker_schedule)
|
|
1078
1213
|
contract = sub.add_parser("contract", help="validate the provider-neutral Booking contract")
|
|
1079
1214
|
contract.add_argument("--contract", default=str(DEFAULT_CONTRACT))
|
|
1080
1215
|
contract.set_defaults(func=command_contract)
|