@topy-ai/maggie 0.7.17 → 0.7.19

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.md CHANGED
@@ -76,6 +76,24 @@ maggie qa summary --project . --run <run-id>
76
76
  The QA workflow stores secret-free run state under `.maggie/qa-runs/` and
77
77
  keeps project-specific scenarios and evidence outside the npm package.
78
78
 
79
+ Dashboard and documentation audits are provider-neutral and keep host data
80
+ behind explicit evidence files:
81
+
82
+ ```bash
83
+ maggie dash api-contract --spec .maggie/openapi.json
84
+ maggie dash api-contract --spec https://example.test/openapi.json
85
+ maggie dash schema-audit --inventory .maggie/schema-inventory.json --fail-on-unread
86
+ maggie dash ui runtime-validate --evidence .maggie/dashboard-runtime.json
87
+ maggie docs audit --project . --docs-dir docs --output docs/documentation-audit.json
88
+ ```
89
+
90
+ The API checker accepts a local JSON file or a public HTTPS URL and validates
91
+ the response in memory. Schema audits use host-provided table, row-count, and
92
+ reader evidence; they never guess from a production database. Runtime
93
+ evidence proves that a dashboard screen mounted and executed its checks, so a
94
+ static component scan cannot pass by itself. MaggieDash hosts may also expose
95
+ the provider-neutral activity-log and managed-navigation contracts.
96
+
79
97
  The current package also includes reusable safeguards from the latest feedback
80
98
  review: `maggie dash api-contract` checks declared request and 2xx response
81
99
  shapes; `maggie blog check-gate`/`approve` enforces review before publish;
@@ -88,6 +106,23 @@ hours. `maggie seo social-cards` checks per-page OG image dimensions and format;
88
106
  Dash inventory separates renderer kind from public page kind while reporting
89
107
  source coverage for code-rendered routes.
90
108
 
109
+ Service booking providers can be checked with a machine-readable,
110
+ fixture-backed capability matrix:
111
+
112
+ ```bash
113
+ maggie service capability-audit --project . \
114
+ --catalogue .maggie/booking/services.json \
115
+ --capabilities-file .maggie/booking/provider-capabilities.json \
116
+ --fixture .maggie/booking/fixtures/provider.json
117
+ ```
118
+
119
+ The declaration records variant mode (`native`, `derived-offers`,
120
+ `single-fallback`, or `blocked`), price semantics, and stable-ID confidence.
121
+ The audit also records observed counts and fixture evidence. Localized service
122
+ variants use market/locale-aware canonical routes and reciprocal hreflang.
123
+ Release preflight consumes the latest matching scenario QA run when one is
124
+ configured and blocks incomplete evidence.
125
+
91
126
  For Google integrations, validate a redacted provider matrix before reporting
92
127
  access. The command fails closed on unknown scopes, missing Ads prerequisites,
93
128
  duplicate provider resources, and unverified edit/publish claims:
@@ -134,7 +169,10 @@ maggie dash transition ... # explicit content approval transition
134
169
  maggie dash variant ... # service variant create/review/preview/publish
135
170
  maggie dash sections ... # field fan-out, locale, binding, media, copy, identity keys
136
171
  maggie dash inventory ... # disjoint renderer/page-kind inventory + coverage
137
- maggie dash api-contract ... # declared API body and 2xx response contract
172
+ maggie dash api-contract ... # local/live declared API body and 2xx contract
173
+ maggie dash schema-audit ... # host schema reader-evidence audit
174
+ maggie dash ui runtime-validate ... # mounted dashboard runtime evidence
175
+ maggie docs audit ... # documentation hygiene audit
138
176
  maggie agent-content write ... # host-authorized, origin-bound content bridge
139
177
  maggie verification coverage ... # changed surface/locale evidence gate
140
178
  maggie clone ... # authorized homepage capture
@@ -145,7 +183,7 @@ maggie memory ... # confirmed preferences and lessons
145
183
  maggie feedback ... # redact, preview, submit, list
146
184
  maggie qa ... # scenario browser QA, fix/retest, release gate
147
185
  maggie localization ... # plan, validate, review, publish, stale
148
- maggie service ... # import, sync, generate, validate
186
+ maggie service ... # import, sync, capability audit, validate
149
187
  maggie seo performance ... # sampled PageSpeed/CWV report and baseline
150
188
  maggie seo images ... # inventory, variants, confirmation, validate
151
189
  maggie seo sitemap ... # typed/semantic plan, agent-files, apply, rollback
@@ -267,8 +305,8 @@ artifact schemas.
267
305
  Recommended upgrade sequence for the current release:
268
306
 
269
307
  ```bash
270
- npx @topy-ai/maggie@0.7.17 update --project . --force
271
- npx @topy-ai/maggie@0.7.17 cleanup --project .
308
+ npx @topy-ai/maggie@0.7.19 update --project . --force
309
+ npx @topy-ai/maggie@0.7.19 cleanup --project .
272
310
  ```
273
311
 
274
312
  Maintainers should pass npm credentials through the repository helper, never
@@ -278,7 +316,13 @@ as a command-line argument:
278
316
  node scripts/publish-npm.mjs --maggie-env-file ../.env
279
317
  ```
280
318
 
281
- The 0.7.17 workflow adds sanitized database-backed blog gate adapters, deployed
319
+ The 0.7.19 workflow adds documentation hygiene audits, live URL API contract
320
+ validation, host-supplied schema reader audits, dashboard runtime evidence,
321
+ and provider-neutral activity-log and managed-navigation contracts. The 0.7.18
322
+ workflow adds staged/untracked changed-surface detection, scenario
323
+ QA release integration, market/locale-aware service variant routes and
324
+ reciprocal hreflang, provider capability matrices with fixture evidence, and
325
+ feedback fixed-proof/path-privacy gates. The 0.7.17 workflow adds sanitized database-backed blog gate adapters, deployed
282
326
  IndexNow key verification, social-card and cross-shell head-tag audits, required
283
327
  Open Graph image coverage, and behavioural favicon verification. The 0.7.16
284
328
  workflow tightens the review-gate exit code and adds retry-path regression
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.7.16 init --agent all
11
+ npx @topy-ai/maggie@0.7.19 init --agent all
12
12
  npx @topy-ai/maggie doctor --project .
13
13
  ```
14
14
 
@@ -25,7 +25,11 @@ maggie doctor --project . --require-bootstrap --strict
25
25
  deployment、memory、feedback 和 MaggieDash。內容先 draft/review,外部寫入、
26
26
  publish 與 production deployment 需要明確確認。
27
27
 
28
- 0.7.16 補強 review gate 的 CI exit code 與 IndexNow retry regression;
28
+ 0.7.19 加入文件 hygiene audit、live URL API contract、schema reader audit、
29
+ dashboard runtime evidence,以及 activity-log 和 managed-navigation contracts。
30
+ 0.7.18 加入 staged/untracked changed-surface release gate、QA run 與 release
31
+ 整合、market/locale-aware service variant route、provider capability matrix
32
+ fixture evidence,以及 feedback fixed-proof/path privacy gate。0.7.16 補強 review gate 的 CI exit code 與 IndexNow retry regression;
29
33
  0.7.15 也加入 API schema contract、blog review gate、sitemap freshness
30
34
  warning、change-driven IndexNow 與 page-kind/source-coverage inventory。
31
35
 
@@ -59,6 +63,12 @@ maggie migration identity --identity-file .maggie/db-identity.json \
59
63
  maggie qa start --project . --scenario-file .maggie/scenario-manifest.json \
60
64
  --environment local --base-url http://localhost:4321
61
65
  maggie qa summary --project . --run <run-id>
66
+
67
+ # Provider variant capability matrix
68
+ maggie service capability-audit --project . \
69
+ --catalogue .maggie/booking/services.json \
70
+ --capabilities-file .maggie/booking/provider-capabilities.json \
71
+ --fixture .maggie/booking/fixtures/provider.json
62
72
  ```
63
73
 
64
74
  完整中文說明、19 個 skills 清單和 roadmap:
package/bin/maggie.js CHANGED
@@ -61,6 +61,9 @@ Usage:
61
61
  maggie dash sections <validate|prompt|keys|remap-translations|fanout-validate|locale-validate|variant-copy-validate|media-validate|bindings-validate|idempotency-validate|reconcile> [options]
62
62
  maggie dash inventory --pages-file FILE [--require-page-kinds] [--require-source-coverage]
63
63
  maggie dash api-contract --spec FILE
64
+ maggie dash api-contract --spec https://example.test/openapi.json
65
+ maggie dash schema-audit --inventory FILE [--fail-on-unread]
66
+ maggie dash ui runtime-validate --evidence FILE
64
67
  maggie dash components-audit --bindings-file FILE --sections-file FILE --pages-file FILE
65
68
  maggie dash status --project PATH
66
69
  maggie dash migrate --project PATH --confirm
@@ -98,6 +101,7 @@ Usage:
98
101
  maggie service import <provider-url> --project PATH
99
102
  maggie service sync <provider-url> --project PATH
100
103
  maggie service generate --project PATH
104
+ maggie service capability-audit --project PATH --catalogue FILE --capabilities-file FILE --fixture FILE
101
105
  maggie service validate --project PATH
102
106
  maggie service inspect --project PATH
103
107
  maggie service status --project PATH
@@ -300,6 +304,11 @@ function service(args) {
300
304
  process.exitCode = result.status ?? 1;
301
305
  }
302
306
 
307
+ function docs(args) {
308
+ if (args[0] && args[0] !== "audit") throw new Error("docs command must be audit");
309
+ workflowCli("maggie_docs.py", args);
310
+ }
311
+
303
312
  function seo(args) {
304
313
  const command = args[0];
305
314
  const scripts = { performance: "maggie_performance.py", images: "maggie_images.py", sitemap: "maggie_sitemap.py", indexnow: "maggie_indexnow.py", "social-cards": "maggie_social_cards.py", "head-tags": "maggie_head_tags.py" };
@@ -395,6 +404,7 @@ try {
395
404
  else if (command === "auth") workflowCli("maggie_auth.py", args);
396
405
  else if (command === "blog") workflowCli("maggie_blog.py", args);
397
406
  else if (command === "service") service(args);
407
+ else if (command === "docs") docs(args);
398
408
  else if (command === "seo") seo(args);
399
409
  else if (command === "ops") workflowCli("maggie_ops.py", args);
400
410
  else if (command === "deployment" && args[0] === "canary") workflowCli("maggie_deployment_canary.py", args.slice(1));
@@ -42,6 +42,25 @@ but they may not change the required identity, status, provenance, or approval
42
42
  fields. Unknown fields must be preserved or reported as unsupported during
43
43
  import.
44
44
 
45
+ ## Operational UI contracts
46
+
47
+ - [`activity-log-v1.json`](activity-log-v1.json) defines append-only activity
48
+ events, actor/action taxonomy, and the metadata boundary;
49
+ - [`navigation-v1.json`](navigation-v1.json) defines locale-aware managed menu
50
+ items with stable ordering, nesting, and safe destinations;
51
+ - [`dashboard-runtime-v1.json`](dashboard-runtime-v1.json) defines evidence
52
+ that an admin screen mounted, ran its checks, and completed its data calls.
53
+
54
+ These are adapter-facing interfaces, not database schemas. A host may use
55
+ PostgreSQL, SQLite, or another first-party store. Activity recording happens
56
+ after a successful caller write, uses the same transaction/client where
57
+ available, redacts credential-like metadata, and must never make the business
58
+ operation fail. Retention/pruning is explicit. Navigation reads fall back to
59
+ site markup when no published menu exists or the adapter is unavailable.
60
+ Dashboard browser checks must emit runtime evidence; a static component scan
61
+ or JSON declaration is not proof that a screen mounted or that its data calls
62
+ ran.
63
+
45
64
  ## Status model
46
65
 
47
66
  Content transitions are explicit:
@@ -0,0 +1,27 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "$id": "https://maggiedash.noblox.app/contracts/activity-log-v1.json",
4
+ "title": "MaggieDash first-party activity log contract v1",
5
+ "type": "object",
6
+ "required": ["schemaVersion", "events"],
7
+ "properties": {
8
+ "schemaVersion": {"const": "maggiedash-activity-log.v1"},
9
+ "events": {
10
+ "type": "array",
11
+ "items": {
12
+ "type": "object",
13
+ "required": ["id", "occurredAt", "actor", "action"],
14
+ "properties": {
15
+ "id": {"type": "string", "minLength": 1},
16
+ "occurredAt": {"type": "string", "format": "date-time"},
17
+ "actor": {"type": "string", "minLength": 1},
18
+ "action": {"type": "string", "pattern": "^[a-z_]+\\.[a-z_]+$"},
19
+ "summary": {"type": "string"},
20
+ "metadata": {"type": "object", "additionalProperties": true}
21
+ },
22
+ "additionalProperties": false
23
+ }
24
+ }
25
+ },
26
+ "additionalProperties": false
27
+ }
@@ -0,0 +1,33 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "$id": "https://maggiedash.noblox.app/contracts/dashboard-runtime-v1.json",
4
+ "title": "MaggieDash dashboard runtime evidence contract v1",
5
+ "type": "object",
6
+ "required": ["schemaVersion", "screen", "route", "mounted", "checks"],
7
+ "properties": {
8
+ "schemaVersion": {"const": "maggiedash-dashboard-runtime.v1"},
9
+ "screen": {"type": "string", "minLength": 1},
10
+ "route": {"type": "string", "minLength": 1},
11
+ "mounted": {"const": true},
12
+ "checks": {
13
+ "type": "array",
14
+ "minItems": 1,
15
+ "items": {
16
+ "type": "object",
17
+ "required": ["name", "passed"],
18
+ "properties": {"name": {"type": "string", "minLength": 1}, "passed": {"type": "boolean"}},
19
+ "additionalProperties": false
20
+ }
21
+ },
22
+ "dataCalls": {
23
+ "type": "array",
24
+ "items": {
25
+ "type": "object",
26
+ "required": ["url", "ok"],
27
+ "properties": {"url": {"type": "string"}, "method": {"type": "string"}, "status": {"type": "integer"}, "ok": {"type": "boolean"}},
28
+ "additionalProperties": false
29
+ }
30
+ }
31
+ },
32
+ "additionalProperties": false
33
+ }
@@ -0,0 +1,29 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "$id": "https://maggiedash.noblox.app/contracts/navigation-v1.json",
4
+ "title": "MaggieDash managed navigation contract v1",
5
+ "type": "object",
6
+ "required": ["schemaVersion", "location", "locale", "items"],
7
+ "properties": {
8
+ "schemaVersion": {"const": "maggiedash-navigation.v1"},
9
+ "location": {"type": "string", "minLength": 1},
10
+ "locale": {"type": "string", "minLength": 2},
11
+ "items": {
12
+ "type": "array",
13
+ "items": {
14
+ "type": "object",
15
+ "required": ["id", "label", "destination", "order"],
16
+ "properties": {
17
+ "id": {"type": "string", "minLength": 1},
18
+ "label": {"type": "string", "minLength": 1},
19
+ "destination": {"type": "string", "minLength": 1},
20
+ "order": {"type": "integer", "minimum": 0},
21
+ "parentId": {"type": ["string", "null"]},
22
+ "newTab": {"type": "boolean"}
23
+ },
24
+ "additionalProperties": false
25
+ }
26
+ }
27
+ },
28
+ "additionalProperties": false
29
+ }
@@ -25,8 +25,10 @@ when a title changes.
25
25
  "title": "Signature Scalp Ritual",
26
26
  "description": "Provider-supplied factual description.",
27
27
  "category": {"level1": "Head Spa", "level2": "Scalp Treatments"},
28
- "variants": [{
28
+ "variants": [{
29
29
  "id": "service-123:60",
30
+ "idSource": "provider",
31
+ "variantSource": "native",
30
32
  "title": "60 minutes",
31
33
  "durationMinutes": 60,
32
34
  "price": {"amountMinor": 9500, "currency": "GBP", "display": "£95.00"}
@@ -147,3 +149,36 @@ Provider adapters may add `raw` evidence in a private local snapshot, but
147
149
  public page generation consumes only the canonical fields above. A sync must
148
150
  preserve removed records as `archived` with `removedAt` and must output a
149
151
  change report.
152
+
153
+ ## Provider variant capability matrix
154
+
155
+ Before service pages are published, each provider declares its variant
156
+ behavior in a project-owned JSON file. The declaration is validated against a
157
+ sanitized fixture and emits `maggie-provider-variant-capabilities.v1`:
158
+
159
+ ```json
160
+ {
161
+ "schemaVersion": "maggie-provider-capabilities.v1",
162
+ "providers": [{
163
+ "provider": "fresha",
164
+ "variantMode": "native",
165
+ "priceSemantics": "minor-unit-and-display",
166
+ "stableIdConfidence": "high"
167
+ }]
168
+ }
169
+ ```
170
+
171
+ `variantMode` is one of `native`, `derived-offers`, `single-fallback`, or
172
+ `blocked`. `priceSemantics` records whether structured minor units and display
173
+ prices are available. `stableIdConfidence` must be justified by provider or
174
+ derived ID evidence. The audit records service/variant counts, observed source
175
+ types, fixture name and hash, and validation errors without copying fixture
176
+ rows or provider credentials. Run it with:
177
+
178
+ ```bash
179
+ maggie service capability-audit \
180
+ --project . \
181
+ --catalogue .maggie/booking/services.json \
182
+ --capabilities-file .maggie/booking/provider-capabilities.json \
183
+ --fixture .maggie/booking/fixtures/provider.json
184
+ ```
@@ -200,8 +200,21 @@ maggie dash sections bindings-validate --sections-file .maggie/sections.json \
200
200
  --references-file .maggie/reference-inventory.json
201
201
  maggie dash sections idempotency-validate --contract-file .maggie/reconcile-contract.json
202
202
 
203
- # Validate API body/response shapes before a client writes against an endpoint:
203
+ # Validate API body/response shapes before a client writes against an endpoint.
204
+ # The source can be a local JSON file or a public HTTPS URL:
204
205
  maggie dash api-contract --spec .maggie/openapi.json
206
+ maggie dash api-contract --spec https://example.test/openapi.json
207
+
208
+ # Find declared tables with no reader evidence; this never connects to a DB:
209
+ maggie dash schema-audit --inventory .maggie/schema-inventory.json \
210
+ --fail-on-unread
211
+
212
+ # Prove a browser check mounted the screen and executed its data calls:
213
+ maggie dash ui runtime-validate --evidence .maggie/dashboard-runtime.json
214
+
215
+ # Classify docs, show inbound references and completed progress trackers:
216
+ maggie docs audit --project . --docs-dir docs \
217
+ --output docs/documentation-audit.json
205
218
 
206
219
  # Classify every published page exactly once. `pageKind` describes the public
207
220
  # page family; `kind`/`rendererKind` describes how it is rendered. Keep these
@@ -219,6 +232,14 @@ ordinary, and blog routes, plus `sourceCoverage`. A host adapter must include
219
232
  code-rendered routes in the input or declare coverage incomplete; a zero
220
233
  database-row count is not evidence that no public routes exist.
221
234
 
235
+ The host can expose the optional `activity-log-v1` and `navigation-v1`
236
+ contracts through `/api/maggie/*` adapters. Activity events should be
237
+ append-only and non-blocking to the original write. Managed navigation should
238
+ be locale-aware, ordered, and safe to fall back to existing markup. Do not
239
+ claim a dashboard feature is tested from a static source scan alone: record a
240
+ `dashboard-runtime-v1` report with `mounted: true`, named checks, and the
241
+ actual data calls.
242
+
222
243
  When a host renders multiple shells, export a normalized head manifest and run
223
244
  `maggie seo head-tags audit`. This is the reusable boundary for detecting icon
224
245
  MIME drift, OG-image fallback drift, and verification-tag coverage; MaggieDash
@@ -32,7 +32,8 @@ response bodies in it.
32
32
 
33
33
  When a release changes a visitor-facing HTML, CSS, component, or asset surface,
34
34
  the release preflight automatically requires both a passing icon inventory and
35
- rendered canary evidence. The canary must include screenshots, zero console or
35
+ rendered canary evidence. It considers unstaged, staged, and untracked
36
+ visitor-facing files. The canary must include screenshots, zero console or
36
37
  network errors, and zero placeholder matches. Query-driven routes belong in
37
38
  behavior/API checks, not static byte baselines.
38
39
 
@@ -161,6 +162,13 @@ python3 tools/clis/maggie_release.py /path/to/project \
161
162
  --base-url https://staging.example.com
162
163
  ```
163
164
 
165
+ If `.maggie/scenario-manifest.json` or `.maggie/qa-runs/` exists, the same
166
+ preflight consumes the latest matching `maggie qa` run for the requested
167
+ environment (and `--base-url`, when supplied). It blocks when a scenario is
168
+ not passed, has no browser-adapter evidence, or the matching run is missing.
169
+ Projects without scenario QA report `not-configured`; Maggie does not run the
170
+ browser itself.
171
+
164
172
  This aggregates deployment, migration, provider-health, schedule, analytics,
165
173
  service-fact, editorial approval, durable SEO/route/category evidence,
166
174
  MaggieDash schema compatibility, and live runtime security-header checks. It writes
@@ -34,6 +34,11 @@ The draft is written to `.maggie/feedback/`. It contains the Maggie version,
34
34
  skill, run ID, phase, error fingerprint, expected/actual result, reproduction
35
35
  steps, resolution, validation, and metadata-only screenshot references.
36
36
 
37
+ If a draft is marked fixed with `--fixed` (or the supplied run report says
38
+ `fixed`), both `--resolution` and `--validation` are required. Feedback text
39
+ also scrubs common POSIX/home/temp and Windows absolute paths by default while
40
+ preserving public URLs and route paths.
41
+
37
42
  ## Submit explicitly
38
43
 
39
44
  The CLI never submits automatically. After reviewing the draft:
@@ -69,6 +69,11 @@ maggie qa record --project . --run local-qa-001 \
69
69
  the test failed, a fix. After a fix, retest the original scenario and at least
70
70
  one adjacent scenario affected by the same surface.
71
71
 
72
+ A passing `test` or `retest` requires at least one `--evidence` reference from
73
+ the host browser adapter. The adapter owns console, network, authentication,
74
+ URL, viewport, and screenshot capture; this skill stores only a relative path,
75
+ URL reference, and hash when a local file exists.
76
+
72
77
  ## Gate and release evidence
73
78
 
74
79
  The run gate is `fail` when any scenario fails, `blocked` when there is no
@@ -88,6 +93,12 @@ Before calling a release Pass, also run the relevant build, accessibility,
88
93
  SEO, media, API, and deployment-canary checks. A green build, HTTP 200, source
89
94
  class, or one guest smoke is not browser QA evidence.
90
95
 
96
+ `maggie release` consumes the latest matching run from `.maggie/qa-runs/` when
97
+ the project has a scenario manifest or QA run directory. A matching run must
98
+ have a passed summary, passing scenarios, a passing final test/retest event,
99
+ and browser evidence for every scenario. No manifest or run reports an
100
+ explicit `not-configured` state.
101
+
91
102
  ## Privacy and feedback
92
103
 
93
104
  Do not put passwords, tokens, cookies, full private URLs, personal data,
@@ -21,7 +21,8 @@ supporting relations for all suggested candidates. Inspect each service's
21
21
  `pages` after selection and validate the rendered links. The current selection
22
22
  path writes `canonical` for selected pages; multiple selections can violate
23
23
  the one-canonical invariant. Resolve roles and run validation before publishing.
24
- Variant role assignment and layout/URL conventions remain tracked in issue #27.
24
+ Variant role assignment and layout/URL conventions are enforced by the shared
25
+ market/locale route and reciprocal hreflang contract.
25
26
 
26
27
  ## Automatic memory hook
27
28
 
@@ -107,6 +108,14 @@ python3 tools/clis/maggie_service_booking.py category-audit \
107
108
  --project . --rendered-dir /tmp/category-rendered
108
109
 
109
110
  python3 tools/clis/maggie_service_booking.py category-context --project .
111
+
112
+ # Declare provider behavior, then validate it against the imported catalogue
113
+ # and a sanitized fixture before publishing service pages.
114
+ python3 tools/clis/maggie_service_booking.py capability-audit \
115
+ --project . \
116
+ --catalogue .maggie/booking/services.json \
117
+ --capabilities-file .maggie/booking/provider-capabilities.json \
118
+ --fixture .maggie/booking/fixtures/provider.json
110
119
  ```
111
120
 
112
121
  `import` creates the first catalogue. `sync` compares the newly imported
@@ -265,6 +274,15 @@ amounts silently. Include `Service` and `Offer` JSON-LD only from validated
265
274
  catalogue fields. Do not emit `Review`, `AggregateRating`, or medical claims
266
275
  unless verified source data is present.
267
276
 
277
+ Localized and market-specific variants use
278
+ `/services/<market>/<locale>/<variant-slug>/` as their canonical route. A
279
+ translation never reuses the source route. Published variants expose
280
+ reciprocal hreflang links only to published counterparts. Before generation,
281
+ each provider must declare `variantMode` (`native`, `derived-offers`,
282
+ `single-fallback`, or `blocked`), `priceSemantics`, and `stableIdConfidence`
283
+ in a machine-readable capability file. The capability audit requires a
284
+ sanitized fixture and fails closed on missing or contradictory evidence.
285
+
268
286
  ## AI category/page copy contract
269
287
 
270
288
  Read [`references/ai-service-copy-contract.md`](../../references/ai-service-copy-contract.md)
@@ -25,7 +25,9 @@ from service_variants import ServiceVariantStore # noqa: E402
25
25
  from maggie_dash_ui import load_and_validate # noqa: E402
26
26
  from maggie_sections import catalogue, remap_translations, section_id_migration, validate_registry, copy_notes, validate_values, validate_fanout, reconcile_fields, validate_locale_coverage # noqa: E402
27
27
  from maggie_quality import validate_variant_copy, validate_media_uniqueness, classify_inventory, validate_bindings, validate_reconcile_contract # noqa: E402
28
- from maggie_api_contract import validate_api_contract # noqa: E402
28
+ from maggie_api_contract import load_api_contract_source, validate_api_contract # noqa: E402
29
+ from maggie_dash_runtime import validate_runtime_evidence # noqa: E402
30
+ from maggie_schema_audit import audit_schema_inventory # noqa: E402
29
31
  from route_imports import classify_bindings # noqa: E402
30
32
 
31
33
 
@@ -316,6 +318,11 @@ def command_cms(args: argparse.Namespace) -> int:
316
318
 
317
319
 
318
320
  def command_ui(args: argparse.Namespace) -> int:
321
+ if args.ui_command == "runtime-validate":
322
+ value = json.loads(Path(args.evidence).resolve().read_text(encoding="utf-8"))
323
+ result = validate_runtime_evidence(value)
324
+ emit(result)
325
+ return 0 if result["passed"] else 1
319
326
  result = load_and_validate(Path(args.contract).resolve(), [Path(value).resolve() for value in args.source])
320
327
  emit(result)
321
328
  return 0 if result["passed"] else 1
@@ -429,8 +436,15 @@ def command_inventory(args: argparse.Namespace) -> int:
429
436
 
430
437
 
431
438
  def command_api_contract(args: argparse.Namespace) -> int:
432
- value = json.loads(Path(args.spec).resolve().read_text(encoding="utf-8"))
433
- result = validate_api_contract(value)
439
+ value, source = load_api_contract_source(args.spec)
440
+ result = {**validate_api_contract(value), "source": source}
441
+ emit(result)
442
+ return 0 if result["passed"] else 1
443
+
444
+
445
+ def command_schema_audit(args: argparse.Namespace) -> int:
446
+ value = json.loads(Path(args.inventory).resolve().read_text(encoding="utf-8"))
447
+ result = audit_schema_inventory(value, fail_on_unread=args.fail_on_unread)
434
448
  emit(result)
435
449
  return 0 if result["passed"] else 1
436
450
 
@@ -537,6 +551,9 @@ def parser() -> argparse.ArgumentParser:
537
551
  ui_validate.add_argument("--contract", required=True)
538
552
  ui_validate.add_argument("--source", action="append", default=[])
539
553
  ui_validate.set_defaults(func=command_ui)
554
+ ui_runtime = ui_sub.add_parser("runtime-validate", help="validate evidence that a dashboard screen mounted and ran")
555
+ ui_runtime.add_argument("--evidence", required=True)
556
+ ui_runtime.set_defaults(func=command_ui)
540
557
  sections = sub.add_parser("sections", help="validate section registry and stable section identities")
541
558
  sections_sub = sections.add_subparsers(dest="sections_command", required=True)
542
559
  sections_validate = sections_sub.add_parser("validate")
@@ -583,9 +600,13 @@ def parser() -> argparse.ArgumentParser:
583
600
  inventory.add_argument("--require-page-kinds", action="store_true", help="fail when a published page has no pageKind")
584
601
  inventory.add_argument("--require-source-coverage", action="store_true", help="fail unless sourceCoverage.complete is true")
585
602
  inventory.set_defaults(func=command_inventory)
586
- api_contract = sub.add_parser("api-contract", help="validate declared request and success-response schemas")
587
- api_contract.add_argument("--spec", required=True, help="OpenAPI-like JSON document")
603
+ api_contract = sub.add_parser("api-contract", help="validate a local or live declared request and success-response contract")
604
+ api_contract.add_argument("--spec", required=True, help="local JSON file or HTTPS URL (loopback HTTP is allowed for development)")
588
605
  api_contract.set_defaults(func=command_api_contract)
606
+ schema_audit = sub.add_parser("schema-audit", help="find declared tables with missing reader evidence")
607
+ schema_audit.add_argument("--inventory", required=True, help="host-produced schema inventory JSON")
608
+ schema_audit.add_argument("--fail-on-unread", action="store_true")
609
+ schema_audit.set_defaults(func=command_schema_audit)
589
610
  variant = sub.add_parser("variant", help="manage service variant lifecycle")
590
611
  variant_sub = variant.add_subparsers(dest="variant_command", required=True)
591
612
  create = variant_sub.add_parser("create"); create.add_argument("--project", default="."); create.add_argument("--service-id", required=True); create.add_argument("--variant-id", required=True); create.add_argument("--variant-type", required=True); create.add_argument("--locale", required=True); create.add_argument("--market", required=True); create.add_argument("--slug", required=True); create.add_argument("--title", required=True); create.add_argument("--facts", required=True); create.add_argument("--source-revision", required=True); create.add_argument("--canonical-variant-id"); create.add_argument("--cluster-link", action="append", default=[]); create.add_argument("--layout-family", default="service-default"); create.add_argument("--confirm", action="store_true")