@topy-ai/maggie 0.7.3 → 0.7.5

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 (31) hide show
  1. package/README.md +29 -9
  2. package/README.zh-TW.md +5 -1
  3. package/bin/maggie.js +18 -2
  4. package/bundled-contracts/maggie-deployment/release-runner-v1.json +14 -0
  5. package/bundled-contracts/maggiedash/README.md +22 -0
  6. package/bundled-contracts/maggiedash/agent-content-v1.json +15 -0
  7. package/bundled-contracts/maggiedash/redirect-policy-v1.json +15 -0
  8. package/bundled-contracts/maggiedash/section-identity-v1.json +14 -0
  9. package/bundled-contracts/maggiedash/telemetry-policy-v1.json +18 -0
  10. package/bundled-contracts/maggiedash/translation-cache-policy-v1.json +15 -0
  11. package/bundled-references/maggiedash-telemetry-contract.md +12 -0
  12. package/bundled-skills/maggie-dash/SKILL.md +76 -3
  13. package/bundled-skills/maggie-deployment/SKILL.md +20 -2
  14. package/bundled-skills/maggie-ops/SKILL.md +23 -2
  15. package/bundled-skills/maggie-seo-geo/SKILL.md +16 -0
  16. package/bundled-templates/maggiedash/dashboard-ui-contract.json +8 -7
  17. package/bundled-templates/maggiedash/section-registry.json +12 -0
  18. package/bundled-tools/clis/maggie_agent_content.py +75 -0
  19. package/bundled-tools/clis/maggie_dash.py +112 -1
  20. package/bundled-tools/clis/maggie_deployment.py +51 -0
  21. package/bundled-tools/clis/maggie_ops.py +23 -0
  22. package/bundled-tools/clis/maggie_verification.py +33 -0
  23. package/bundled-tools/runtime/agent_runtime.py +38 -0
  24. package/bundled-tools/runtime/analytics_traffic.py +6 -1
  25. package/bundled-tools/runtime/maggie_dash_store.py +121 -8
  26. package/bundled-tools/runtime/maggie_dash_ui.py +35 -1
  27. package/bundled-tools/runtime/maggie_sections.py +143 -0
  28. package/bundled-tools/runtime/restart_gate.py +22 -0
  29. package/bundled-tools/runtime/surface_coverage.py +48 -0
  30. package/package.json +1 -1
  31. package/references/maggiedash-telemetry-contract.md +12 -0
package/README.md CHANGED
@@ -92,6 +92,9 @@ maggie bootstrap interview | phase ...
92
92
  maggie dash install | init | status | migrate | cms ...
93
93
  maggie dash transition ... # explicit content approval transition
94
94
  maggie dash variant ... # service variant create/review/preview/publish
95
+ maggie dash sections ... # validate/catalogue/identity keys
96
+ maggie agent-content write ... # host-authorized content bridge
97
+ maggie verification coverage ... # changed surface/locale evidence gate
95
98
  maggie clone ... # authorized homepage capture
96
99
  maggie design ... # authorized interior-page design
97
100
  maggie clone-to-template ... # URL → validated marketplace template
@@ -191,8 +194,8 @@ artifact schemas.
191
194
  Recommended upgrade sequence for the current release:
192
195
 
193
196
  ```bash
194
- npx @topy-ai/maggie@0.7.3 update --project . --force
195
- npx @topy-ai/maggie@0.7.3 cleanup --project .
197
+ npx @topy-ai/maggie@0.7.5 update --project . --force
198
+ npx @topy-ai/maggie@0.7.5 cleanup --project .
196
199
  ```
197
200
 
198
201
  Maintainers should pass npm credentials through the repository helper, never
@@ -202,14 +205,20 @@ as a command-line argument:
202
205
  node scripts/publish-npm.mjs --maggie-env-file ../.env
203
206
  ```
204
207
 
205
- The 0.7.2 workflow adds the installable MaggieDash admin distribution and
208
+ The 0.7.5 workflow adds the installable MaggieDash admin distribution and
206
209
  audited CMS operations (`cms revisions`,
207
- `trash`, `restore`, `schedule`, `duplicate`, `redirect`, and signed
208
- `preview`), import-authoritative service matching, shared translation indexes,
209
- sanitized seed manifests, lockfile/analytics traffic checks, semantic sitemap
210
- validation, locale-aware `llms.txt`/`sitemap.md`/`insights.md` generation,
211
- explicit integration states, and deployment Origin/infrastructure/data
212
- rollback gates.
210
+ `trash`, `restore`, `schedule`, `publish-due`, `duplicate`, `redirect`, and
211
+ signed `preview`). It also adds a read-only MaggieDash `--diff`/`--dry-run`
212
+ migration inventory, body-only dashboard prop validation, dialog accessibility
213
+ contract checks, 404-only redirect policy, trash/scheduler/preview invariants,
214
+ and a shared first-party traffic filter for every measurement channel. It also
215
+ adds stable section identities and reusable section presets, a machine-readable
216
+ MaggieDash section registry, a short-lived agent content bridge, restart gates
217
+ for out-of-band translation writes, deployment release-runner generation,
218
+ disk-versus-manifest skill inventory checks, and changed-surface/locale
219
+ verification. The release retains the existing service matching,
220
+ localization, seed-manifest, lockfile/analytics, sitemap, deployment and
221
+ rollback workflows.
213
222
 
214
223
  ## MaggieDash lifecycle
215
224
 
@@ -231,6 +240,17 @@ for local development or a configured private Git URL. The host project keeps
231
240
  ownership of routes, email/password auth, database, provider credentials, and
232
241
  the `/api/maggie/*` adapter.
233
242
 
243
+ Compare an existing dashboard before replacing it:
244
+
245
+ ```bash
246
+ maggie dash install --project . --diff \
247
+ --existing-dir src/plugins/maggie-studio/src
248
+ maggie dash install --project . --dry-run
249
+ ```
250
+
251
+ These commands do not write files or installation state. Review the report and
252
+ map host-owned files before using `--force`.
253
+
234
254
  Content remains draft-first and external writes remain explicit. Use the
235
255
  project-local memory workflow for confirmed preferences and reusable lessons;
236
256
  feedback drafts are never promoted to active memory automatically.
package/README.zh-TW.md CHANGED
@@ -8,7 +8,7 @@ Codex、Claude Code 與相容的 coding agents。
8
8
  ## 安裝
9
9
 
10
10
  ```bash
11
- npx @topy-ai/maggie@0.6.9 init --agent all
11
+ npx @topy-ai/maggie@0.7.5 init --agent all
12
12
  npx @topy-ai/maggie doctor --project .
13
13
  ```
14
14
 
@@ -25,6 +25,10 @@ maggie doctor --project . --require-bootstrap --strict
25
25
  deployment、memory、feedback 和 MaggieDash。內容先 draft/review,外部寫入、
26
26
  publish 與 production deployment 需要明確確認。
27
27
 
28
+ MaggieDash 也提供穩定 section identity、可重用 section arrangement、機器可讀
29
+ section registry、短期 agent content bridge,以及 translation out-of-band write
30
+ 後的 restart gate。`maggie doctor` 會比較 install manifest 與磁碟上的實際 skills。
31
+
28
32
  新版 release gates:
29
33
 
30
34
  ```bash
package/bin/maggie.js CHANGED
@@ -66,9 +66,11 @@ Usage:
66
66
  maggie doctor [--project PATH]
67
67
  maggie bootstrap interview [project]
68
68
  maggie dash init|install|status|migrate|transition|variant|cms --project PATH [options]
69
+ maggie dash sections <validate|prompt|keys> [options]
69
70
  maggie dash status --project PATH
70
71
  maggie dash migrate --project PATH --confirm
71
72
  maggie content FILE --source PROVIDER --project PATH --confirm
73
+ maggie agent-content write --url URL --token-env ENV --payload FILE
72
74
  maggie bootstrap phase <start|pass|fail|status> <phase> [project]
73
75
  maggie remove [SKILL ...] [--project PATH] [--agent codex|claude|all]
74
76
  maggie cleanup --project PATH [--confirm]
@@ -117,6 +119,7 @@ Usage:
117
119
  maggie site-audit URL --crawl --save-baseline FILE --reviewer NAME
118
120
  maggie site-audit URL --crawl --baseline FILE
119
121
  maggie browser-audit URL --browse PATH --output DIR --required SELECTOR [--sticky SELECTOR]
122
+ maggie verification coverage --contract FILE --evidence FILE
120
123
  maggie ops audit|preflight|verify|lockfiles|seed-manifest --project PATH
121
124
  maggie ops preflight --project PATH --write
122
125
 
@@ -320,11 +323,22 @@ function doctor(args) {
320
323
  ["claude-skills", existsSync(join(root, ".claude", "skills"))],
321
324
  ];
322
325
  for (const [name, passed] of checks) console.log(`${passed ? "PASS" : "INFO"} ${name}`);
326
+ const manifestPath = join(root, STATE_DIR, "install.json");
327
+ let manifest = null;
328
+ try { if (existsSync(manifestPath)) manifest = JSON.parse(readFileSync(manifestPath, "utf8")); } catch { manifest = null; }
329
+ const expected = Array.isArray(manifest?.skills) ? [...new Set(manifest.skills)] : null;
323
330
  const installed = [];
331
+ const installedNames = new Set();
324
332
  for (const agentRoot of [join(root, ".agents"), join(root, ".claude")]) {
325
- for (const skill of SKILL_NAMES) if (existsSync(join(agentRoot, "skills", skill, "SKILL.md"))) installed.push(`${agentRoot}/${skill}`);
333
+ for (const skill of SKILL_NAMES) if (existsSync(join(agentRoot, "skills", skill, "SKILL.md"))) {
334
+ installed.push(`${agentRoot}/${skill}`);
335
+ installedNames.add(skill);
336
+ }
326
337
  }
327
- console.log(`INFO installed-skills=${installed.length}`);
338
+ const missing = expected ? expected.filter((skill) => !installedNames.has(skill)) : [];
339
+ const inventoryState = expected && missing.length ? "INCOMPLETE" : "PASS";
340
+ console.log(`${inventoryState} skill-inventory manifest=${expected?.length ?? "unknown"} on-disk=${installedNames.size}${missing.length ? ` missing=${missing.join(",")}` : ""}`);
341
+ console.log(`INFO installed-skills=${installedNames.size}`);
328
342
  console.log("INFO doctor is diagnostic; bootstrap completion remains a project decision gate.");
329
343
  }
330
344
 
@@ -394,7 +408,9 @@ try {
394
408
  else if (command === "feedback") workflowCli("maggie_feedback.py", args);
395
409
  else if (command === "site-audit") workflowCli("site_audit.py", args);
396
410
  else if (command === "browser-audit") workflowCli("maggie_browser_audit.py", args);
411
+ else if (command === "verification") workflowCli("maggie_verification.py", args);
397
412
  else if (command === "dash") workflowCli("maggie_dash.py", args);
413
+ else if (command === "agent-content") workflowCli("maggie_agent_content.py", args);
398
414
  else if (command === "content") workflowCli("maggie_content.py", args);
399
415
  else if (command === "init" || command === "install") install(args);
400
416
  else if (command === "update") update(args);
@@ -0,0 +1,14 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "$id": "https://maggiedash.noblox.app/contracts/release-runner-v1.schema.json",
4
+ "title": "Maggie deployment release runner v1",
5
+ "type": "object",
6
+ "required": ["schemaVersion", "orderedSteps", "retention"],
7
+ "properties": {
8
+ "schemaVersion": {"const": "maggie-release-runner.v1"},
9
+ "orderedSteps": {"const": ["install", "typecheck", "migrate", "build", "carry-agent-state", "switch", "restart", "verify", "prune"]},
10
+ "retention": {"const": "exactly-current-plus-one-rollback"},
11
+ "mutation": {"enum": ["reviewable-script", "executed-after-confirmation"]}
12
+ },
13
+ "additionalProperties": false
14
+ }
@@ -14,6 +14,16 @@ frontend framework.
14
14
  dry-run, backup, checksum, cutover, and rollback evidence;
15
15
  - [`storage-model.md`](storage-model.md): SQLite/PostgreSQL table ownership and
16
16
  adapter rules.
17
+ - [`redirect-policy-v1.json`](redirect-policy-v1.json): 404-only lookup and
18
+ self/chain safety for canonical route redirects;
19
+ - [`telemetry-policy-v1.json`](telemetry-policy-v1.json): first-party/toolchain
20
+ traffic exclusion for every measurement channel.
21
+ - [`agent-content-v1.json`](agent-content-v1.json): short-lived host bridge for
22
+ validated agent content writes without database credentials;
23
+ - [`section-identity-v1.json`](section-identity-v1.json): stable section IDs
24
+ for translation keys and ordered migration;
25
+ - [`translation-cache-policy-v1.json`](translation-cache-policy-v1.json):
26
+ restart-after-out-of-band-write evidence.
17
27
 
18
28
  MaggieDash owns these contracts. Provider adapters may add namespaced metadata,
19
29
  but they may not change the required identity, status, provenance, or approval
@@ -32,3 +42,15 @@ published → archived
32
42
 
33
43
  No import, AI generation, rewrite, or adapter sync may publish content without
34
44
  an approval record and an auditable actor/correlation ID.
45
+
46
+ ## CMS invariants
47
+
48
+ - `trashed` is a status. Public readers exclude it; trash is reversible.
49
+ - A no-op save creates no revision, and the first update of an imported row
50
+ snapshots the existing state before replacing it.
51
+ - Scheduled content with a past timestamp is published on the next scheduler
52
+ tick. A trashed row is never selected by the due query.
53
+ - Preview responses are real preview routes protected by HMAC tokens and
54
+ advertise `noindex`, `X-Robots-Tag: noindex`, and `Cache-Control: no-store`.
55
+ - Redirect lookup happens after the route has returned 404. Self-redirects and
56
+ redirect chains are rejected at write time.
@@ -0,0 +1,15 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "$id": "https://maggiedash.noblox.app/contracts/agent-content-v1.schema.json",
4
+ "title": "MaggieDash agent content bridge v1",
5
+ "type": "object",
6
+ "required": ["method", "authorization", "tokenLifetime", "databaseCredentials"],
7
+ "properties": {
8
+ "method": {"const": "POST"},
9
+ "authorization": {"const": "Bearer short-lived session token"},
10
+ "tokenLifetime": {"const": "terminal session"},
11
+ "databaseCredentials": {"const": "never exposed to agent"},
12
+ "writes": {"type": "array", "items": {"enum": ["validated content operation", "revision", "audit event"]}}
13
+ },
14
+ "additionalProperties": false
15
+ }
@@ -0,0 +1,15 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "$id": "https://maggiedash.noblox.app/contracts/redirect-policy-v1.schema.json",
4
+ "title": "MaggieDash redirect policy v1",
5
+ "type": "object",
6
+ "required": ["schemaVersion", "lookupAfterStatus", "rejectSelf", "rejectChains"],
7
+ "properties": {
8
+ "schemaVersion": {"const": "maggiedash-redirect-policy.v1"},
9
+ "lookupAfterStatus": {"const": 404},
10
+ "rejectSelf": {"const": true},
11
+ "rejectChains": {"const": true},
12
+ "allowedStatusCodes": {"type": "array", "items": {"enum": [301, 302, 307, 308]}}
13
+ },
14
+ "additionalProperties": false
15
+ }
@@ -0,0 +1,14 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "$id": "https://maggiedash.noblox.app/contracts/section-identity-v1.schema.json",
4
+ "title": "MaggieDash stable section identity v1",
5
+ "type": "object",
6
+ "required": ["schemaVersion", "keyPattern", "fallback", "migrationOrder"],
7
+ "properties": {
8
+ "schemaVersion": {"const": "maggiedash-section-identity.v1"},
9
+ "keyPattern": {"const": "page.<id>.sections.<section-id>.<field>"},
10
+ "fallback": {"const": "array-index-until-migrated"},
11
+ "migrationOrder": {"const": "longest-old-prefix-first"}
12
+ },
13
+ "additionalProperties": false
14
+ }
@@ -0,0 +1,18 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "$id": "https://maggiedash.noblox.app/contracts/telemetry-policy-v1.schema.json",
4
+ "title": "MaggieDash telemetry policy v1",
5
+ "type": "object",
6
+ "required": ["schemaVersion", "filterBeforeMeasurement", "channels"],
7
+ "properties": {
8
+ "schemaVersion": {"const": "maggiedash-telemetry-policy.v1"},
9
+ "filterBeforeMeasurement": {"const": true},
10
+ "channels": {
11
+ "type": "array",
12
+ "items": {"enum": ["page_view", "not_found", "redirect_hit", "booking_click", "analytics", "audit"]},
13
+ "minItems": 1
14
+ },
15
+ "identityInference": {"const": "forbidden"}
16
+ },
17
+ "additionalProperties": false
18
+ }
@@ -0,0 +1,15 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "$id": "https://maggiedash.noblox.app/contracts/translation-cache-policy-v1.schema.json",
4
+ "title": "MaggieDash translation cache policy v1",
5
+ "type": "object",
6
+ "required": ["schemaVersion", "outOfBandWrite", "restartRequired", "restartCommand", "verificationAfterRestart"],
7
+ "properties": {
8
+ "schemaVersion": {"const": "maggie-restart-gate.v1"},
9
+ "outOfBandWrite": {"const": true},
10
+ "restartRequired": {"const": true},
11
+ "restartCommand": {"type": "string", "minLength": 1},
12
+ "verificationAfterRestart": {"const": true}
13
+ },
14
+ "additionalProperties": false
15
+ }
@@ -0,0 +1,12 @@
1
+ # MaggieDash telemetry contract
2
+
3
+ Every measurement path must call the shared first-party traffic filter before
4
+ incrementing a counter or writing a measurement event. This includes page
5
+ views, 404/not-found rows, redirect hits, booking clicks, analytics events and
6
+ operational audit counters. Filtering only page views leaves synthetic sweeps
7
+ in the other totals.
8
+
9
+ Use `should_record_measurement(event)` from
10
+ `tools/runtime/analytics_traffic.py`. The filter accepts explicit Maggie/test
11
+ markers and known toolchain user agents; it never guesses identity from IP
12
+ addresses or private user fields.
@@ -25,11 +25,42 @@ explicitly supplied, and records the resolved revision in
25
25
  CLI argument. For local development or a pinned checkout, use
26
26
  `--source /path/to/MaggieDash` or `--source URL --ref REF`.
27
27
 
28
+ Before replacing an existing hand-written dashboard, create a read-only
29
+ migration report. It compares the first-party manifest with the existing
30
+ workspace and labels additions, unchanged files, updates that would be
31
+ preserved, and unmanaged files:
32
+
33
+ ```bash
34
+ maggie dash install --project . --diff \
35
+ --existing-dir src/plugins/maggie-studio/src
36
+ maggie dash install --project . --dry-run
37
+ ```
38
+
39
+ These modes do not require `--confirm` and do not write dashboard files or
40
+ `.maggie/dash-install.json`. Review the report and explicitly map or back up
41
+ hand-written files before using `--force`; an idempotent installer is not a
42
+ schema migration.
43
+
28
44
  The installed dashboard owns the React workspace UI. The host project still
29
45
  owns the framework route, email/password session middleware, database, media
30
46
  storage, provider credentials, and `/api/maggie/*` adapter endpoints. Read the
31
47
  MaggieDash host adapter contract before adding a new framework adapter.
32
48
 
49
+ For a built/deployed host, an agent must use the host's validated content
50
+ bridge rather than editing release files or receiving database credentials:
51
+
52
+ ```bash
53
+ maggie agent-content write \
54
+ --url https://example.test/api/maggie/agent-content.json \
55
+ --token-env MAGGIE_CONTENT_TOKEN --payload .maggie/content-operation.json
56
+ ```
57
+
58
+ The host mints a short-lived session token. The bridge uses `POST` with
59
+ `Authorization: Bearer`, delegates to the ordinary save/revision/audit path,
60
+ and returns 401/422 for invalid authority or content. Never put database
61
+ credentials or the short-lived token in the payload or generated reports. See
62
+ the [agent content contract](../../contracts/maggiedash/agent-content-v1.json).
63
+
33
64
  ## Workflow
34
65
 
35
66
  1. Inspect the project with `maggie dash status --project PATH`.
@@ -105,8 +136,15 @@ maggie dash cms trash --project . --project-id local-project --document-id <id>
105
136
  maggie dash cms restore --project . --project-id local-project --document-id <id> --reason "restore" --confirm
106
137
  maggie dash cms schedule --project . --project-id local-project --document-id <id> \
107
138
  --publish-at 2026-09-20T10:00:00Z --reason "approved release" --confirm
139
+ maggie dash cms publish-due --project . --project-id local-project \
140
+ --reason "scheduled publish tick" --confirm
108
141
  maggie dash cms duplicate --project . --project-id local-project --document-id <id> \
109
142
  --new-id <new-id> --new-slug <new-slug> --reason "create draft" --confirm
143
+ maggie dash cms preset-save --project . --project-id local-project \
144
+ --preset-id treatment-page --name "Treatment page" \
145
+ --sections-file .maggie/sections.json --confirm
146
+ maggie dash cms preset-insert --project . --project-id local-project \
147
+ --preset-id treatment-page --existing-id hero-1 --reason "add arrangement" --confirm
110
148
  maggie dash cms redirect --project . --project-id local-project --from-path /old --to-path /new \
111
149
  --reason "canonical slug change" --confirm
112
150
  maggie dash cms preview --project . --project-id local-project --document-id <id> \
@@ -114,6 +152,41 @@ maggie dash cms preview --project . --project-id local-project --document-id <id
114
152
  ```
115
153
 
116
154
  Content writes create immutable revision snapshots. Trash is reversible and
117
- does not destroy the document. Scheduling is accepted only for approved
118
- content and requires a timezone. Preview tokens are short-lived HMAC-signed
119
- tokens; never place the secret in source control or generated reports.
155
+ does not destroy the document; public readers must filter `status=trashed`.
156
+ Scheduling is accepted only for approved content and requires a timezone; a
157
+ past timestamp is published on the next scheduler tick, while trashed rows
158
+ are never eligible. A no-op save creates no revision, and the first update of
159
+ an imported row snapshots its prior state. Redirects are consulted only after
160
+ the route returns 404 and self/chain redirects are rejected. Preview tokens
161
+ are short-lived HMAC-signed tokens; preview responses must use a real preview
162
+ URL with `noindex`, `X-Robots-Tag: noindex`, and `Cache-Control: no-store`.
163
+ Never place the secret in source control or generated reports. See the
164
+ provider-neutral [MaggieDash contract set](../../contracts/maggiedash/README.md).
165
+
166
+ Reusable arrangements are stored without page-specific section IDs and expand
167
+ into ordinary editable sections with fresh IDs on every insert. Use the
168
+ machine-readable section catalogue before asking a model to plan copy:
169
+
170
+ ```bash
171
+ maggie dash sections validate --registry templates/maggiedash/section-registry.json \
172
+ --sections-file .maggie/sections.json
173
+ maggie dash sections prompt --registry templates/maggiedash/section-registry.json
174
+ maggie dash sections keys --registry templates/maggiedash/section-registry.json \
175
+ --page-id <page-id> --sections-file .maggie/sections.json
176
+ ```
177
+
178
+ The catalogue declares purpose, usage, placement, repeatability and layout
179
+ limits. Over-limit copy is an editor note; unknown types fail validation.
180
+ Translation keys use stable section IDs, with an array-index fallback only
181
+ until the ordered migration is complete.
182
+
183
+ After any script or direct adapter write to translation data, invalidate the
184
+ running process before verification. Record the restart and run the rendering
185
+ check afterward:
186
+
187
+ ```bash
188
+ maggie ops restart-gate --manifest .maggie/translation-restart.json
189
+ ```
190
+
191
+ This prevents an in-process cache window from turning correct database state
192
+ into temporarily stale pages and false verification failures.
@@ -63,11 +63,15 @@ Generate a reviewable VPS plan and configuration artifacts locally:
63
63
  python3 tools/clis/maggie_deployment.py /path/to/project \
64
64
  --vps-plan --domain example.co.uk --service example \
65
65
  --release-root /var/www/example \
66
- --plan-dir .maggie/deployment/vps-staging
66
+ --plan-dir .maggie/deployment/vps-staging \
67
+ --runner-output .maggie/deployment/vps-staging/release-runner.sh
67
68
  ```
68
69
 
69
70
  The output contains only the immutable release/current layout, systemd unit,
70
- Nginx reverse-proxy config, health commands and rollback command. It never
71
+ Nginx reverse-proxy config, health commands, rollback command, and an ordered
72
+ reviewable release runner. The runner keeps migrations before the symlink flip,
73
+ carries `.agents`/`.claude` state, restarts before verification, and prunes to
74
+ the current release plus one rollback candidate. It never
71
75
  contains credentials and never SSHs, changes DNS, restarts systemd or deploys.
72
76
 
73
77
  VPS plans include a bounded retention policy: keep two immutable releases,
@@ -136,6 +140,20 @@ MaggieDash schema compatibility, and live runtime security-header checks. It wri
136
140
  `.maggie/release-preflight.json`; a failed gate blocks release. It never
137
141
  deploys, migrates, publishes, or sends provider requests.
138
142
 
143
+ Generate a repeatable VPS runner as part of the plan. A deploy step that exists
144
+ only in an operator's shell history is not a release contract:
145
+
146
+ ```bash
147
+ python3 tools/clis/maggie_deployment.py /path/to/project \
148
+ --vps-plan --domain example.co.uk --service example \
149
+ --release-root /var/www/example \
150
+ --runner-output .maggie/deployment/release-runner.sh
151
+ ```
152
+
153
+ Review the generated runner before execution. Its order is install → typecheck
154
+ → migrate → build → carry agent state → switch → restart → verify → prune;
155
+ retention keeps the current release and one rollback candidate.
156
+
139
157
  Production additionally requires `docs/deployment-rollback-smoke.json` with
140
158
  `passed: true`, `environment: production`, a non-empty `previousRelease`, and
141
159
  `testedAt`. A rollback command in a plan is not proof that the rollback was
@@ -220,5 +220,26 @@ The seed manifest must be opt-in, sanitized, and contain non-empty fixtures;
220
220
  an empty dev database is not evidence that an operational check passed.
221
221
  Known Maggie/toolchain traffic can be measured without polluting first-party
222
222
  analytics using `maggie analytics traffic-audit --events events.json`. The
223
- classifier excludes only explicit tool/test markers and never infers identity
224
- from IP or private fields.
223
+ classifier must run before every measurement channel, including page views,
224
+ 404/not-found rows, redirect hits, booking clicks and audit counters. The
225
+ shared contract is in
226
+ [references/maggiedash-telemetry-contract.md](../../references/maggiedash-telemetry-contract.md);
227
+ it excludes only explicit tool/test markers and never infers identity from IP
228
+ or private fields.
229
+
230
+ ## Agent runtime preflight
231
+
232
+ Before starting an agent from a dashboard, run the runtime preflight and show
233
+ its warnings to the operator. Terminal output must be stripped of ANSI escape
234
+ sequences before URL/code parsing. Headless hosts should use device auth and
235
+ state the account prerequisite before starting. Release installs and agent
236
+ conversation state must be carried outside the immutable release directory.
237
+ When continuing a conversation, pass the agent's resume subcommand before
238
+ other arguments; otherwise the CLI may interpret it as a new prompt.
239
+
240
+ ```bash
241
+ maggie ops agent-preflight --output .maggie/agent-runtime-preflight.json
242
+ ```
243
+
244
+ The preflight is advisory about the host environment but its warnings are
245
+ load-bearing operational evidence, not a claim that an agent login succeeded.
@@ -19,6 +19,22 @@ Static audit success
19
19
  does not establish Google indexing, scroll behavior, responsive visibility,
20
20
  or translation quality. Record those as unverified without separate evidence.
21
21
 
22
+ Choose verification from the changed surface. An admin-only change still needs
23
+ the admin routes; an API change needs the API responses; a localized change
24
+ needs every affected locale. Declare the matrix and validate explicit evidence
25
+ before release:
26
+
27
+ ```bash
28
+ maggie verification coverage \
29
+ --contract contracts/maggie-verification/surface-coverage-v1.json \
30
+ --evidence .maggie/surface-coverage.json
31
+ ```
32
+
33
+ The evidence must contain one observation for every declared surface/locale
34
+ pair with a status and `passed: true`. Public-page checks alone cannot close an
35
+ admin or API change, and one source-language rendering cannot close a
36
+ localized change.
37
+
22
38
  Crawl reports include `summary.byLocaleTemplate` with total, passed, failed
23
39
  and failed URLs per declared locale and template. Regions/scripts remain
24
40
  distinct (for example en-GB versus en-US). A template is identified only when
@@ -13,19 +13,20 @@
13
13
  "components": [
14
14
  {
15
15
  "name": "WorkspaceBar",
16
- "props": ["workspaceName", "activeSection", "actions", "children"],
16
+ "props": ["tabs", "active", "onSelect", "lead", "actions"],
17
17
  "forbiddenProps": []
18
18
  },
19
19
  {
20
20
  "name": "WorkspaceCard",
21
- "props": ["title", "children"],
21
+ "props": ["children", "className"],
22
22
  "forbiddenProps": []
23
- },
24
- {
25
- "name": "ContentTabs",
26
- "props": ["tabs", "active", "actions", "children"],
27
- "forbiddenProps": ["title", "description"]
28
23
  }
29
24
  ],
25
+ "dialogs": {
26
+ "primitive": "Modal",
27
+ "overlayPrimitive": "Overlay",
28
+ "forbiddenSourcePatterns": ["window.confirm(", "window.prompt("],
29
+ "requiredLiterals": ["role=\"dialog\"", "aria-modal", "Escape", "opener.current"]
30
+ },
30
31
  "accessibility": ["active tab exposes aria-selected", "actions have accessible names", "workspace navigation has a landmark"]
31
32
  }
@@ -0,0 +1,12 @@
1
+ {
2
+ "schemaVersion": "maggiedash-section-registry.v1",
3
+ "description": "The machine-readable section vocabulary used by page planners and editors.",
4
+ "sections": [
5
+ {"type": "hero", "purpose": "Says what the page is about and offers the one important action.", "usage": "First on the page; once.", "position": "opening", "repeatable": false, "fields": [{"name": "title", "usage": "Plain words a visitor would search for.", "limit": 70, "required": true}, {"name": "intro", "usage": "Who it is for and what it does.", "limit": 220}, {"name": "ctaLabel", "usage": "The action as a verb.", "limit": 30}]},
6
+ {"type": "prose", "purpose": "Explains a topic at length, optionally beside an image.", "usage": "Use in the body for explanation.", "position": "body", "repeatable": true, "fields": [{"name": "heading", "usage": "The argument in a sentence.", "limit": 90}, {"name": "paragraphs", "usage": "Two or three standalone paragraphs.", "limit": 400, "repeats": {"min": 1, "max": 4}}]},
7
+ {"type": "cards", "purpose": "Presents parallel points side by side.", "usage": "Use for steps or genuinely parallel points.", "position": "body", "repeatable": true, "fields": [{"name": "heading", "usage": "What the cards share.", "limit": 90}, {"name": "items", "usage": "The parallel points.", "limit": 220, "repeats": {"min": 3, "max": 3}}]},
8
+ {"type": "links", "purpose": "Sends the reader to related pages.", "usage": "Near the end after the page has done its job.", "position": "closing", "repeatable": true, "fields": [{"name": "heading", "usage": "What the links have in common.", "limit": 90}, {"name": "items", "usage": "Real related pages on this site.", "limit": 160, "repeats": {"min": 2, "max": 6}}]},
9
+ {"type": "faq", "purpose": "Answers questions a visitor would otherwise ask.", "usage": "Once near the end; use real visitor questions.", "position": "closing", "repeatable": false, "fields": [{"name": "heading", "usage": "The question topic.", "limit": 90}, {"name": "items", "usage": "Direct questions and answers.", "limit": 600, "repeats": {"min": 2, "max": 8}}]},
10
+ {"type": "cta", "purpose": "Makes the closing ask.", "usage": "Last on the page; once.", "position": "closing", "repeatable": false, "fields": [{"name": "heading", "usage": "The invitation in a sentence.", "limit": 90, "required": true}, {"name": "body", "usage": "What happens next.", "limit": 220}, {"name": "label", "usage": "The action as a verb.", "limit": 30, "required": true}]}
11
+ ]
12
+ }
@@ -0,0 +1,75 @@
1
+ #!/usr/bin/env python3
2
+ """Use a short-lived MaggieDash host token for validated content writes."""
3
+
4
+ from __future__ import annotations
5
+
6
+ import argparse
7
+ import json
8
+ import os
9
+ import sys
10
+ from pathlib import Path
11
+ from urllib.error import HTTPError, URLError
12
+ from urllib.request import Request, urlopen
13
+
14
+
15
+ SENSITIVE_KEYS = {"databaseUrl", "databaseURL", "password", "secret", "token", "apiKey"}
16
+
17
+
18
+ def validate_payload(value: object) -> dict:
19
+ if not isinstance(value, dict):
20
+ raise ValueError("payload must be a JSON object")
21
+ forbidden = sorted(SENSITIVE_KEYS.intersection(value))
22
+ if forbidden:
23
+ raise ValueError("payload must not contain credentials; use the short-lived token environment variable")
24
+ if not value.get("op"):
25
+ raise ValueError("payload.op is required")
26
+ return value
27
+
28
+
29
+ def write(url: str, token_env: str, payload: dict, timeout: int = 30) -> dict:
30
+ token = os.environ.get(token_env, "")
31
+ if not token:
32
+ raise ValueError(f"{token_env} is not set")
33
+ request = Request(url, data=json.dumps(payload, ensure_ascii=False).encode("utf-8"), method="POST", headers={
34
+ "Accept": "application/json", "Content-Type": "application/json", "Authorization": f"Bearer {token}",
35
+ })
36
+ try:
37
+ with urlopen(request, timeout=timeout) as response:
38
+ raw = response.read().decode("utf-8", errors="replace")
39
+ status = response.status
40
+ except HTTPError as error:
41
+ raw = error.read().decode("utf-8", errors="replace")
42
+ status = error.code
43
+ except URLError as error:
44
+ raise RuntimeError("content host could not be reached") from error
45
+ try:
46
+ body = json.loads(raw) if raw else {}
47
+ except json.JSONDecodeError:
48
+ body = {"error": "host returned non-JSON"}
49
+ if isinstance(body, dict):
50
+ for key in SENSITIVE_KEYS:
51
+ body.pop(key, None)
52
+ return {"status": status, "passed": 200 <= status < 300, "response": body}
53
+
54
+
55
+ def main() -> int:
56
+ parser = argparse.ArgumentParser(description=__doc__)
57
+ sub = parser.add_subparsers(dest="command", required=True)
58
+ command = sub.add_parser("write", help="POST a validated content operation")
59
+ command.add_argument("--url", required=True)
60
+ command.add_argument("--token-env", default="MAGGIE_CONTENT_TOKEN")
61
+ command.add_argument("--payload", type=Path, required=True)
62
+ command.add_argument("--timeout", type=int, default=30)
63
+ args = parser.parse_args()
64
+ try:
65
+ payload = validate_payload(json.loads(args.payload.read_text(encoding="utf-8")))
66
+ result = write(args.url, args.token_env, payload, args.timeout)
67
+ print(json.dumps(result, ensure_ascii=False, indent=2))
68
+ return 0 if result["passed"] else 1
69
+ except (OSError, ValueError, RuntimeError) as error:
70
+ print(f"agent-content: {error}", file=sys.stderr)
71
+ return 2
72
+
73
+
74
+ if __name__ == "__main__":
75
+ raise SystemExit(main())