@topy-ai/maggie 0.7.47 → 0.7.49

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.
Files changed (91) hide show
  1. package/README-zh-TW.md +2 -2
  2. package/README.md +76 -15
  3. package/bin/maggie.js +23 -6
  4. package/bundled-contracts/google-integrations/capability-report-v2.schema.json +2 -1
  5. package/bundled-contracts/maggie-content/email-localization-v1.schema.json +89 -0
  6. package/bundled-contracts/maggie-content/lifecycle-email-v1.schema.json +76 -0
  7. package/bundled-contracts/maggie-deployment/readiness-v1.schema.json +6 -1
  8. package/bundled-contracts/maggie-design/palette-roles-v1.schema.json +47 -0
  9. package/bundled-contracts/maggie-marketplace/template-content-v1.schema.json +103 -0
  10. package/bundled-contracts/maggie-ops/marketing-run-v1.schema.json +89 -0
  11. package/bundled-contracts/maggie-ops/notification-catalogue-example-v1.json +941 -0
  12. package/bundled-contracts/maggie-ops/notification-catalogue-v1.schema.json +273 -0
  13. package/bundled-contracts/maggie-service-booking/fact-provenance-v1.schema.json +128 -0
  14. package/bundled-contracts/maggie-service-booking/import-stages-v1.schema.json +191 -0
  15. package/bundled-contracts/maggiedash/booking-browser-evidence-v1.json +21 -0
  16. package/bundled-contracts/maggiedash/booking-browser-evidence-v2.json +191 -0
  17. package/bundled-contracts/maggiedash/booking-email-templates-v1.json +3 -1
  18. package/bundled-contracts/maggiedash/booking-voucher-lifecycle-v1.json +85 -0
  19. package/bundled-contracts/maggiedash/execution-board.json +4629 -1553
  20. package/bundled-contracts/maggiedash/stripe-booking-capabilities-v1.json +1 -0
  21. package/bundled-marketplace/README.md +17 -0
  22. package/bundled-marketplace/catalog.json +56 -7
  23. package/bundled-references/browser-inspection.md +25 -0
  24. package/bundled-references/google-integrations-runbook.md +54 -0
  25. package/bundled-references/maggiedash-booking/CURRENT-STATE.md +128 -10
  26. package/bundled-references/maggiedash-booking/EXECUTION-BOARD.json +4629 -1553
  27. package/bundled-references/maggiedash-booking/HOST-ADAPTER.md +5 -0
  28. package/bundled-references/maggiedash-booking/ORAWELLNESS-INTEGRATION-AUDIT.md +64 -21
  29. package/bundled-references/maggiedash-booking/PRD.md +9 -1
  30. package/bundled-references/maggiedash-booking/PROGRESS.md +244 -2399
  31. package/bundled-references/maggiedash-booking/QA-TEST-PLAN.md +37 -6
  32. package/bundled-references/maggiedash-booking/README.md +23 -6
  33. package/bundled-references/maggiedash-booking/RUNTIME-OPERATIONS.md +64 -8
  34. package/bundled-references/maggiedash-booking/SKILLS-AND-CLI.md +11 -2
  35. package/bundled-references/maggiedash-booking/STRIPE-INTEGRATION.md +66 -2
  36. package/bundled-references/maggiedash-booking/TASK-RUNBOOK.md +1 -1
  37. package/bundled-references/maggiedash-booking/TASKS.md +44 -3
  38. package/bundled-references/maggiedash-booking/VOUCHERS.md +47 -0
  39. package/bundled-references/provider-adapter-contract.md +33 -0
  40. package/bundled-skills/maggie-booking/SKILL.md +8 -0
  41. package/bundled-skills/maggie-clone-to-template/SKILL.md +26 -0
  42. package/bundled-skills/maggie-content-localization/SKILL.md +17 -1
  43. package/bundled-skills/maggie-content-localization/references/email-and-lifecycle.md +83 -0
  44. package/bundled-skills/maggie-deployment/SKILL.md +141 -7
  45. package/bundled-skills/maggie-deployment/references/vps.md +74 -1
  46. package/bundled-skills/maggie-design/SKILL.md +154 -0
  47. package/bundled-skills/maggie-feedback/SKILL.md +67 -1
  48. package/bundled-skills/maggie-marketplace/SKILL.md +91 -0
  49. package/bundled-skills/maggie-ops/SKILL.md +37 -0
  50. package/bundled-skills/maggie-ops/references/notifications-operations.md +154 -0
  51. package/bundled-skills/maggie-qa-workflow/SKILL.md +41 -1
  52. package/bundled-skills/maggie-service-booking/SKILL.md +82 -0
  53. package/bundled-skills/maggie-social-share/SKILL.md +56 -7
  54. package/bundled-skills/maggie-social-share/references/marketing-run.md +57 -0
  55. package/bundled-skills/maggie-social-share/references/queue-and-connections.md +152 -0
  56. package/bundled-skills/maggie-template/SKILL.md +57 -3
  57. package/bundled-skills/maggie-template/references/template-content-v1.example.json +34 -0
  58. package/bundled-tools/clis/maggie_booking.py +199 -26
  59. package/bundled-tools/clis/maggie_booking_vouchers.py +185 -0
  60. package/bundled-tools/clis/maggie_clone_to_template.py +14 -0
  61. package/bundled-tools/clis/maggie_deployment.py +307 -18
  62. package/bundled-tools/clis/maggie_deployment_auth_canary.py +216 -0
  63. package/bundled-tools/clis/maggie_deployment_collect.py +225 -0
  64. package/bundled-tools/clis/maggie_deployment_readiness.py +53 -3
  65. package/bundled-tools/clis/maggie_design.py +17 -0
  66. package/bundled-tools/clis/maggie_design_lint.py +277 -0
  67. package/bundled-tools/clis/maggie_design_verify.py +359 -0
  68. package/bundled-tools/clis/maggie_docs.py +82 -4
  69. package/bundled-tools/clis/maggie_email_localization.py +382 -0
  70. package/bundled-tools/clis/maggie_feedback.py +115 -5
  71. package/bundled-tools/clis/maggie_icon_glyph_check.py +382 -0
  72. package/bundled-tools/clis/maggie_icon_release_gate.py +28 -1
  73. package/bundled-tools/clis/maggie_localization.py +5 -0
  74. package/bundled-tools/clis/maggie_marketing_run.py +272 -0
  75. package/bundled-tools/clis/maggie_marketplace.py +199 -5
  76. package/bundled-tools/clis/maggie_notifications.py +528 -0
  77. package/bundled-tools/clis/maggie_ops.py +87 -0
  78. package/bundled-tools/clis/maggie_palette.py +280 -0
  79. package/bundled-tools/clis/maggie_qa_workflow.py +156 -1
  80. package/bundled-tools/clis/maggie_schedule.py +12 -2
  81. package/bundled-tools/clis/maggie_service_import.py +520 -0
  82. package/bundled-tools/clis/maggie_social_queue.py +442 -0
  83. package/bundled-tools/clis/maggie_social_share.py +46 -9
  84. package/bundled-tools/clis/maggie_template_package.py +1141 -0
  85. package/bundled-tools/runtime/google_ads.py +306 -0
  86. package/bundled-tools/runtime/google_capabilities.py +18 -2
  87. package/bundled-tools/runtime/google_remediation.py +152 -0
  88. package/package.json +1 -1
  89. package/references/browser-inspection.md +25 -0
  90. package/references/google-integrations-runbook.md +54 -0
  91. package/references/provider-adapter-contract.md +33 -0
@@ -67,6 +67,32 @@ then stores the prompt as
67
67
  `CUSTOMIZATION_PROMPT.md` beside the package. It creates a resumable job under
68
68
  `marketplace/.maggie/clone-to-template-jobs/`.
69
69
 
70
+ ### Neutral display identity
71
+
72
+ A package's customer-facing identity is separate from its source evidence.
73
+ `template.json` `name` and `id` are internal; `sourceMetadata` records the
74
+ source title only as provenance. Pass a designed, neutral style name and a short
75
+ description when planning:
76
+
77
+ ```bash
78
+ maggie tool maggie_clone_to_template.py plan <url> "<approved brief>" --rename <template-id> \
79
+ --display-name "Olive Atelier" \
80
+ --display-description "A light editorial beauty homepage with olive accents."
81
+ ```
82
+
83
+ Packaging forwards both to the marketplace importer. Without them, completion
84
+ derives a name from the measured design (accent hue, display typeface,
85
+ industry). The validator rejects a name that repeats a distinctive source-title
86
+ word, looks like a counter (`Beauty Studio 20 001`), or duplicates another
87
+ catalogue entry. Change it later with `maggie_marketplace.py identity <id>
88
+ --display-name …`. Template pickers must show `displayName` and
89
+ `displayDescription`, never the source business name.
90
+
91
+ Packaging also measures `DESIGN.md` from the rendered CSS, records
92
+ `screenshotReview`, and lists every asset with its provenance. Review captures
93
+ stay at viewport height; tile any full-page capture as described in the
94
+ `maggie-marketplace` package quality gates.
95
+
70
96
  If the full clone run fails or cannot reach `verified`, stop and report the
71
97
  clone checkpoint. Do not invoke the marketplace importer as a fallback. If the
72
98
  Maggie design review fails, stop before declaring the template ready.
@@ -2,7 +2,7 @@
2
2
  name: maggie-content-localization
3
3
  description: Manage translation, polish, rewrite, market localization, review, stale detection, and publishing for pages, guides, posts, services, products, and categories.
4
4
  metadata:
5
- version: 1.3.0
5
+ version: 1.4.0
6
6
  ---
7
7
 
8
8
  # Maggie Content Localization
@@ -70,6 +70,20 @@ Norwegian, Finnish, Greek, Hebrew, Czech, Romanian, Hungarian, Thai, Persian,
70
70
  and Swahili. Use BCP 47 regions such as `es-MX`, `pt-BR`, `ar-SA`, or `ja-JP`
71
71
  when market-specific copy is required.
72
72
 
73
+ ## Email and lifecycle email
74
+
75
+ Localized email is a content surface with its own checks. Resolve the
76
+ recipient locale as user preference > market > account > fallback; never
77
+ hard-code a locale or rely on `Accept-Language` alone in fulfilment paths.
78
+ `maggie localization email-matrix` checks missing locales, untranslated
79
+ values, changed or undeclared variables, HTML/plain-text parity, market CTA
80
+ routes, glossary terms, and approval, and can write fixture-filled previews.
81
+ `maggie localization lifecycle` validates a consent-aware marketing plan
82
+ (consent basis, unsubscribe and preference-centre paths, frequency cap,
83
+ attribution, no send until approved) and `--library` drafts the standard
84
+ template set with nothing sendable. Both are read-only and never send. See
85
+ [email and lifecycle email](references/email-and-lifecycle.md).
86
+
73
87
  ## Invocation lifecycle
74
88
 
75
89
  At the start, run the memory hook for the current skill and load only relevant
@@ -98,6 +112,8 @@ maggie tool maggie_localization.py review .maggie/localization/<job>.json \
98
112
  maggie tool maggie_localization.py publish .maggie/localization/<job>.json --confirm
99
113
  maggie tool maggie_localization.py stale --project .
100
114
  maggie tool maggie_localization.py glossary --project .
115
+ maggie tool maggie_localization.py email-matrix emails/manifest.json --preview-dir .maggie/localization/email-preview
116
+ maggie tool maggie_localization.py lifecycle emails/lifecycle.json
101
117
  ```
102
118
 
103
119
  Operations are distinct: `translate` preserves meaning, `polish` improves
@@ -0,0 +1,83 @@
1
+ # Localized email and consent-aware lifecycle email
2
+
3
+ Email is a localized content surface. Treat every template × locale × market
4
+ as a reviewable variant, and keep transactional/security messages separate
5
+ from marketing consent. Both commands below are read-only: they never call a
6
+ provider and never send.
7
+
8
+ ## Localized templates (`email-matrix`)
9
+
10
+ Describe templates in a `maggie-email-localization.v1` manifest (schema:
11
+ `contracts/maggie-content/email-localization-v1.schema.json`, shipped as
12
+ `bundled-contracts/maggie-content/` in the npm package).
13
+
14
+ Locale precedence is fixed: **user preference > market > account > fallback**.
15
+ A send path must pass the resolved recipient locale. Two patterns fail:
16
+
17
+ - a hard-coded locale (e.g. a fulfilment path that always renders `en`);
18
+ - `Accept-Language` as the only source for fulfilment or transactional email.
19
+ For other paths it is only a hint and produces a warning.
20
+
21
+ Declare each send path under `callSites` with its `localeSource` so the check
22
+ covers code paths, not only templates.
23
+
24
+ The check fails when:
25
+
26
+ - a required locale or its subject/HTML/plain text is missing;
27
+ - a translated field is identical to the source text (untranslated);
28
+ - variables differ from the source field, are undeclared, or differ between
29
+ the HTML and plain-text bodies;
30
+ - a CTA route is not a site path or `https` URL, uses a market whose prefix it
31
+ does not carry, targets a market that does not serve that locale, or changes
32
+ the template route's parameters.
33
+
34
+ It warns about glossary terms not rendered as their approved translation,
35
+ CTA routes missing from a body, unused variables, and unapproved variants.
36
+ A variant is publishable only when its `approval` has `status: approved` and a
37
+ `reviewer`; `--require-approved` turns that into a failure for release gates.
38
+
39
+ ```bash
40
+ maggie localization email-matrix emails/manifest.json \
41
+ --preview-dir .maggie/localization/email-preview
42
+ maggie localization email-matrix emails/manifest.json --require-approved
43
+ ```
44
+
45
+ `--preview-dir` writes one `.html` and one `.txt` file per template × locale,
46
+ filled from the template's synthetic `fixtures` (HTML-escaped) and labelled as
47
+ a preview. Never put customer data in fixtures.
48
+
49
+ ## Lifecycle and marketing email (`lifecycle`)
50
+
51
+ A `maggie-lifecycle-email.v1` plan (schema:
52
+ `contracts/maggie-content/lifecycle-email-v1.schema.json`) runs through fixed
53
+ stages: plan → segment → consent-check → preview → approve → schedule → send →
54
+ report. `defaults.send` must be `disabled`: nothing is sent until approved.
55
+
56
+ Start from the library, which drafts every kind with nothing sendable:
57
+
58
+ ```bash
59
+ maggie localization lifecycle --library > emails/lifecycle.json
60
+ maggie localization lifecycle emails/lifecycle.json
61
+ ```
62
+
63
+ Library kinds: opt-in confirmation, welcome, digest, recommendation, gift /
64
+ voucher / waitlist follow-up, merchant lead digest, campaign announcement,
65
+ re-engagement.
66
+
67
+ Rules the validator enforces:
68
+
69
+ - Marketing templates need `consent.basis` (opt-in, double-opt-in,
70
+ soft-opt-in, or legitimate-interest-b2b) and a preference-centre
71
+ `consent.category`, an unsubscribe path, a preference-centre path, and
72
+ attribution fields (source/UTM) so outcomes can be measured.
73
+ - Every template has a frequency cap (`maxPerWindow` + ISO 8601 `window`).
74
+ - The opt-in confirmation is sent before consent exists, so it must be
75
+ transactional and carry no promotional content.
76
+ - `sendable: true` requires an approval with `status: approved`, `reviewer`
77
+ and `approvedAt` — on the template and on the campaign.
78
+ - Campaigns using marketing templates require consent, must honour
79
+ suppression, must deduplicate contacts, and need a schedule before they are
80
+ sendable. Declare aggregate report metrics; never report per-recipient data.
81
+
82
+ The host owns consent records, segments, suppression lists, delivery and
83
+ reporting. Maggie validates the plan; it does not send or store addresses.
@@ -2,7 +2,7 @@
2
2
  name: maggie-deployment
3
3
  description: Deploy and operate Maggie blog projects with Cloudflare Workers as the default target, while preserving an adapter boundary for VPS, GCP, and AWS.
4
4
  metadata:
5
- version: 1.4.0
5
+ version: 1.6.0
6
6
  ---
7
7
 
8
8
  # Maggie Deployment
@@ -88,6 +88,74 @@ not fabricate browser, rendered, or deployment evidence and does not deploy
88
88
  from this command. See
89
89
  [`readiness-v1.schema.json`](../../bundled-contracts/maggie-deployment/readiness-v1.schema.json).
90
90
 
91
+ ### Collect host evidence
92
+
93
+ Instead of hand-written SSH, systemd, and retention scripts per host, declare
94
+ the standard adapters once in `.maggie/deployment/collector.json`
95
+ (`maggie-evidence-collector.v1`) and let one command run them and emit the
96
+ same `maggie-deployment-readiness.v1` report:
97
+
98
+ ```json
99
+ {
100
+ "schemaVersion": "maggie-evidence-collector.v1",
101
+ "release": "2026-10-10-a",
102
+ "adapters": {
103
+ "canary": {"assets": ["https://example.test/_astro/app.js=<sha256>"], "renderReport": ".maggie/rendered-canary.json"},
104
+ "health": {"urls": ["https://example.test/robots.txt", "https://example.test/sitemap.xml"]},
105
+ "rollback": {"evidence": "docs/deployment-rollback-smoke.json", "plan": ".maggie/deployment/vps/deployment-plan.json"},
106
+ "releaseIdentity": {"command": ["ssh", "-o", "BatchMode=yes", "maggie-deploy@host", "readlink -f /var/www/example/current"]},
107
+ "authCanary": {"plan": ".maggie/deployment/auth-canary-plan.json"}
108
+ }
109
+ }
110
+ ```
111
+
112
+ ```bash
113
+ maggie deployment collect --project . --config .maggie/deployment/collector.json
114
+ ```
115
+
116
+ Adapters write their evidence under `.maggie/deployment/collected/` and the
117
+ readiness report adds the optional `hostHealth`, `releaseIdentity`,
118
+ `rollbackEvidence`, and `authenticatedCanary` slots next to the five required
119
+ slots. `releaseIdentity` proves the host's `current` release (from an argv
120
+ command such as `readlink -f`, or a `{"url", "field"}` version endpoint)
121
+ equals the expected `release`. Commands are argv lists, never shell strings,
122
+ and are rejected if they contain a literal credential. The report records the
123
+ config SHA-256, the release, and per-adapter run metadata so it can be
124
+ reproduced; it never deploys.
125
+
126
+ ### Authenticated API canaries
127
+
128
+ Public canaries cannot see failures on signed-in account or business APIs
129
+ (database permission errors, schema mismatches, 403s). Declare role-scoped
130
+ probes in a `maggie-auth-canary-plan.v1` plan:
131
+
132
+ ```json
133
+ {
134
+ "schemaVersion": "maggie-auth-canary-plan.v1",
135
+ "baseUrl": "https://example.test",
136
+ "roles": {"owner": {"tokenEnv": "CANARY_OWNER_TOKEN"}, "staff": {"cookieEnv": "CANARY_STAFF_COOKIE"}},
137
+ "probes": [
138
+ {"id": "account", "role": "owner", "path": "/api/account", "expectJsonKeys": ["id", "role"]},
139
+ {"id": "business", "role": "staff", "path": "/api/business", "expectStatus": [200]},
140
+ {"id": "fixture-create", "role": "owner", "method": "POST", "path": "/api/canary-fixtures", "fixture": "disposable", "expectStatus": [201]}
141
+ ]
142
+ }
143
+ ```
144
+
145
+ ```bash
146
+ CANARY_OWNER_TOKEN=... maggie deployment auth-canary \
147
+ --plan .maggie/deployment/auth-canary-plan.json --output .maggie/deployment/auth-canary.json
148
+ ```
149
+
150
+ Credentials come only from the named environment variables of dedicated
151
+ canary accounts; a plan containing a literal token or cookie is rejected.
152
+ Mutating probes must target a `disposable` fixture and run only with
153
+ `--allow-mutations`. Evidence (`maggie-deployment-auth-canary.v1`) records
154
+ probe id, role, method, path, status, and contract-key counts, classifying
155
+ failures as `authorization`, `server`, `route`, `contract`, or `network`;
156
+ it never stores headers, cookies, tokens, response bodies, or personal data.
157
+ A missing credential makes the probe `inconclusive`, not passed.
158
+
91
159
  Classify host/runtime failures before a release can move:
92
160
 
93
161
  ```bash
@@ -207,7 +275,7 @@ The output contains only the immutable release/current layout, systemd unit,
207
275
  Nginx reverse-proxy config, health commands, rollback command, and an ordered
208
276
  reviewable release runner. The runner keeps migrations before the symlink flip,
209
277
  carries `.agents`/`.claude` state, restarts before verification, and prunes to
210
- the current release plus one rollback candidate. It never
278
+ the plan's retention policy (default: current plus one rollback candidate). It never
211
279
  contains credentials and never SSHs, changes DNS, restarts systemd or deploys.
212
280
 
213
281
  VPS plans use a dedicated deployer account by default:
@@ -225,9 +293,23 @@ when they are unmarked or stale; use `--check-runner PATH` for a runner stored
225
293
  elsewhere. Regenerate from the reviewed VPS plan, inspect the diff, and replace
226
294
  the checked-in runner only after review.
227
295
 
228
- VPS plans include a bounded retention policy: keep two immutable releases,
229
- preserve the `current` target and rollback target, and review prune candidates
230
- before any operator executes cleanup. Generate a read-only candidate report:
296
+ VPS plans carry one explicit retention policy, `--keep-releases N` (1-5,
297
+ default 2). The same value drives the plan's prune command, the generated
298
+ runner's prune step, `--retention-plan`, preflight, and rollback evidence, so
299
+ Maggie never advertises a rollback target the host deletes:
300
+
301
+ - `N >= 2` keeps the `current` target plus the release it replaced; the plan's
302
+ `commands.rollback` switches back to it.
303
+ - `N = 1` keeps only the active release. The plan reports
304
+ `retention.rollbackAvailable: false` and `commands.rollback: null`, the
305
+ runner prints `rollback target: unavailable`, and production rollback
306
+ evidence must declare `codeRollback: "unavailable"` with a `recoveryPlan`
307
+ (redeploy the previous commit as a new release and restore its data
308
+ checkpoint). Preflight's `rollback_target_retained` check fails when a plan
309
+ keeps one release but still advertises a previous-release rollback.
310
+
311
+ Review prune candidates before any operator executes cleanup. Generate a
312
+ read-only candidate report:
231
313
 
232
314
  ```bash
233
315
  maggie tool maggie_deployment.py --retention-plan \
@@ -236,8 +318,60 @@ maggie tool maggie_deployment.py --retention-plan \
236
318
  --keep-releases 2 --output .maggie/deployment/retention-plan.json
237
319
  ```
238
320
 
239
- The CLI never deletes release directories and never permits fewer than two
240
- retained releases.
321
+ The CLI never deletes release directories; only the reviewed runner prunes,
322
+ and only after health checks pass.
323
+
324
+ ### Multi-host secret parity
325
+
326
+ When several hosts serve the same routes, check that each one holds every
327
+ required secret. The check compares key names and SHA-256 fingerprints and
328
+ never prints, stores, or transmits values:
329
+
330
+ ```bash
331
+ # Local env files (one per host):
332
+ maggie deployment secret-parity \
333
+ --host api-1=.secrets/api-1.env --host api-2=.secrets/api-2.env \
334
+ --required SOCIAL_SHARE_ENCRYPTION_KEY
335
+
336
+ # Or fingerprint manifests produced on each host:
337
+ maggie deployment secret-fingerprint --env-file /etc/example.env \
338
+ --host api-1 --output api-1.json
339
+ maggie deployment secret-parity --manifest api-1.json --manifest api-2.json \
340
+ --required SOCIAL_SHARE_ENCRYPTION_KEY
341
+ ```
342
+
343
+ A required key that is missing or differs on any host fails the check. Other
344
+ drifting keys are warnings unless `--strict` is set. A host without Maggie can
345
+ use the no-echo shell snippet in [the VPS reference](references/vps.md); the
346
+ parity command accepts its `KEY <sha256>` output directly. Manifests that
347
+ contain anything other than names and 64-character hex digests are rejected.
348
+
349
+ ### Local and production database isolation
350
+
351
+ ```bash
352
+ maggie deployment env-isolation --project . \
353
+ --production-env .env.production
354
+ # or: --production-db-host db.example.net
355
+ ```
356
+
357
+ The check fails when the local `.env` points at the production database (same
358
+ host, and port/name when known). The same server with a different database is
359
+ a warning. Deployment preflight runs the check automatically, as a non-blocking
360
+ warning, when `.env.production` exists or `MAGGIE_PRODUCTION_DB_HOST` is set.
361
+ Database URLs are reduced to host/port; credentials are never reported.
362
+
363
+ ### Release ordering for workers and notification templates
364
+
365
+ Deploy the worker and its templates before any code path can enqueue an event
366
+ that uses a new template. When local development or a staged app shares a
367
+ queue with production, an event that names a template the running worker does
368
+ not know yet must be held and retried, not dead-lettered: an unknown template
369
+ during a rollout is a release-ordering state, not a permanent failure. Check
370
+ that the outbox worker (MaggieDash Booking or the host's own) does not let
371
+ unknown-template deliveries consume the retry budget, and after the release
372
+ confirm the held deliveries were sent.
373
+ If dead-lettered rows already exist, requeue them only after the worker that
374
+ knows the template is live.
241
375
 
242
376
  Validate the database release separately before a VPS migration:
243
377
 
@@ -121,6 +121,29 @@ before staging migration and require a successful restore test before a
121
121
  production release. Keep the backup artifact and migration version in the
122
122
  handover record; never put a database URL or password in the manifest.
123
123
 
124
+ ### Running migrations as the database owner
125
+
126
+ `sudo -u postgres psql -f /home/ubuntu/site/releases/<release>/db/073.sql`
127
+ fails with `Permission denied` when the release lives under a deploy user's
128
+ home: the `postgres` account cannot traverse that path. Do not widen
129
+ filesystem permissions. Stream the file from a root-readable read into the
130
+ owner-level `psql` instead (the generated plan's `commands.migrate_as_db_owner`):
131
+
132
+ ```bash
133
+ sudo cat /home/ubuntu/site/releases/<release>/db/073.sql \
134
+ | sudo -u postgres psql -v ON_ERROR_STOP=1 --single-transaction -d <database> -f -
135
+ ```
136
+
137
+ If a file path is required, copy it to a private temporary file readable only
138
+ by the owner and remove it afterwards:
139
+
140
+ ```bash
141
+ tmp=$(sudo -u postgres mktemp) && sudo cp <migration.sql> "$tmp" \
142
+ && sudo chown postgres: "$tmp" && sudo chmod 600 "$tmp" \
143
+ && sudo -u postgres psql -v ON_ERROR_STOP=1 -d <database> -f "$tmp"; \
144
+ sudo rm -f "$tmp"
145
+ ```
146
+
124
147
  ## Release workflow
125
148
 
126
149
  ```text
@@ -132,6 +155,56 @@ inspect → plan → backup/migrate → npm ci → npm run build
132
155
  Deploy staging first. A passing build does not prove Nginx, systemd, DNS, TLS,
133
156
  robots, sitemap, or private-route correctness.
134
157
 
158
+ ### Release retention
159
+
160
+ The retention policy is one explicit number, `--keep-releases N` (default 2),
161
+ shared by the plan, the runner, `--retention-plan`, preflight, and rollback
162
+ evidence. The default keeps the `current` release plus one previous release as
163
+ the rollback target. `N = 1` is allowed for hosts that intentionally keep only
164
+ the active release, but then no on-disk rollback exists: the plan reports
165
+ `rollbackAvailable: false`, the runner says so after pruning, and rollback
166
+ evidence must name a recovery plan (redeploy the previous commit, restore the
167
+ data checkpoint) instead of a previous release.
168
+
169
+ Prune after every deploy, and only after the new release passed its health
170
+ checks: an unhealthy release must still have its predecessor on disk. The
171
+ generated release runner does this as its final step; it never removes the
172
+ release `current` points at, and with `N >= 2` it also keeps the release it
173
+ replaced. Without pruning,
174
+ `releases/` grows by one full `node_modules` tree per deploy until the disk
175
+ fills. Use `--retention-plan` for a read-only list of prune candidates on hosts
176
+ whose runner predates this step, and review it before deleting anything.
177
+
178
+ ### Multi-host secret parity
179
+
180
+ When more than one host can serve the same routes (failover, load-balanced API
181
+ nodes, workers), every host must hold the same required secrets. A key that
182
+ exists on only one host is invisible until traffic fails over to another and
183
+ returns 5xx. Compare hosts by name and SHA-256 fingerprint, never by value:
184
+
185
+ ```bash
186
+ # On each host (prints names and digests only; values are never echoed):
187
+ for k in SOCIAL_SHARE_ENCRYPTION_KEY DATABASE_URL; do
188
+ v=$(sed -n "s/^$k=//p" /etc/example.env | head -1)
189
+ printf '%s %s\n' "$k" "$(printf %s "$v" | sha256sum | cut -c1-64)"
190
+ done > /tmp/api-1.fingerprints
191
+
192
+ # Locally, after copying each host's fingerprint file:
193
+ maggie deployment secret-parity \
194
+ --manifest api-1.fingerprints --manifest api-2.fingerprints \
195
+ --manifest api-3.fingerprints \
196
+ --required SOCIAL_SHARE_ENCRYPTION_KEY --required DATABASE_URL
197
+ ```
198
+
199
+ Run it after adding a secret and before enabling a host in a pool.
200
+
201
+ ### Environment isolation
202
+
203
+ A local `.env` must never point at the production database. Shared databases
204
+ let local test records (vouchers, palettes, emails) appear on live pages and be
205
+ picked up by production workers. `maggie deployment env-isolation` fails when
206
+ the local database host, port, and name match production.
207
+
135
208
  ## Verification and rollback
136
209
 
137
210
  Verify the homepage, changed service routes, `/robots.txt`, `/sitemap.xml`,
@@ -139,7 +212,7 @@ canonical URLs, booking links, and protected Ops/API routes. Check both apex
139
212
  and `www` when configured. Record HTTP status, commit, and release.
140
213
 
141
214
  Rollback switches `current` to the previous immutable release and restarts the
142
- service. If migrations are not backward-compatible, stop before deployment and
215
+ service, when the retention policy keeps one. If migrations are not backward-compatible, stop before deployment and
143
216
  provide a tested restore or database rollback plan. The first production
144
217
  release may declare rollback as not applicable only with `releaseKind: "first"`,
145
218
  a non-empty `notApplicableReason`, and `RELEASE_KIND=first`; upgrades still
@@ -84,6 +84,61 @@ a successful `assetStatus` (2xx), and a non-empty accessible name. This
84
84
  gate is independent from typecheck/build and is read-only apart from its
85
85
  explicit output.
86
86
 
87
+ ### Rendered glyph proof
88
+
89
+ Collect that browser evidence with the shared collector instead of a
90
+ hand-written report. It renders each route at mobile, tablet, and desktop in
91
+ headless Chrome — through a local GET-only proxy for a running host
92
+ (`--base-url`) or directly from a static build (`--dist`) — and records, per
93
+ representative icon, whether its icon font reached `document.fonts` status
94
+ `loaded`, whether the glyph paints ink that differs from the browser fallback
95
+ font (a missing glyph falls back or paints blank), whether runtime CSS hides
96
+ it (`display`, `visibility`, `opacity`, zero size, or a hidden pseudo-element),
97
+ its font family, accessible name, and the served font asset status:
98
+
99
+ ```bash
100
+ maggie design icon-glyph-check --base-url http://localhost:4321 \
101
+ --route / --route /contact --family ph=Phosphor \
102
+ --output .maggie/icon-browser-evidence.json
103
+ ```
104
+
105
+ `--family PREFIX=FAMILY` asserts the expected visual family for a class
106
+ prefix. Evidence from this collector makes `icon-release-gate` also require
107
+ `fontLoaded`, `glyphPainted`, matching family, and an icon at every declared
108
+ viewport; hand-written evidence still passes the original checks but the gate
109
+ reports `glyphIdentityProven: false` with a warning. The collector forwards GET
110
+ requests only and never stores page text beyond icon names and labels.
111
+
112
+ ## Design job verification
113
+
114
+ `design step` records evidence one step at a time. `design verify` runs every
115
+ step that can be proven automatically against a host adapter and records it
116
+ with durable evidence under `.maggie/design-evidence/<job>/`:
117
+
118
+ ```bash
119
+ # section-scoped job: snapshot before editing, then verify after
120
+ maggie design verify section-<id> --project . --base-url http://localhost:4321 --stage before
121
+ maggie design verify section-<id> --project . --base-url http://localhost:4321
122
+
123
+ # in-place or author job against a static build, including the host build
124
+ maggie design verify in-place-<id> --project . --dist dist --build-command "npm run build"
125
+ ```
126
+
127
+ Automated steps: `inspect-route`/`inspect-shell`/`inspect-section` (2xx HTML,
128
+ shell landmarks, target section present), `declare-content-source` (no
129
+ unproven catch-all resolution), `capture-before`/`capture-after` (per-section
130
+ structure hashes from `<section id>` or `data-section-id`),
131
+ `assert-unchanged-rest`, `capture-responsive-screenshots` (390/768/1440 PNGs
132
+ with hashes), `accessibility-check` (lang, one h1, alt text, named controls,
133
+ labelled fields, `<main>`), `route-validation` (2xx, title, h1, no template
134
+ placeholders, no accidental `noindex`), and `build-check` only with an explicit
135
+ `--build-command`. A failed check leaves its step pending with `lastCheck`
136
+ evidence. `apply-design-contract`, `implement-*`, `visual-review`, and
137
+ `approval` are never completed by `verify`; record them with `design step` and
138
+ real implementer or reviewer evidence, so a job cannot reach `done` without
139
+ evidence. Client-rendered sections need host-adapter evidence through
140
+ `design step` when the server HTML does not contain them.
141
+
87
142
  ## Hero video playback contract
88
143
 
89
144
  Hero video is an enhancement, not the only way to see the first-band message.
@@ -285,6 +340,57 @@ all required camera states, and valid PNGs at 390px, 768px, and desktop
285
340
  widths. The machine-readable contract is
286
341
  [mobile-app-surface-v1.schema.json](../../bundled-contracts/maggie-design/mobile-app-surface-v1.schema.json).
287
342
 
343
+ ### Onboarding wizards
344
+
345
+ Use one pattern for multi-step setup (brand, logo, voice, posting hours, and
346
+ similar first-run flows) instead of designing each flow ad hoc:
347
+
348
+ - A segmented progress bar with one segment per step; the current step is
349
+ visibly distinct and completed steps are tappable.
350
+ - Each step shows Maggie (or the product guide) with exactly one headline and,
351
+ at most, one supporting line. Put help in the control, not in paragraphs.
352
+ - Record step state on the server (`pending`, `done`, `skipped`) after every
353
+ step, and resume from the first unfinished step on any device. Local state
354
+ alone loses progress on reinstall or a second device.
355
+ - Every optional step offers **Later**. A skipped step reopens on the next
356
+ sign-in until it is done or explicitly dismissed; never block the app on it.
357
+ - Completed steps keep a redo action (for example "Retrain voice" or
358
+ "Choose another logo") that reopens that step with the saved value
359
+ preselected, without restarting the wizard.
360
+ - The primary action stays in the fixed footer; Back never discards saved
361
+ server state.
362
+
363
+ ### Pickers with previews
364
+
365
+ For choices that need to be seen (logo styles, templates, palettes, voices):
366
+
367
+ - The list sheet shows a thumbnail, name, and selection state per row; the
368
+ row itself selects, and a separate preview control opens a larger view.
369
+ - The large preview opens as a sheet nested in the list sheet, so dismissing
370
+ it returns to the same scroll position in the list. It offers **Use this**
371
+ and close; it never replaces the list route.
372
+ - Thumbnails are generated or cached assets, not live renders of every option.
373
+
374
+ ### React Native Web pressables
375
+
376
+ React Native Web renders `Pressable` and the `Touchable*` family as button-like
377
+ DOM elements. A chip with a nested remove control becomes `<button>` inside
378
+ `<button>`: the DOM is invalid, browsers repair it differently, and taps land
379
+ on the wrong control (for example, removing a tag opens its preview). Render
380
+ the chip body and its remove control as **sibling** pressables inside a plain
381
+ `View`, each with its own accessible label. The same rule applies to `<a>`
382
+ and `<button>` on the web.
383
+
384
+ Run the static check before browser QA; it scans `.tsx`/`.jsx`, skips build
385
+ and dependency folders, and treats `asChild` wrappers (such as an expo-router
386
+ `Link`) as a pass-through:
387
+
388
+ maggie design pressable-check --project . [--pressable Chip --pressable IconButton]
389
+
390
+ Pass `--pressable` for project components that render a pressable. A rare,
391
+ reviewed exception can carry `maggie-allow-nested-pressable` in a comment on
392
+ the inner element's line.
393
+
288
394
  ## Explicit homepage rebrand mode
289
395
 
290
396
  The homepage `review` mode only compares screenshots. It does not rebrand a
@@ -543,6 +649,54 @@ collapsed-rail, mobile-drawer, and modal evidence. Sample plans require
543
649
  noindex, sitemap exclusion, a visible sample banner, placeholders, and a
544
650
  finish checklist.
545
651
 
652
+ ### Tailwind v4 cascade layers
653
+
654
+ Tailwind v4 emits utilities inside `@layer utilities`. An unlayered rule beats
655
+ every layered rule regardless of specificity, so an unlayered
656
+ `h1, h2, h3 { font-family: … }` silently defeats template `font-*` utilities,
657
+ and an unlayered icon runtime rule such as `.ph { display: inline-block }`
658
+ defeats `hidden` and `group-open:inline` (a mobile menu then shows both the
659
+ open and close icons). Static class checks pass; only a browser shows it.
660
+
661
+ - Put app-wide element rules (headings, links, body copy) in `@layer base`.
662
+ - Put icon-font runtime rules and component classes in `@layer components`,
663
+ or import the icon stylesheet with `@import "./icons.css" layer(components);`.
664
+ - Keep `@theme`, `@utility`, and `@font-face` as they are; they are not
665
+ competing rules.
666
+
667
+ Run the static check whenever CSS or the icon package changes:
668
+
669
+ maggie design layer-check --project .
670
+
671
+ It runs only when an `@import "tailwindcss"` entry exists (or with
672
+ `--tailwind-v4`), honours `@layer` blocks and `layer(...)` imports, and fails on
673
+ unlayered element or icon rules that set utility-competing properties.
674
+ Unlayered `html`/`body` rules and class rules that set `display` are reported
675
+ as warnings. Keep `css-cascade-check` for the rendered-browser proof.
676
+
677
+ ### Palette role contract
678
+
679
+ A template that offers brand colour choices exposes semantic roles instead of
680
+ raw hex values in markup: `canvas`, `panel`, `soft`, `text`, `accent`,
681
+ `accentText`, `onAccent`, `dark`, and `deep`. Bind them to CSS variables (for
682
+ example `--color-canvas`) so a palette swap never edits markup. The generator
683
+ keeps the signature palette and derives named variants by moving hue while
684
+ keeping lightness; neutrals never gain chroma, accent saturation is capped
685
+ (uncapped variants turn garish), `accentText` is forced to 4.5:1 on canvas,
686
+ and `onAccent` is the most legible of white, deep, text, and canvas:
687
+
688
+ maggie design palette generate --template <template-id> \
689
+ --canvas '#F7F3EE' --text '#1F1B16' --accent '#B5651D' \
690
+ [--roles roles.json] [--count 17] [--max-saturation 0.65] \
691
+ --output design/palettes.json
692
+ maggie design palette validate --palette design/palettes.json
693
+
694
+ Measure the signature roles from the template's rendered CSS, not from a
695
+ reference palette. Generation stops if the signature itself fails the
696
+ contrast rules; fix the signature rather than lowering the threshold. The
697
+ contract is
698
+ [palette-roles-v1.schema.json](../../bundled-contracts/maggie-design/palette-roles-v1.schema.json).
699
+
546
700
  For generated or converted pages, run the markup gate before handing the page
547
701
  to a host editor:
548
702