@topy-ai/maggie 0.7.48 → 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.
- package/README-zh-TW.md +2 -2
- package/README.md +31 -4
- package/bin/maggie.js +17 -4
- package/bundled-contracts/google-integrations/capability-report-v2.schema.json +2 -1
- package/bundled-contracts/maggie-content/email-localization-v1.schema.json +89 -0
- package/bundled-contracts/maggie-content/lifecycle-email-v1.schema.json +76 -0
- package/bundled-contracts/maggie-deployment/readiness-v1.schema.json +6 -1
- package/bundled-contracts/maggie-ops/marketing-run-v1.schema.json +89 -0
- package/bundled-contracts/maggie-ops/notification-catalogue-example-v1.json +941 -0
- package/bundled-contracts/maggie-ops/notification-catalogue-v1.schema.json +273 -0
- package/bundled-contracts/maggie-service-booking/fact-provenance-v1.schema.json +128 -0
- package/bundled-contracts/maggie-service-booking/import-stages-v1.schema.json +191 -0
- package/bundled-references/google-integrations-runbook.md +54 -0
- package/bundled-skills/maggie-content-localization/SKILL.md +17 -1
- package/bundled-skills/maggie-content-localization/references/email-and-lifecycle.md +83 -0
- package/bundled-skills/maggie-deployment/SKILL.md +89 -7
- package/bundled-skills/maggie-deployment/references/vps.md +38 -6
- package/bundled-skills/maggie-design/SKILL.md +55 -0
- package/bundled-skills/maggie-feedback/SKILL.md +29 -0
- package/bundled-skills/maggie-ops/SKILL.md +37 -0
- package/bundled-skills/maggie-ops/references/notifications-operations.md +154 -0
- package/bundled-skills/maggie-service-booking/SKILL.md +59 -0
- package/bundled-skills/maggie-social-share/SKILL.md +19 -2
- package/bundled-skills/maggie-social-share/references/marketing-run.md +57 -0
- package/bundled-skills/maggie-social-share/references/queue-and-connections.md +51 -0
- package/bundled-tools/clis/maggie_deployment.py +58 -17
- package/bundled-tools/clis/maggie_deployment_auth_canary.py +216 -0
- package/bundled-tools/clis/maggie_deployment_collect.py +225 -0
- package/bundled-tools/clis/maggie_deployment_readiness.py +53 -3
- package/bundled-tools/clis/maggie_design.py +8 -0
- package/bundled-tools/clis/maggie_design_verify.py +359 -0
- package/bundled-tools/clis/maggie_docs.py +82 -4
- package/bundled-tools/clis/maggie_email_localization.py +382 -0
- package/bundled-tools/clis/maggie_feedback.py +9 -2
- package/bundled-tools/clis/maggie_icon_glyph_check.py +382 -0
- package/bundled-tools/clis/maggie_icon_release_gate.py +28 -1
- package/bundled-tools/clis/maggie_localization.py +5 -0
- package/bundled-tools/clis/maggie_marketing_run.py +272 -0
- package/bundled-tools/clis/maggie_notifications.py +528 -0
- package/bundled-tools/clis/maggie_ops.py +87 -0
- package/bundled-tools/clis/maggie_schedule.py +12 -2
- package/bundled-tools/clis/maggie_service_import.py +520 -0
- package/bundled-tools/clis/maggie_social_queue.py +104 -0
- package/bundled-tools/clis/maggie_social_share.py +6 -2
- package/bundled-tools/runtime/google_ads.py +306 -0
- package/bundled-tools/runtime/google_capabilities.py +18 -2
- package/bundled-tools/runtime/google_remediation.py +152 -0
- package/package.json +1 -1
- package/references/google-integrations-runbook.md +54 -0
|
@@ -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.
|
|
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
|
|
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
|
|
229
|
-
|
|
230
|
-
|
|
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,8 @@ 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
|
|
240
|
-
|
|
321
|
+
The CLI never deletes release directories; only the reviewed runner prunes,
|
|
322
|
+
and only after health checks pass.
|
|
241
323
|
|
|
242
324
|
### Multi-host secret parity
|
|
243
325
|
|
|
@@ -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
|
|
@@ -134,11 +157,20 @@ robots, sitemap, or private-route correctness.
|
|
|
134
157
|
|
|
135
158
|
### Release retention
|
|
136
159
|
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
release
|
|
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,
|
|
142
174
|
`releases/` grows by one full `node_modules` tree per deploy until the disk
|
|
143
175
|
fills. Use `--retention-plan` for a read-only list of prune candidates on hosts
|
|
144
176
|
whose runner predates this step, and review it before deleting anything.
|
|
@@ -180,7 +212,7 @@ canonical URLs, booking links, and protected Ops/API routes. Check both apex
|
|
|
180
212
|
and `www` when configured. Record HTTP status, commit, and release.
|
|
181
213
|
|
|
182
214
|
Rollback switches `current` to the previous immutable release and restarts the
|
|
183
|
-
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
|
|
184
216
|
provide a tested restore or database rollback plan. The first production
|
|
185
217
|
release may declare rollback as not applicable only with `releaseKind: "first"`,
|
|
186
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.
|
|
@@ -70,6 +70,15 @@ with one shared correlation ID instead of losing their relationship:
|
|
|
70
70
|
|
|
71
71
|
maggie feedback batch --project . --batch-file ./feedback-batch.json
|
|
72
72
|
|
|
73
|
+
For long cross-feature reviews, prefer the batch file over repeated `collect`
|
|
74
|
+
calls: JSON strings carry multiline text, backticks, and command names without
|
|
75
|
+
shell escaping. `--batch-file -` reads the same JSON from stdin, so a quoted
|
|
76
|
+
heredoc (`<<'EOF'`) never interpolates command substitutions:
|
|
77
|
+
|
|
78
|
+
maggie feedback batch --project . --batch-file - <<'EOF'
|
|
79
|
+
{"batchId": "review-2026-10", "skill": "maggie-deployment", "items": [...]}
|
|
80
|
+
EOF
|
|
81
|
+
|
|
73
82
|
The batch file contains 1–50 items and may provide shared `skill`, `runId`,
|
|
74
83
|
`phase`, `priority`, and `affectedCli` values. An item can override those
|
|
75
84
|
fields and supplies the normal `type`, `summary`, `expected`, `actual`,
|
|
@@ -155,6 +164,26 @@ skill or CLI, and replace every `REVIEW:` field (expected, actual, validation).
|
|
|
155
164
|
placeholders. Then continue with `batch`, `batch-review`, and explicit
|
|
156
165
|
`submit` as usual.
|
|
157
166
|
|
|
167
|
+
## Check release and rollout claims before reporting "fixed"
|
|
168
|
+
|
|
169
|
+
A fixed report is only as good as the release state it points to. Two
|
|
170
|
+
read-only checks catch the drift that previous reports had to find by hand:
|
|
171
|
+
|
|
172
|
+
python3 scripts/validate_docs_freshness.py --release-metadata
|
|
173
|
+
maggie docs rollout-status --project .
|
|
174
|
+
|
|
175
|
+
The first (Maggie source repository) fails when `VERSION`, the CLI package
|
|
176
|
+
version, the newest `CHANGELOG.md` heading, and the README current-version text
|
|
177
|
+
disagree, so a published version cannot ship without its release notes. The
|
|
178
|
+
second scans `docs/` for rollout item IDs (default pattern `ABC-12`, override
|
|
179
|
+
with `--id-pattern`) and fails when one document says an item is complete while
|
|
180
|
+
another says it is pending or in progress. When `docs/rollout-status.json`
|
|
181
|
+
(`maggie-rollout-status.v1`: `items[]` with `id`, `status` of `planned`,
|
|
182
|
+
`in_progress`, `blocked`, or `complete`, and `evidence` for complete items)
|
|
183
|
+
exists, it is canonical and every document must agree with it. Mark superseded
|
|
184
|
+
documents with `<!-- documentation-status: historical-snapshot -->` instead of
|
|
185
|
+
leaving contradictory claims current.
|
|
186
|
+
|
|
158
187
|
## Submit explicitly
|
|
159
188
|
|
|
160
189
|
The CLI never submits automatically. After reviewing the draft:
|
|
@@ -96,10 +96,47 @@ prints these safe identifiers and scopes before any separate provider adapter
|
|
|
96
96
|
is allowed to perform a write. A v1 report is rejected because it cannot prove
|
|
97
97
|
that the browser/session is on the intended Google account and property.
|
|
98
98
|
|
|
99
|
+
When a check fails, add an optional `failure` (`code`, `reason`, `httpStatus`;
|
|
100
|
+
never a token or payload) to the row. The validator maps it to one failed
|
|
101
|
+
prerequisite and one safe next action under `remediation`. For a single error,
|
|
102
|
+
`maggie ops google-diagnose --code SERVICE_DISABLED --http-status 403` prints the
|
|
103
|
+
same mapping.
|
|
104
|
+
|
|
105
|
+
For Google Ads, run the read-only adapter before any keyword research:
|
|
106
|
+
|
|
107
|
+
```bash
|
|
108
|
+
maggie ops google-ads-preflight --customer-id 123-456-7890 \
|
|
109
|
+
[--login-customer-id 999-888-7777] [--probe-keyword-planner]
|
|
110
|
+
maggie ops google-ads-keyword-ideas --customer-id 123-456-7890 \
|
|
111
|
+
--keyword "day spa" --language 1000 --geo-target 2826 --currency GBP
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
Credentials come from `GOOGLE_ADS_ACCESS_TOKEN` and
|
|
115
|
+
`GOOGLE_ADS_DEVELOPER_TOKEN` (names overridable); values are never printed.
|
|
116
|
+
The adapter only allows `listAccessibleCustomers`, `googleAds:search`, and
|
|
117
|
+
`generateKeywordIdeas`, so it cannot mutate Ads state.
|
|
118
|
+
|
|
99
119
|
The result separates `read`, `report`, `edit`, and `publish` for each
|
|
100
120
|
provider/resource. Write and publish remain `not_tested` until the operator
|
|
101
121
|
explicitly confirms the exact mutation and a successful read-back is captured.
|
|
102
122
|
|
|
123
|
+
## Website notifications
|
|
124
|
+
|
|
125
|
+
For website email, in-app, and SMS operations, follow
|
|
126
|
+
[Website notification operations](references/notifications-operations.md):
|
|
127
|
+
a catalogue with consent, retention, and delivery rules, template drift
|
|
128
|
+
against a paginated provider export, a Resend preflight by variable name
|
|
129
|
+
only, aggregate queue health with operator alerts, and a dry-run retention
|
|
130
|
+
plan. All `maggie ops notifications` commands are offline and read-only, and
|
|
131
|
+
none of them sends a message.
|
|
132
|
+
|
|
133
|
+
```bash
|
|
134
|
+
maggie ops notifications catalogue validate --project . --catalogue docs/notifications/catalogue.json
|
|
135
|
+
maggie ops notifications templates drift --project . --catalogue docs/notifications/catalogue.json --local src/emails --provider-export exports/page-1.json
|
|
136
|
+
maggie ops notifications preflight --project . --env-file .env.production --catalogue docs/notifications/catalogue.json
|
|
137
|
+
maggie ops notifications health --project . --snapshot .maggie/notifications-health.json
|
|
138
|
+
```
|
|
139
|
+
|
|
103
140
|
## Required preflight
|
|
104
141
|
|
|
105
142
|
1. Inspect `.maggie/analysis.json` and `.maggie/bootstrap-state.json`.
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
# Website notification operations
|
|
2
|
+
|
|
3
|
+
A provider-neutral operating contract for website email, in-app, and SMS
|
|
4
|
+
notifications: what each message is, which consent it needs, how templates
|
|
5
|
+
stay in sync with the provider, how delivery is kept durable, how the queue is
|
|
6
|
+
watched, and how notification data expires. Every command below is offline and
|
|
7
|
+
read-only. None of them call a provider API, send a message, or print secret
|
|
8
|
+
values or recipient addresses.
|
|
9
|
+
|
|
10
|
+
## 1. Catalogue every message
|
|
11
|
+
|
|
12
|
+
Keep one machine-readable catalogue in the project (for example
|
|
13
|
+
`docs/notifications/catalogue.json`) using
|
|
14
|
+
`contracts/maggie-ops/notification-catalogue-v1.schema.json`. The packaged
|
|
15
|
+
example `contracts/maggie-ops/notification-catalogue-example-v1.json` covers the
|
|
16
|
+
seven sections a website normally needs:
|
|
17
|
+
|
|
18
|
+
| Section | Typical entries |
|
|
19
|
+
|---|---|
|
|
20
|
+
| `identity-security` | email verification, password reset (often provider-owned), sign-in alert, credential/2FA/passkey change, account deletion |
|
|
21
|
+
| `account` | welcome, onboarding reminder, preference update, saved-item digest |
|
|
22
|
+
| `business` | listing/claim status, verification, team invite/approval, import or sync result, publish/domain readiness |
|
|
23
|
+
| `billing` | checkout/subscription active, payment failed, receipt (often provider-owned), plan change, cancellation, trial ending |
|
|
24
|
+
| `support` | contact/waitlist receipt, team copy of a contact, case reply/closure, feedback request |
|
|
25
|
+
| `marketing` | opt-in confirmation, newsletter welcome, content digest, recommendation, gift/voucher interest follow-up, merchant lead digest, re-engagement |
|
|
26
|
+
| `deliverability-ops` | bounce/complaint, suppression, worker failure, dead-letter growth, template drift, sender-domain health, queue health |
|
|
27
|
+
|
|
28
|
+
Each entry records trigger, audience, category (`transactional`, `support`,
|
|
29
|
+
`marketing`), consent boundary, locales and markets, variables, CTA route,
|
|
30
|
+
idempotency-key template, retention days, provider, channels, delivery mode
|
|
31
|
+
(`outbox`, `direct`, `provider`), queue, and status (`implemented`,
|
|
32
|
+
`provider-owned`, `reserved`, `missing`). The status column turns the
|
|
33
|
+
catalogue into a planning asset: `missing` lists what the site still lacks.
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
maggie ops notifications catalogue validate --project . --catalogue docs/notifications/catalogue.json
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
The validator enforces the cross-field rules a schema cannot:
|
|
40
|
+
|
|
41
|
+
- Marketing messages require `consent: opt-in-required`, `unsubscribe: true`
|
|
42
|
+
(preference-centre and unsubscribe links), and the `marketing` queue.
|
|
43
|
+
- `deliverability-ops` alerts use the `operations` queue. An alert about a
|
|
44
|
+
failing provider or queue must not travel through that same queue.
|
|
45
|
+
- External-recipient lead email (`lead: true`: contact, waitlist, gift or
|
|
46
|
+
voucher interest) must use durable `outbox` delivery. A direct send that
|
|
47
|
+
only logs a failure can silently lose a business lead.
|
|
48
|
+
- CTA routes are site paths or `https://` URLs with a fixed host.
|
|
49
|
+
|
|
50
|
+
## 2. Durable delivery for lead email
|
|
51
|
+
|
|
52
|
+
Contact, waitlist, and interest messages are acquisition signals. Route them
|
|
53
|
+
through the same outbox as transactional mail: an idempotency key per
|
|
54
|
+
submission, lease-based claiming, bounded retries, a dead-letter state with
|
|
55
|
+
recorded-reason replay, and provider webhook reconciliation. The visitor's
|
|
56
|
+
request succeeds once the outbox row is committed; delivery failure is an
|
|
57
|
+
operations alert, not a visitor-facing error.
|
|
58
|
+
|
|
59
|
+
## 3. Template lifecycle and drift
|
|
60
|
+
|
|
61
|
+
Template work moves through these stages, each producing reviewable evidence:
|
|
62
|
+
|
|
63
|
+
1. **Discover/scaffold**: derive the template matrix (alias × locale) from the
|
|
64
|
+
catalogue's `implemented` email entries.
|
|
65
|
+
2. **Preview**: render HTML and plain text from fixtures only, never live
|
|
66
|
+
customer data.
|
|
67
|
+
3. **Lint**: every placeholder is a declared variable; CTA routes resolve.
|
|
68
|
+
4. **Localize**: every catalogue locale exists in HTML and plain text, with
|
|
69
|
+
the same variables (see `maggie-content-localization`).
|
|
70
|
+
5. **Approve**: record who approved which template version.
|
|
71
|
+
6. **Sync dry run**: compute the provider diff without writing.
|
|
72
|
+
7. **Publish**: apply the reviewed diff with an explicit confirmation.
|
|
73
|
+
8. **Rollback**: keep the previous provider version ID to restore.
|
|
74
|
+
9. **Verify**: re-run drift; any real send uses a provider test mode or an
|
|
75
|
+
allow-listed internal recipient, behind an explicit confirmation.
|
|
76
|
+
|
|
77
|
+
Before every deploy, compare the catalogue, local templates, and a provider
|
|
78
|
+
export. Export **every** page of the provider's template list to JSON first
|
|
79
|
+
(list endpoints paginate; a single call with a page limit can miss templates):
|
|
80
|
+
|
|
81
|
+
```bash
|
|
82
|
+
maggie ops notifications templates drift --project . \
|
|
83
|
+
--catalogue docs/notifications/catalogue.json \
|
|
84
|
+
--local src/emails/templates \
|
|
85
|
+
--provider-export exports/templates-page-1.json \
|
|
86
|
+
--provider-export exports/templates-page-2.json
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
`--local` accepts a directory of `<alias>.<locale>.<ext>` files (placeholders
|
|
90
|
+
`{{ name }}` are extracted) or a `maggie-notification-templates.v1` manifest.
|
|
91
|
+
The report lists missing local/provider templates, extra provider or local
|
|
92
|
+
aliases, locale gaps, undeclared or unused variables, and local/provider
|
|
93
|
+
version drift. It exits non-zero on drift, and when the last export page still
|
|
94
|
+
reports `has_more: true`. A runtime fallback to locally rendered email protects
|
|
95
|
+
delivery but hides drift, so treat drift as a deploy blocker.
|
|
96
|
+
|
|
97
|
+
## 4. Provider preflight (Resend)
|
|
98
|
+
|
|
99
|
+
```bash
|
|
100
|
+
maggie ops notifications preflight --project . --env-file .env.production \
|
|
101
|
+
--catalogue docs/notifications/catalogue.json
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
Checks, by variable name only: API key, sender (`RESEND_EMAIL`, `RESEND_FROM`,
|
|
105
|
+
or `RESEND_SENDER`), worker secret, support recipients, webhook secret, and
|
|
106
|
+
the template map. Template IDs are not printed. Only the map's alias keys are
|
|
107
|
+
compared with implemented Resend catalogue entries. Add project-specific names
|
|
108
|
+
with `--require NAME`. Presence is not proof of delivery: after deploy, the
|
|
109
|
+
release still needs sender-domain verification, webhook reachability, timer
|
|
110
|
+
health, and an authenticated synthetic delivery recorded as redacted evidence.
|
|
111
|
+
|
|
112
|
+
## 5. Queue health and alerts
|
|
113
|
+
|
|
114
|
+
Have the host worker or a read-only SQL job write an aggregate snapshot
|
|
115
|
+
(`maggie-notification-queue-snapshot.v1`) with counts (`pending`,
|
|
116
|
+
`processing`, `deadLetter`, `awaitingWebhook`), `oldestPendingAt`,
|
|
117
|
+
`expiredLeases`, `lastWebhookAt`, `lastSuccessfulRunAt`, optional `rates`
|
|
118
|
+
(`bounce`, `complaint`), and `senderDomainVerified`. Snapshots must not contain
|
|
119
|
+
addresses; the check rejects them if they do.
|
|
120
|
+
|
|
121
|
+
```bash
|
|
122
|
+
maggie ops notifications health --project . --snapshot .maggie/notifications-health.json \
|
|
123
|
+
--max-oldest-pending-minutes 30 --max-dead-letter 0
|
|
124
|
+
maggie ops notifications alerts
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
`health` exits non-zero when any threshold is exceeded, so it can run from a
|
|
128
|
+
systemd timer or CI after the worker. A timer can be active while the worker
|
|
129
|
+
never succeeds, so `lastSuccessfulRunAt` is required. `alerts` prints the
|
|
130
|
+
operator alert templates (severity, owner, recovery action, escalation). Send
|
|
131
|
+
alerts through an operations channel that is independent of the notification
|
|
132
|
+
queue and provider being monitored.
|
|
133
|
+
|
|
134
|
+
## 6. Privacy, retention, and suppression
|
|
135
|
+
|
|
136
|
+
The catalogue's `retention` block sets days for `webhook-payload`,
|
|
137
|
+
`delivery-record`, `rendered-snapshot`, and `template-variables`. Store
|
|
138
|
+
minimized webhook fields (event type, provider message ID, status, time), not
|
|
139
|
+
the full raw payload. Keep suppression entries as a hashed address with
|
|
140
|
+
reason and time. Recovery from suppression (for example, a fixed mailbox)
|
|
141
|
+
needs an explicit operator action with a recorded reason.
|
|
142
|
+
|
|
143
|
+
Plan purges from aggregate counts only:
|
|
144
|
+
|
|
145
|
+
```bash
|
|
146
|
+
maggie ops notifications retention plan --project . \
|
|
147
|
+
--catalogue docs/notifications/catalogue.json \
|
|
148
|
+
--inventory .maggie/notifications-inventory.json
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
The plan is always a dry run. Rows tied to an open support case or legal hold
|
|
152
|
+
are reported as held. Apply purges through the host's reviewed job or
|
|
153
|
+
migration, and handle account deletion by purging that user's delivery
|
|
154
|
+
records and snapshots and keeping only the hashed suppression entry.
|
|
@@ -231,6 +231,65 @@ The contract is documented in
|
|
|
231
231
|
It is provider-neutral and does not accept credentials, provider response
|
|
232
232
|
bodies, or arbitrary filesystem paths in the poll reference.
|
|
233
233
|
|
|
234
|
+
### Long-running import stages, readiness, and fact provenance
|
|
235
|
+
|
|
236
|
+
Inside the `import` workflow, a provider import runs four stages: `source`,
|
|
237
|
+
then `enrichment` and `research` in parallel, then `verification`. Validate a
|
|
238
|
+
redacted snapshot with:
|
|
239
|
+
|
|
240
|
+
```bash
|
|
241
|
+
maggie service import-stages-validate --project . \
|
|
242
|
+
--manifest .maggie/booking/import-stages.json --output docs/import-stages.json
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
The `maggie-service-import-stages.v1` contract
|
|
246
|
+
(`contracts/maggie-service-booking/import-stages-v1.schema.json`) requires a
|
|
247
|
+
stage key per stage prefixed with the import ID and reused on retry, bounded
|
|
248
|
+
attempts, `{done, total}` progress (a succeeded stage is complete), and a
|
|
249
|
+
`heartbeatAt` for running stages. A running stage without a heartbeat for more
|
|
250
|
+
than `staleAfterSeconds` makes the import `stale` with an explicit
|
|
251
|
+
`nextAction`. Parallel stages may complete in any order, but a stage may not
|
|
252
|
+
start before its dependencies finish, and a downstream result built from a
|
|
253
|
+
superseded `source.resultRevision` must be marked `resultDiscarded` instead of
|
|
254
|
+
applied. Cancellation must reach every stage; results completed after
|
|
255
|
+
`cancellation.requestedAt` are discarded. The declared import state must match
|
|
256
|
+
the state derived from its stages (`partial` when retries are exhausted after
|
|
257
|
+
some stages succeeded).
|
|
258
|
+
|
|
259
|
+
Before queuing a long-running provider job, run the worker readiness preflight
|
|
260
|
+
on the worker host. It fails — and the job must not be queued — when a Python
|
|
261
|
+
module is not importable, a browser or matching WebDriver is missing, a crawler
|
|
262
|
+
setting is absent (checked by environment variable name; values are never
|
|
263
|
+
printed), the runtime is not UTF-8, or a country label cannot be normalized to
|
|
264
|
+
ISO 3166 (locale display names and mis-decoded text such as `Königreich` are
|
|
265
|
+
repaired first). A rate-limit probe is a single HEAD request and runs only when
|
|
266
|
+
requested:
|
|
267
|
+
|
|
268
|
+
```bash
|
|
269
|
+
maggie service import-readiness --project . --python-module selenium \
|
|
270
|
+
--browser --webdriver --env MAPS_CRAWLER_KEY --country "United Kingdom" \
|
|
271
|
+
--output docs/import-readiness.json
|
|
272
|
+
```
|
|
273
|
+
|
|
274
|
+
Imported business facts keep field-level provenance in
|
|
275
|
+
`maggie-service-fact-provenance.v1`
|
|
276
|
+
(`contracts/maggie-service-booking/fact-provenance-v1.schema.json`). A
|
|
277
|
+
user-entered value is authoritative and always wins; provider values carry a
|
|
278
|
+
source, confidence, and reference. Providers that agree corroborate a value;
|
|
279
|
+
providers that disagree above `minConfidence` produce a `needs-review` conflict
|
|
280
|
+
with the policy's proposed choice (`fieldPrecedence` can, for example, prefer
|
|
281
|
+
the website for `websiteUrl`). A provider error is recorded under
|
|
282
|
+
`unavailableSources` and never blocks other providers or fields:
|
|
283
|
+
|
|
284
|
+
```bash
|
|
285
|
+
maggie service fact-provenance --project . \
|
|
286
|
+
--facts .maggie/booking/fact-provenance.json --output docs/fact-merge.json
|
|
287
|
+
```
|
|
288
|
+
|
|
289
|
+
The command exits 1 while conflicts need review; the host review UI reads the
|
|
290
|
+
`maggie-service-fact-merge.v1` artifact and records the reviewer's choice as a
|
|
291
|
+
user-entered value.
|
|
292
|
+
|
|
234
293
|
`convert-page` imports a normal page as a draft service without inventing
|
|
235
294
|
booking facts. `match-pages` reads filesystem pages plus `docs/pages.json` and
|
|
236
295
|
`docs/page-content.json`, writes candidate evidence to both `.maggie/booking`
|