@topy-ai/maggie 0.7.41 → 0.7.43

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 CHANGED
@@ -8,11 +8,16 @@ Codex、Claude Code 與相容的 coding agents。
8
8
  ## 安裝
9
9
 
10
10
  ```bash
11
- npx @topy-ai/maggie@0.7.41 init --agent all
12
- npx @topy-ai/maggie@0.7.41 doctor --project .
11
+ npx @topy-ai/maggie@0.7.43 init --agent all
12
+ npx @topy-ai/maggie@0.7.43 doctor --project .
13
13
  ```
14
14
 
15
- 目前 release 是 `0.7.41`,完成 feedback batches 22、23:加入 declarative
15
+ 目前 release 是 `0.7.43`。新增 secret-free Starter marketplace provisioning
16
+ plan、versioned GitHub/Cloudflare/Starter contracts,以及 deterministic
17
+ `maggie marketplace plan` dry-run CLI。0.7.42 新增 redacted Gemini video-provider evidence gate,
18
+ 驗證 provider acknowledgement、idempotency、moderation、object-storage
19
+ readback、bounded retry 與 git provenance,並拒絕 raw provider payload 或
20
+ input asset 欄位。0.7.41 完成 feedback batches 22、23:加入 declarative
16
21
  browser interaction evidence、design markup/style/content-key gate、compiler
17
22
  source anchoring、entity double-escape protection、`update --help` read-only,
18
23
  以及 noindex hypothetical indexability、可過期 baseline acknowledgement、
@@ -41,6 +46,8 @@ maggie provenance --project . --source src/content/post.md
41
46
  maggie scaffold --project . --framework astro --language typescript --write --confirm
42
47
  maggie brand-kit --project . --design DESIGN.md --write --confirm
43
48
  maggie npm11 --project . --smoke --confirm
49
+ maggie media video-provider-evidence --evidence .maggie/video-provider-evidence.json \
50
+ --environment staging --model gemini-omni-1.1-flash --require-host
44
51
  maggie media video-playback --evidence .maggie/video-playback-evidence.json
45
52
  maggie booking-delivery --policy .maggie/booking-delivery-policy.json
46
53
  ```
@@ -73,7 +80,7 @@ structure drift 或重複 treatment product 時 fail closed。
73
80
  deployment、memory、feedback 和 MaggieDash。內容先 draft/review,外部寫入、
74
81
  publish 與 production deployment 需要明確確認。
75
82
 
76
- 目前 source tree 的正式 release 是 `0.7.41`。本版完成 feedback batches 22、23:加入 declarative browser interaction evidence、design markup/style/content-key gate、compiler source anchoring、entity double-escape protection、`update --help` read-only、noindex hypothetical indexability、可過期 baseline acknowledgement、DOM-only link extraction regression,以及 MaggieDash 0.2.10 的 content override、短期 browser session、CDN-safe public-session cache contracts。authentication 和 isolated dev server 仍由 host 負責。`maggie-booking` workflow、傳統 email/password、Stripe、Resend、public booking flow 及既有安全邊界維持不變;provider-bound video generation 與 transactional delivery 仍記錄在 issue #39,前一批延後功能仍在 issue #38,不會直接成為 active memory。0.7.33 記錄並審查一筆更正回饋;確認 0.7.32 已正確處理 batch index,沒有修改已驗證的 shared behavior。0.7.32 加入 feedback 一基索引支援、MaggieDash panel 的 source/freshness/error evidence、Consent Mode 與實際 write/no-op reconciliation、SEO baseline recapture、sitemap origin rebasing、content-diff scope、migration ledger reconciliation、deployment credential preflight、release provenance/runtime preflight、icon release gate、opt-in blog auto-publish gate,以及 least-privilege VPS deployer。也修復 top-level `maggie feedback` dispatcher parity。0.7.31 加入 update 差異摘要、atomic installer copy、subset install manifest merge,以及 inventory drift 時 doctor non-zero。0.7.30 補上 feedback tracking issue closeout gate 與 batch 14/15 audit ledger。0.7.29 修正 npm package landing page 使用英文 README;0.7.28 加入 versioned Gemini model policy、明確 fallback 與 provenance、bundled-first CLI
83
+ 目前 source tree 的正式 release 是 `0.7.43`。本版新增 Starter marketplace provisioning plan;本版完成 feedback batches 22、23:加入 declarative browser interaction evidence、design markup/style/content-key gate、compiler source anchoring、entity double-escape protection、`update --help` read-only、noindex hypothetical indexability、可過期 baseline acknowledgement、DOM-only link extraction regression,以及 MaggieDash 0.2.10 的 content override、短期 browser session、CDN-safe public-session cache contracts。authentication 和 isolated dev server 仍由 host 負責。`maggie-booking` workflow、傳統 email/password、Stripe、Resend、public booking flow 及既有安全邊界維持不變;provider-bound video generation 與 transactional delivery 仍記錄在 issue #39,前一批延後功能仍在 issue #38,不會直接成為 active memory。0.7.33 記錄並審查一筆更正回饋;確認 0.7.32 已正確處理 batch index,沒有修改已驗證的 shared behavior。0.7.32 加入 feedback 一基索引支援、MaggieDash panel 的 source/freshness/error evidence、Consent Mode 與實際 write/no-op reconciliation、SEO baseline recapture、sitemap origin rebasing、content-diff scope、migration ledger reconciliation、deployment credential preflight、release provenance/runtime preflight、icon release gate、opt-in blog auto-publish gate,以及 least-privilege VPS deployer。也修復 top-level `maggie feedback` dispatcher parity。0.7.31 加入 update 差異摘要、atomic installer copy、subset install manifest merge,以及 inventory drift 時 doctor non-zero。0.7.30 補上 feedback tracking issue closeout gate 與 batch 14/15 audit ledger。0.7.29 修正 npm package landing page 使用英文 README;0.7.28 加入 versioned Gemini model policy、明確 fallback 與 provenance、bundled-first CLI
77
84
  dispatch、`maggie --version`、feedback batch review 聚合與重複偵測、marketplace enrichment evidence、booking
78
85
  worker/resolver evidence,以及 read-only migration preflight。它也包含 host-owned mobile app surface contract、signed-in
79
86
  camera-state QA、直接 Astro route resolution、correlated feedback batch,以及
package/README.md CHANGED
@@ -60,7 +60,16 @@ Maggie keeps the existing project foundation and asks for decisions before
60
60
  shared routes, analytics, or publishing boundaries change. The current
61
61
  package ships 20 installable skills and a local-first MaggieDash foundation.
62
62
 
63
- The current release is `0.7.41`. It closes feedback batches 22 and 23 with
63
+ The current release is `0.7.43`. It adds a secret-free Starter marketplace
64
+ provisioning plan and bundles versioned GitHub repository, Cloudflare Pages,
65
+ Starter site, and Starter release contracts. `maggie marketplace plan` emits a
66
+ deterministic dry-run plan for a public Cal.com URL, static Cloudflare Pages,
67
+ no database, and ordered idempotent steps; it never stores credentials or
68
+ mutates provider state. The 0.7.42 release adds a redacted Gemini video-provider
69
+ evidence gate for provider acknowledgement, idempotency, moderation,
70
+ object-storage readback, bounded retry, and git provenance; unknown raw
71
+ provider payload and input-asset fields fail validation. The 0.7.41 release
72
+ closes feedback batches 22 and 23 with
64
73
  declarative browser interaction evidence, stable design markup/style/content-key
65
74
  gates, compiler source anchoring, entity double-escape protection, and a
66
75
  read-only `update --help` path. It also adds hypothetical indexability
@@ -86,6 +95,8 @@ maggie provenance --project . --source src/content/post.md
86
95
  maggie scaffold --project . --framework astro --language typescript --write --confirm
87
96
  maggie brand-kit --project . --design DESIGN.md --write --confirm
88
97
  maggie npm11 --project . --smoke --confirm
98
+ maggie media video-provider-evidence --evidence .maggie/video-provider-evidence.json \
99
+ --environment staging --model gemini-omni-1.1-flash --require-host
89
100
  maggie media video-playback --evidence .maggie/video-playback-evidence.json
90
101
  maggie booking-delivery --policy .maggie/booking-delivery-policy.json
91
102
  ```
package/bin/maggie.js CHANGED
@@ -80,6 +80,7 @@ Usage:
80
80
  maggie remove [SKILL ...] [--project PATH] [--agent codex|claude|all]
81
81
  maggie cleanup --project PATH [--confirm]
82
82
  maggie marketplace add <template-url> clone <preview-url> --rename <id>
83
+ maggie marketplace plan --workspace-id ID --merchant-id ID --site-slug SLUG --cal-url URL [--output FILE]
83
84
  maggie marketplace list
84
85
  maggie marketplace suggest "premium London medical spa"
85
86
  maggie marketplace inspect <id>
@@ -124,6 +125,7 @@ Usage:
124
125
  maggie service convert-page <page-path> --project PATH
125
126
  maggie service match-pages --project PATH --pages-dir src/pages
126
127
  maggie deployment --project PATH --target vps-with-cloudflare-dns
128
+ maggie deployment worker-secrets --project PATH --environment staging --required RESEND_API_KEY
127
129
  maggie deployment readiness --project PATH
128
130
  maggie deployment parity --project PATH --evidence FILE --output FILE
129
131
  maggie deployment canary --project PATH --asset URL=SHA256 --render-report report.json --output docs/deployment-canary.json
@@ -135,7 +137,7 @@ Usage:
135
137
  maggie schedule PATH/.maggie/schedule.json --project PATH
136
138
  maggie analytics [traffic-audit|release-gate] --project PATH --environment staging
137
139
  maggie analytics gsc-readiness --gsc-evidence FILE
138
- maggie media [image-policy|video-policy|video-job|video-playback] [--policy/--job/--evidence FILE]
140
+ maggie media [image-policy|video-policy|video-job|video-provider-evidence|video-playback] [--policy/--job/--evidence FILE]
139
141
  maggie provenance --project PATH [--source PATH] [--output FILE]
140
142
  maggie scaffold --project PATH --framework astro|nextjs|sveltekit|nuxt|vite-react|vite-vue [--write --confirm]
141
143
  maggie brand-kit --manifest FILE | --project PATH --design DESIGN.md [--write --confirm]
@@ -0,0 +1,90 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "$id": "https://maggie.noblox.app/contracts/maggie-marketplace/cloudflare-pages-v1.schema.json",
4
+ "title": "Maggie marketplace Cloudflare Pages adapter result v1",
5
+ "type": "object",
6
+ "additionalProperties": false,
7
+ "required": ["schemaVersion", "provider", "state", "sourceRevision", "retry", "privacy"],
8
+ "properties": {
9
+ "schemaVersion": {"const": "maggie-marketplace-cloudflare-pages.v1"},
10
+ "provider": {"const": "cloudflare-pages"},
11
+ "state": {"enum": ["succeeded", "retryable", "dead-letter"]},
12
+ "projectOutcome": {"enum": ["created", "reused", "updated"]},
13
+ "domainOutcome": {"enum": ["created", "reused", "updated"]},
14
+ "project": {
15
+ "type": "object",
16
+ "additionalProperties": false,
17
+ "required": ["externalReference", "name", "productionBranch", "repositoryOwner", "repositoryName", "sourceType"],
18
+ "properties": {
19
+ "externalReference": {"type": "string", "minLength": 1, "maxLength": 200},
20
+ "name": {"type": "string", "pattern": "^[A-Za-z0-9][A-Za-z0-9_-]{0,62}$"},
21
+ "productionBranch": {"type": "string", "pattern": "^[A-Za-z0-9._/-]+$"},
22
+ "repositoryOwner": {"type": "string", "minLength": 1, "maxLength": 100},
23
+ "repositoryName": {"type": "string", "minLength": 1, "maxLength": 100},
24
+ "sourceType": {"enum": ["github", "direct-upload"]},
25
+ "canonicalUrl": {"type": ["string", "null"], "format": "uri", "pattern": "^https://"}
26
+ }
27
+ },
28
+ "domain": {
29
+ "type": "object",
30
+ "additionalProperties": false,
31
+ "required": ["externalReference", "name", "status", "validationStatus", "verificationStatus"],
32
+ "properties": {
33
+ "externalReference": {"type": "string", "minLength": 1, "maxLength": 255},
34
+ "name": {"type": "string", "pattern": "^[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\\.bea\\.gift$"},
35
+ "status": {"enum": ["initializing", "pending", "active", "deactivated", "blocked", "error", "unknown"]},
36
+ "validationStatus": {"type": "string", "minLength": 1, "maxLength": 80},
37
+ "verificationStatus": {"type": "string", "minLength": 1, "maxLength": 80}
38
+ }
39
+ },
40
+ "deployment": {
41
+ "type": "object",
42
+ "additionalProperties": false,
43
+ "required": ["externalReference", "environment", "stageStatus", "skipped"],
44
+ "properties": {
45
+ "externalReference": {"type": "string", "minLength": 1, "maxLength": 200},
46
+ "environment": {"enum": ["production", "preview", "unknown"]},
47
+ "sourceRevision": {"type": ["string", "null"], "pattern": "^[A-Za-z0-9._:/-]{1,200}$"},
48
+ "branch": {"type": ["string", "null"], "pattern": "^[A-Za-z0-9._/-]+$"},
49
+ "stageStatus": {"enum": ["success", "idle", "active", "failure", "canceled", "unknown"]},
50
+ "url": {"type": ["string", "null"], "format": "uri", "pattern": "^https://"},
51
+ "skipped": {"type": "boolean"}
52
+ }
53
+ },
54
+ "publicReadback": {
55
+ "type": "object",
56
+ "additionalProperties": false,
57
+ "required": ["url", "status", "https", "canonical"],
58
+ "properties": {
59
+ "url": {"type": "string", "format": "uri", "pattern": "^https://"},
60
+ "status": {"type": "integer", "minimum": 100, "maximum": 599},
61
+ "https": {"const": true},
62
+ "canonical": {"const": true},
63
+ "contentType": {"type": ["string", "null"], "maxLength": 160}
64
+ }
65
+ },
66
+ "sourceRevision": {"type": "string", "pattern": "^[A-Za-z0-9._:/-]{1,200}$"},
67
+ "errorCode": {"type": "string", "pattern": "^cloudflare_[a-z0-9_]{1,80}$"},
68
+ "retry": {
69
+ "type": "object",
70
+ "additionalProperties": false,
71
+ "required": ["allowed", "attempt", "maxAttempts"],
72
+ "properties": {
73
+ "allowed": {"type": "boolean"},
74
+ "attempt": {"type": "integer", "minimum": 1, "maximum": 3},
75
+ "maxAttempts": {"const": 3},
76
+ "afterSeconds": {"type": "integer", "minimum": 0, "maximum": 900}
77
+ }
78
+ },
79
+ "privacy": {
80
+ "type": "object",
81
+ "additionalProperties": false,
82
+ "required": ["tokenIncluded", "rawResponseIncluded", "secretFree"],
83
+ "properties": {
84
+ "tokenIncluded": {"const": false},
85
+ "rawResponseIncluded": {"const": false},
86
+ "secretFree": {"const": true}
87
+ }
88
+ }
89
+ }
90
+ }
@@ -0,0 +1,17 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "$id": "https://maggie.noblox.app/contracts/maggie-marketplace/enrichment-v1.schema.json",
4
+ "title": "Maggie marketplace enrichment evidence v1",
5
+ "type": "object",
6
+ "required": ["schemaVersion", "candidateId", "status", "passed", "errors", "crawl", "audit", "snapshot", "seoBackfill", "retry", "privacy"],
7
+ "properties": {
8
+ "schemaVersion": {"const": "maggie-marketplace-enrichment.v1"},
9
+ "candidateId": {"type": "string", "minLength": 1},
10
+ "status": {"enum": ["passed", "failed"]}, "passed": {"type": "boolean"},
11
+ "errors": {"type": "array", "items": {"type": "string"}},
12
+ "crawl": {"type": "object"}, "audit": {"type": "object"}, "snapshot": {"type": "object"}, "seoBackfill": {"type": "object"},
13
+ "retry": {"type": "object", "required": ["allowed"]},
14
+ "privacy": {"type": "object", "required": ["rawHtmlIncluded", "responseBodiesIncluded", "localPathsIncluded", "secretsRedacted"]}
15
+ },
16
+ "additionalProperties": false
17
+ }
@@ -0,0 +1,50 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "$id": "https://maggie.noblox.app/contracts/maggie-marketplace/github-repository-v1.schema.json",
4
+ "title": "Maggie marketplace GitHub repository adapter result v1",
5
+ "type": "object",
6
+ "additionalProperties": false,
7
+ "required": ["schemaVersion", "provider", "state", "sourceRevision", "retry", "privacy"],
8
+ "properties": {
9
+ "schemaVersion": {"const": "maggie-marketplace-github-repository.v1"},
10
+ "provider": {"const": "github"},
11
+ "state": {"enum": ["succeeded", "retryable", "dead-letter"]},
12
+ "outcome": {"enum": ["created", "reused", "updated"]},
13
+ "repository": {
14
+ "type": "object",
15
+ "additionalProperties": false,
16
+ "required": ["externalReference", "owner", "name", "private", "defaultBranch", "htmlUrl"],
17
+ "properties": {
18
+ "externalReference": {"type": "string", "pattern": "^[A-Za-z0-9_.-]+/[A-Za-z0-9_.-]+$"},
19
+ "owner": {"type": "string", "pattern": "^[A-Za-z0-9-]{1,39}$"},
20
+ "name": {"type": "string", "pattern": "^[A-Za-z0-9](?:[A-Za-z0-9._-]{0,98}[A-Za-z0-9])?$"},
21
+ "private": {"type": "boolean"},
22
+ "defaultBranch": {"type": "string", "pattern": "^[A-Za-z0-9._/-]+$"},
23
+ "htmlUrl": {"type": "string", "format": "uri", "pattern": "^https://github\\.com/"}
24
+ }
25
+ },
26
+ "sourceRevision": {"type": "string", "pattern": "^[A-Za-z0-9._:/-]{1,200}$"},
27
+ "errorCode": {"type": "string", "pattern": "^github_[a-z0-9_]{1,80}$"},
28
+ "retry": {
29
+ "type": "object",
30
+ "additionalProperties": false,
31
+ "required": ["allowed", "attempt", "maxAttempts"],
32
+ "properties": {
33
+ "allowed": {"type": "boolean"},
34
+ "attempt": {"type": "integer", "minimum": 1, "maximum": 3},
35
+ "maxAttempts": {"const": 3},
36
+ "afterSeconds": {"type": "integer", "minimum": 0, "maximum": 900}
37
+ }
38
+ },
39
+ "privacy": {
40
+ "type": "object",
41
+ "additionalProperties": false,
42
+ "required": ["tokenIncluded", "rawResponseIncluded", "secretFree"],
43
+ "properties": {
44
+ "tokenIncluded": {"const": false},
45
+ "rawResponseIncluded": {"const": false},
46
+ "secretFree": {"const": true}
47
+ }
48
+ }
49
+ }
50
+ }
@@ -0,0 +1,72 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "$id": "https://maggie.noblox.app/contracts/maggie-marketplace/starter-provisioning-v1.schema.json",
4
+ "title": "Maggie marketplace Starter provisioning plan v1",
5
+ "type": "object",
6
+ "additionalProperties": false,
7
+ "required": ["schemaVersion", "plan", "mode", "workspace", "site", "booking", "steps", "security", "nextActions"],
8
+ "properties": {
9
+ "schemaVersion": {"const": "maggie-marketplace-starter-provisioning.v1"},
10
+ "plan": {"const": "starter"},
11
+ "mode": {"const": "dry-run"},
12
+ "workspace": {
13
+ "type": "object",
14
+ "additionalProperties": false,
15
+ "required": ["workspaceId", "merchantId"],
16
+ "properties": {
17
+ "workspaceId": {"type": "string", "minLength": 1, "maxLength": 128, "pattern": "^[A-Za-z0-9_-]+$"},
18
+ "merchantId": {"type": "string", "minLength": 1, "maxLength": 128, "pattern": "^[A-Za-z0-9_-]+$"}
19
+ }
20
+ },
21
+ "site": {
22
+ "type": "object",
23
+ "additionalProperties": false,
24
+ "required": ["slug", "hostname", "repository", "branch", "runtime", "database"],
25
+ "properties": {
26
+ "slug": {"type": "string", "pattern": "^[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?$"},
27
+ "hostname": {"type": "string", "pattern": "^[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\\.bea\\.gift$"},
28
+ "repository": {"type": "string", "pattern": "^bea-merchant-[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?$"},
29
+ "templateId": {"type": "string", "pattern": "^[a-z0-9](?:[a-z0-9-]{0,118}[a-z0-9])?$"},
30
+ "branch": {"type": "string", "pattern": "^[A-Za-z0-9._/-]+$"},
31
+ "runtime": {"const": "cloudflare-pages-static"},
32
+ "database": {"const": "none"}
33
+ }
34
+ },
35
+ "booking": {
36
+ "type": "object",
37
+ "additionalProperties": false,
38
+ "required": ["mode", "sourceOfTruth", "publicUrl", "embed"],
39
+ "properties": {
40
+ "mode": {"const": "cal-public"},
41
+ "sourceOfTruth": {"const": "cal.com"},
42
+ "publicUrl": {"type": "string", "format": "uri", "pattern": "^https://"},
43
+ "embed": {"enum": ["link", "iframe"]}
44
+ }
45
+ },
46
+ "steps": {
47
+ "type": "array",
48
+ "minItems": 6,
49
+ "items": {
50
+ "type": "object",
51
+ "additionalProperties": false,
52
+ "required": ["id", "status", "idempotencyKey"],
53
+ "properties": {
54
+ "id": {"enum": ["entitlement", "repository", "site-deploy", "hostname", "booking", "release-readback"]},
55
+ "status": {"const": "planned"},
56
+ "idempotencyKey": {"type": "string", "pattern": "^starter:[a-f0-9]{64}$"}
57
+ }
58
+ }
59
+ },
60
+ "security": {
61
+ "type": "object",
62
+ "additionalProperties": false,
63
+ "required": ["secretsInArtifact", "calCredentialsStored", "databaseRequired"],
64
+ "properties": {
65
+ "secretsInArtifact": {"const": false},
66
+ "calCredentialsStored": {"const": false},
67
+ "databaseRequired": {"const": false}
68
+ }
69
+ },
70
+ "nextActions": {"type": "array", "minItems": 1, "items": {"type": "string", "minLength": 1}}
71
+ }
72
+ }
@@ -0,0 +1,71 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "$id": "https://maggie.noblox.app/contracts/maggie-marketplace/starter-release-v1.schema.json",
4
+ "title": "Maggie marketplace Starter release evidence v1",
5
+ "type": "object",
6
+ "additionalProperties": false,
7
+ "required": ["schemaVersion", "status", "checkedAt", "sourceRevision", "site", "booking", "artifact", "repository", "cloudflare", "errors", "security"],
8
+ "properties": {
9
+ "schemaVersion": {"const": "maggie-marketplace-starter-release.v1"},
10
+ "status": {"enum": ["passed", "blocked"]},
11
+ "checkedAt": {"type": "string", "format": "date-time"},
12
+ "sourceRevision": {"type": "string", "pattern": "^[A-Za-z0-9._:/-]{1,200}$"},
13
+ "site": {
14
+ "type": "object",
15
+ "additionalProperties": false,
16
+ "required": ["slug", "hostname", "templateId"],
17
+ "properties": {
18
+ "slug": {"type": "string", "pattern": "^[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?$"},
19
+ "hostname": {"type": "string", "pattern": "^[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\\.bea\\.gift$"},
20
+ "templateId": {"type": "string", "minLength": 1, "maxLength": 120}
21
+ }
22
+ },
23
+ "booking": {
24
+ "type": "object",
25
+ "additionalProperties": false,
26
+ "required": ["mode", "publicUrl", "embed", "readback"],
27
+ "properties": {
28
+ "mode": {"const": "cal-public"},
29
+ "publicUrl": {"type": "string", "format": "uri", "pattern": "^https://"},
30
+ "embed": {"enum": ["link", "iframe"]},
31
+ "readback": {"enum": ["passed", "failed"]}
32
+ }
33
+ },
34
+ "artifact": {
35
+ "type": "object",
36
+ "additionalProperties": false,
37
+ "required": ["readback", "fileCount"],
38
+ "properties": {"readback": {"enum": ["passed", "failed"]}, "fileCount": {"type": "integer", "minimum": 1}}
39
+ },
40
+ "repository": {
41
+ "type": "object",
42
+ "additionalProperties": false,
43
+ "required": ["readback", "externalReference", "branch"],
44
+ "properties": {
45
+ "readback": {"enum": ["passed", "failed"]},
46
+ "externalReference": {"type": ["string", "null"], "maxLength": 200},
47
+ "branch": {"type": ["string", "null"], "pattern": "^[A-Za-z0-9._/-]+$"},
48
+ "commitSha": {"type": ["string", "null"], "pattern": "^[A-Fa-f0-9]{7,64}$"}
49
+ }
50
+ },
51
+ "cloudflare": {
52
+ "type": "object",
53
+ "additionalProperties": false,
54
+ "required": ["readback", "projectReference", "deploymentReference", "hostnameReference", "publicUrl"],
55
+ "properties": {
56
+ "readback": {"enum": ["passed", "failed"]},
57
+ "projectReference": {"type": ["string", "null"], "maxLength": 200},
58
+ "deploymentReference": {"type": ["string", "null"], "maxLength": 200},
59
+ "hostnameReference": {"type": ["string", "null"], "maxLength": 255},
60
+ "publicUrl": {"type": ["string", "null"], "format": "uri", "pattern": "^https://"}
61
+ }
62
+ },
63
+ "errors": {"type": "array", "items": {"type": "string", "pattern": "^[a-z0-9_]{1,100}$"}},
64
+ "security": {
65
+ "type": "object",
66
+ "additionalProperties": false,
67
+ "required": ["secretsInEvidence", "databaseRequired"],
68
+ "properties": {"secretsInEvidence": {"const": false}, "databaseRequired": {"const": false}}
69
+ }
70
+ }
71
+ }
@@ -0,0 +1,56 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "$id": "https://maggie.noblox.app/contracts/maggie-marketplace/starter-site-v1.schema.json",
4
+ "title": "Maggie marketplace Starter site artifact v1",
5
+ "type": "object",
6
+ "additionalProperties": false,
7
+ "required": ["schemaVersion", "templateId", "sourceRevision", "site", "booking", "files", "security"],
8
+ "properties": {
9
+ "schemaVersion": {"const": "maggie-marketplace-starter-site.v1"},
10
+ "templateId": {"type": "string", "minLength": 1, "maxLength": 120},
11
+ "sourceRevision": {"type": "string", "pattern": "^[A-Za-z0-9._:/-]{1,200}$"},
12
+ "site": {
13
+ "type": "object",
14
+ "additionalProperties": false,
15
+ "required": ["slug", "hostname"],
16
+ "properties": {
17
+ "slug": {"type": "string", "pattern": "^[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?$"},
18
+ "hostname": {"type": "string", "pattern": "^[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\\.bea\\.gift$"}
19
+ }
20
+ },
21
+ "booking": {
22
+ "type": "object",
23
+ "additionalProperties": false,
24
+ "required": ["mode", "publicUrl", "embed"],
25
+ "properties": {
26
+ "mode": {"const": "cal-public"},
27
+ "publicUrl": {"type": "string", "format": "uri", "pattern": "^https://"},
28
+ "embed": {"enum": ["link", "iframe"]}
29
+ }
30
+ },
31
+ "files": {
32
+ "type": "array",
33
+ "minItems": 1,
34
+ "items": {
35
+ "type": "object",
36
+ "additionalProperties": false,
37
+ "required": ["path", "sha256", "bytes"],
38
+ "properties": {
39
+ "path": {"type": "string", "pattern": "^(?!/)(?!.*\\.\\.)[A-Za-z0-9_./-]+$"},
40
+ "sha256": {"type": "string", "pattern": "^[a-f0-9]{64}$"},
41
+ "bytes": {"type": "integer", "minimum": 0}
42
+ }
43
+ }
44
+ },
45
+ "security": {
46
+ "type": "object",
47
+ "additionalProperties": false,
48
+ "required": ["runtimeSecrets", "credentialsStored", "databaseRequired"],
49
+ "properties": {
50
+ "runtimeSecrets": {"const": false},
51
+ "credentialsStored": {"const": false},
52
+ "databaseRequired": {"const": false}
53
+ }
54
+ }
55
+ }
56
+ }
@@ -0,0 +1,25 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "$id": "https://maggie.noblox.app/contracts/maggie-media/video-provider-evidence-v1.schema.json",
4
+ "title": "Maggie video provider canary evidence v1",
5
+ "type": "object",
6
+ "required": ["schemaVersion", "environment", "provider", "model", "status", "hostVerified", "acknowledgement", "idempotency", "moderation", "storage", "retry", "rawProviderPayloadIncluded", "inputAssetDataIncluded", "sourceRevision"],
7
+ "properties": {
8
+ "schemaVersion": {"const": "maggie-video-provider-evidence.v1"},
9
+ "environment": {"enum": ["test", "staging", "production"]},
10
+ "provider": {"const": "google-gemini"},
11
+ "model": {"type": "string", "pattern": "^(gemini|veo)-"},
12
+ "status": {"const": "passed"},
13
+ "hostVerified": {"const": true},
14
+ "acknowledgement": {"type": "object", "required": ["accepted", "providerReferencePresent"], "properties": {"accepted": {"const": true}, "providerReferencePresent": {"const": true}}, "additionalProperties": false},
15
+ "idempotency": {"type": "object", "required": ["replayAccepted", "duplicateChargePrevented", "assetDeduplicated"], "properties": {"replayAccepted": {"const": true}, "duplicateChargePrevented": {"const": true}, "assetDeduplicated": {"const": true}}, "additionalProperties": false},
16
+ "moderation": {"type": "object", "required": ["preflightPassed", "postGenerationReviewPassed"], "properties": {"preflightPassed": {"const": true}, "postGenerationReviewPassed": {"const": true}}, "additionalProperties": false},
17
+ "storage": {"type": "object", "required": ["uploadAccepted", "metadataPersisted", "readbackPassed", "signedOrApprovedUrlPresent"], "properties": {"uploadAccepted": {"const": true}, "metadataPersisted": {"const": true}, "readbackPassed": {"const": true}, "signedOrApprovedUrlPresent": {"const": true}}, "additionalProperties": false},
18
+ "retry": {"type": "object", "required": ["transientFailureObserved", "bounded", "attempts"], "properties": {"transientFailureObserved": {"const": true}, "bounded": {"const": true}, "attempts": {"type": "integer", "minimum": 1, "maximum": 3}}, "additionalProperties": false},
19
+ "rawProviderPayloadIncluded": {"const": false},
20
+ "inputAssetDataIncluded": {"const": false},
21
+ "sourceRevision": {"type": "object", "required": ["commit", "branch", "dirty"], "properties": {"commit": {"type": "string"}, "branch": {"type": "string"}, "dirty": {"type": "boolean"}}, "additionalProperties": false},
22
+ "generatedAt": {"type": "string"}
23
+ },
24
+ "additionalProperties": false
25
+ }
@@ -145,6 +145,24 @@ This command never deploys. Production execution still requires the shared
145
145
  decision loop, explicit confirmation, migration/rollback details, and the
146
146
  provider-specific deploy command.
147
147
 
148
+ For Cloudflare Workers, check the required secret *names* before a canary. The
149
+ command never reads, prints, uploads, or changes secret values:
150
+
151
+ ```bash
152
+ maggie deployment worker-secrets --project . \
153
+ --environment staging \
154
+ --required DATABASE_URL \
155
+ --required BOOKING_TOKEN_SECRET \
156
+ --required RESEND_API_KEY \
157
+ --required RESEND_FROM_EMAIL \
158
+ --output .maggie/deployment/worker-secrets.json
159
+ ```
160
+
161
+ It calls `wrangler secret list --format json` and stores only configured and
162
+ missing binding names. A missing required name fails closed. Use a redacted
163
+ JSON name snapshot with `--snapshot` for CI or a host adapter that cannot
164
+ expose Cloudflare credentials to Maggie; the snapshot must contain names only.
165
+
148
166
  For a VPS adapter, check the project `.env` by variable name before reporting
149
167
  credentials as unavailable:
150
168
 
@@ -77,6 +77,110 @@ def credential_preflight_main(argv: list[str]) -> int:
77
77
  return 0 if result["passed"] else 1
78
78
 
79
79
 
80
+ def _secret_names(payload: object) -> list[str]:
81
+ """Extract only binding names from a Wrangler secret-list response."""
82
+ entries: object = payload
83
+ if isinstance(payload, dict):
84
+ entries = payload.get("secrets", payload.get("result", []))
85
+ if not isinstance(entries, list):
86
+ return []
87
+ names: set[str] = set()
88
+ for entry in entries:
89
+ if isinstance(entry, str):
90
+ name = entry
91
+ elif isinstance(entry, dict):
92
+ name = entry.get("name", entry.get("key", ""))
93
+ else:
94
+ continue
95
+ if isinstance(name, str) and re.fullmatch(r"[A-Z][A-Z0-9_]{1,127}", name):
96
+ names.add(name)
97
+ return sorted(names)
98
+
99
+
100
+ def worker_secret_preflight(
101
+ project: Path,
102
+ environment: str,
103
+ required: list[str],
104
+ snapshot: Path | None = None,
105
+ worker_name: str | None = None,
106
+ wrangler_command: list[str] | None = None,
107
+ ) -> dict:
108
+ """Read Worker secret names without reading or mutating secret values."""
109
+ required_names = sorted({name.strip() for name in required if name.strip()})
110
+ errors: list[str] = []
111
+ if not required_names:
112
+ errors.append("at least one --required secret name is required")
113
+ if any(not re.fullmatch(r"[A-Z][A-Z0-9_]{1,127}", name) for name in required_names):
114
+ errors.append("required secret names must match [A-Z][A-Z0-9_]{1,127}")
115
+
116
+ source = "cloudflare-wrangler-secret-list"
117
+ configured: list[str] = []
118
+ if not errors and snapshot:
119
+ source = "redacted-secret-name-snapshot"
120
+ try:
121
+ configured = _secret_names(json.loads(snapshot.read_text(encoding="utf-8")))
122
+ except (OSError, json.JSONDecodeError) as error:
123
+ errors.append(f"secret-name snapshot is unreadable: {error}")
124
+ elif not errors:
125
+ command = list(wrangler_command or ["npx", "wrangler"])
126
+ command.extend(["secret", "list", "--env", environment, "--format", "json"])
127
+ if worker_name:
128
+ command.extend(["--name", worker_name])
129
+ try:
130
+ completed = subprocess.run(command, cwd=project, capture_output=True, text=True, check=False, timeout=30)
131
+ except (OSError, subprocess.SubprocessError):
132
+ completed = None
133
+ if completed is None or completed.returncode != 0:
134
+ errors.append("Wrangler secret-list command failed; inspect authentication and Worker target")
135
+ else:
136
+ try:
137
+ configured = _secret_names(json.loads(completed.stdout))
138
+ except json.JSONDecodeError:
139
+ errors.append("Wrangler secret-list response was not valid JSON")
140
+
141
+ missing = sorted(set(required_names) - set(configured))
142
+ errors.extend(f"required Worker secret is missing: {name}" for name in missing)
143
+ return {
144
+ "schemaVersion": "maggie-worker-secrets.v1",
145
+ "project": str(project),
146
+ "provider": "cloudflare-workers",
147
+ "environment": environment,
148
+ "worker": worker_name,
149
+ "required": required_names,
150
+ "configured": configured,
151
+ "missing": missing,
152
+ "source": source,
153
+ "valuesReturned": False,
154
+ "passed": not errors,
155
+ "errors": errors,
156
+ "mutation": "not executed",
157
+ }
158
+
159
+
160
+ def worker_secret_preflight_main(argv: list[str]) -> int:
161
+ parser = argparse.ArgumentParser(description="Check Cloudflare Worker secret names without reading or changing secret values.")
162
+ parser.add_argument("--project", default=".")
163
+ parser.add_argument("--environment", choices=("staging", "production"), default="staging")
164
+ parser.add_argument("--required", action="append", default=[], help="required secret name; repeat for each binding")
165
+ parser.add_argument("--worker-name")
166
+ parser.add_argument("--snapshot", help="offline JSON containing only secret names")
167
+ parser.add_argument("--output")
168
+ args = parser.parse_args(argv)
169
+ result = worker_secret_preflight(
170
+ Path(args.project).expanduser().resolve(),
171
+ args.environment,
172
+ args.required,
173
+ Path(args.snapshot).expanduser().resolve() if args.snapshot else None,
174
+ args.worker_name,
175
+ )
176
+ if args.output:
177
+ output = Path(args.output).resolve()
178
+ output.parent.mkdir(parents=True, exist_ok=True)
179
+ output.write_text(json.dumps(result, indent=2, ensure_ascii=False) + "\n", encoding="utf-8")
180
+ print(json.dumps(result, indent=2, ensure_ascii=False))
181
+ return 0 if result["passed"] else 1
182
+
183
+
80
184
  def preflight(project: Path, target: str, environment: str) -> dict:
81
185
  package_path = project / "package.json"
82
186
  package = json.loads(package_path.read_text(encoding="utf-8")) if package_path.exists() else {}
@@ -390,6 +494,8 @@ def write_release_runner(path: Path, plan: dict) -> None:
390
494
  def main() -> int:
391
495
  if sys.argv[1:2] == ["credential-preflight"]:
392
496
  return credential_preflight_main(sys.argv[2:])
497
+ if sys.argv[1:2] == ["worker-secrets"]:
498
+ return worker_secret_preflight_main(sys.argv[2:])
393
499
  parser = argparse.ArgumentParser(description=__doc__)
394
500
  parser.add_argument("project", nargs="?", default=".")
395
501
  parser.add_argument("--target", choices=("detected", "cloudflare", "vps", "vps-with-cloudflare-dns", "gcp", "aws"), default="detected")
@@ -3,6 +3,7 @@
3
3
  from __future__ import annotations
4
4
 
5
5
  import argparse
6
+ import hashlib
6
7
  import json
7
8
  import re
8
9
  import shutil
@@ -10,6 +11,7 @@ import subprocess
10
11
  import sys
11
12
  import urllib.error
12
13
  import urllib.request
14
+ from urllib.parse import urlparse
13
15
  from datetime import datetime, timezone
14
16
  from html.parser import HTMLParser
15
17
  from html import escape, unescape
@@ -57,6 +59,137 @@ def now() -> str:
57
59
  return datetime.now(timezone.utc).isoformat()
58
60
 
59
61
 
62
+ STARTER_PLAN_SCHEMA_VERSION = "maggie-marketplace-starter-provisioning.v1"
63
+ STARTER_STEP_IDS = (
64
+ "entitlement",
65
+ "repository",
66
+ "site-deploy",
67
+ "hostname",
68
+ "booking",
69
+ "release-readback",
70
+ )
71
+
72
+
73
+ def _starter_slug(value: str) -> str:
74
+ slug = str(value or "").strip().lower()
75
+ if not re.fullmatch(r"[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?", slug):
76
+ raise ValueError("Starter site slug must be 1-63 lowercase letters, numbers, or hyphens")
77
+ return slug
78
+
79
+
80
+ def _starter_public_url(value: str) -> str:
81
+ url = str(value or "").strip()
82
+ parsed = urlparse(url)
83
+ if parsed.scheme != "https" or not parsed.netloc or parsed.username or parsed.password or parsed.fragment:
84
+ raise ValueError("Starter Cal public URL must be an https URL without credentials or a fragment")
85
+ return url
86
+
87
+
88
+ def build_starter_plan(
89
+ workspace_id: str,
90
+ merchant_id: str,
91
+ slug: str,
92
+ cal_url: str,
93
+ embed: str = "link",
94
+ branch: str = "main",
95
+ ) -> dict:
96
+ workspace = str(workspace_id or "").strip()
97
+ merchant = str(merchant_id or "").strip()
98
+ if not re.fullmatch(r"[A-Za-z0-9_-]{1,128}", workspace) or not re.fullmatch(r"[A-Za-z0-9_-]{1,128}", merchant):
99
+ raise ValueError("workspace and merchant IDs must use letters, numbers, underscores, or hyphens")
100
+ site_slug = _starter_slug(slug)
101
+ public_url = _starter_public_url(cal_url)
102
+ if embed not in {"link", "iframe"}:
103
+ raise ValueError("Starter booking embed must be link or iframe")
104
+ if not re.fullmatch(r"[A-Za-z0-9._/-]+", branch or ""):
105
+ raise ValueError("Starter branch contains unsupported characters")
106
+ fingerprint = hashlib.sha256(
107
+ "|".join((workspace, merchant, site_slug, public_url, embed, branch)).encode("utf-8")
108
+ ).hexdigest()
109
+ return {
110
+ "schemaVersion": STARTER_PLAN_SCHEMA_VERSION,
111
+ "plan": "starter",
112
+ "mode": "dry-run",
113
+ "workspace": {"workspaceId": workspace, "merchantId": merchant},
114
+ "site": {
115
+ "slug": site_slug,
116
+ "hostname": f"{site_slug}.bea.gift",
117
+ "repository": f"bea-merchant-{site_slug}",
118
+ "branch": branch,
119
+ "runtime": "cloudflare-pages-static",
120
+ "database": "none",
121
+ },
122
+ "booking": {"mode": "cal-public", "sourceOfTruth": "cal.com", "publicUrl": public_url, "embed": embed},
123
+ "steps": [
124
+ {"id": step_id, "status": "planned", "idempotencyKey": f"starter:{fingerprint}"}
125
+ for step_id in STARTER_STEP_IDS
126
+ ],
127
+ "security": {
128
+ "secretsInArtifact": False,
129
+ "calCredentialsStored": False,
130
+ "databaseRequired": False,
131
+ },
132
+ "nextActions": [
133
+ "Confirm the merchant owns the supplied Cal public booking URL",
134
+ "Create or reconcile the GitHub repository and static site artifact",
135
+ "Deploy the generated site to Cloudflare Pages and read back the hostname",
136
+ ],
137
+ }
138
+
139
+
140
+ def validate_starter_plan(plan: dict) -> list[str]:
141
+ errors = []
142
+ if not isinstance(plan, dict):
143
+ return ["plan must be an object"]
144
+ if plan.get("schemaVersion") != STARTER_PLAN_SCHEMA_VERSION:
145
+ errors.append("unsupported Starter plan schema")
146
+ if plan.get("plan") != "starter" or plan.get("mode") != "dry-run":
147
+ errors.append("Starter plan must be a dry-run")
148
+ site = plan.get("site") if isinstance(plan.get("site"), dict) else {}
149
+ if site.get("runtime") != "cloudflare-pages-static" or site.get("database") != "none":
150
+ errors.append("Starter site must be static Cloudflare Pages with no database")
151
+ booking = plan.get("booking") if isinstance(plan.get("booking"), dict) else {}
152
+ if booking.get("mode") != "cal-public" or booking.get("sourceOfTruth") != "cal.com":
153
+ errors.append("Starter booking must use a public Cal URL")
154
+ try:
155
+ _starter_public_url(booking.get("publicUrl", ""))
156
+ except ValueError as error:
157
+ errors.append(str(error))
158
+ security = plan.get("security") if isinstance(plan.get("security"), dict) else {}
159
+ for field in ("secretsInArtifact", "calCredentialsStored", "databaseRequired"):
160
+ if security.get(field) is not False:
161
+ errors.append(f"security.{field} must be false")
162
+ steps = plan.get("steps")
163
+ if not isinstance(steps, list) or [item.get("id") for item in steps if isinstance(item, dict)] != list(STARTER_STEP_IDS):
164
+ errors.append("Starter steps must be ordered and complete")
165
+ else:
166
+ for item in steps:
167
+ if item.get("status") != "planned" or not re.fullmatch(r"starter:[a-f0-9]{64}", str(item.get("idempotencyKey", ""))):
168
+ errors.append(f"invalid Starter step: {item.get('id')}")
169
+ return errors
170
+
171
+
172
+ def starter_plan(args) -> int:
173
+ plan = build_starter_plan(
174
+ args.workspace_id,
175
+ args.merchant_id,
176
+ args.site_slug,
177
+ args.cal_url,
178
+ args.embed,
179
+ args.branch,
180
+ )
181
+ errors = validate_starter_plan(plan)
182
+ if errors:
183
+ raise ValueError("invalid generated Starter plan: " + "; ".join(errors))
184
+ rendered = json.dumps(plan, indent=2, ensure_ascii=False) + "\n"
185
+ if args.output:
186
+ output = args.output.resolve()
187
+ output.parent.mkdir(parents=True, exist_ok=True)
188
+ output.write_text(rendered, encoding="utf-8")
189
+ print(rendered, end="")
190
+ return 0
191
+
192
+
60
193
  def repo_root() -> Path:
61
194
  return Path(__file__).resolve().parents[2]
62
195
 
@@ -2140,6 +2273,16 @@ def main() -> int:
2140
2273
  add.add_argument("--sample", type=Path)
2141
2274
  add.add_argument("--force", action="store_true")
2142
2275
  add.set_defaults(func=lambda args: run_marketplace_job(argparse.Namespace(**{**vars(args), "template_url": args.template_url, "preview_url": args.preview_url})))
2276
+ plan = sub.add_parser("plan", help="build a secret-free Starter provisioning plan")
2277
+ plan.add_argument("--project", type=Path, default=Path.cwd(), help="host project path; planning is read-only")
2278
+ plan.add_argument("--workspace-id", required=True)
2279
+ plan.add_argument("--merchant-id", required=True)
2280
+ plan.add_argument("--site-slug", required=True)
2281
+ plan.add_argument("--cal-url", required=True)
2282
+ plan.add_argument("--embed", choices=("link", "iframe"), default="link")
2283
+ plan.add_argument("--branch", default="main")
2284
+ plan.add_argument("--output", type=Path)
2285
+ plan.set_defaults(func=starter_plan)
2143
2286
  val = sub.add_parser("validate")
2144
2287
  val.add_argument("template", type=Path)
2145
2288
  val.add_argument(
@@ -104,6 +104,57 @@ def validate_video_job(args: argparse.Namespace) -> int:
104
104
  return emit({"schemaVersion": "maggie-video-job.v1", "passed": not errors, "jobId": job.get("jobId"), "state": state, "errors": errors, "rawProviderPayloadIncluded": False}, args.output)
105
105
 
106
106
 
107
+ def validate_video_provider_evidence(args: argparse.Namespace) -> int:
108
+ evidence = load(Path(args.evidence).expanduser().resolve())
109
+ errors: list[str] = []
110
+ allowed = {
111
+ "schemaVersion", "environment", "provider", "model", "status", "hostVerified",
112
+ "acknowledgement", "idempotency", "moderation", "storage", "retry",
113
+ "rawProviderPayloadIncluded", "inputAssetDataIncluded", "sourceRevision", "generatedAt",
114
+ }
115
+ for key in evidence:
116
+ if key not in allowed:
117
+ errors.append(f"evidence.{key} is not allowed")
118
+ nested_allowed = {
119
+ "acknowledgement": {"accepted", "providerReferencePresent"},
120
+ "idempotency": {"replayAccepted", "duplicateChargePrevented", "assetDeduplicated"},
121
+ "moderation": {"preflightPassed", "postGenerationReviewPassed"},
122
+ "storage": {"uploadAccepted", "metadataPersisted", "readbackPassed", "signedOrApprovedUrlPresent"},
123
+ "retry": {"transientFailureObserved", "bounded", "attempts"},
124
+ "sourceRevision": {"commit", "branch", "dirty"},
125
+ }
126
+ for parent, keys in nested_allowed.items():
127
+ value = evidence.get(parent)
128
+ if isinstance(value, dict):
129
+ for key in value:
130
+ if key not in keys:
131
+ errors.append(f"{parent}.{key} is not allowed")
132
+ if evidence.get("schemaVersion") != "maggie-video-provider-evidence.v1": errors.append("schemaVersion must be maggie-video-provider-evidence.v1")
133
+ if evidence.get("environment") not in {"test", "staging", "production"}: errors.append("environment is unsupported")
134
+ if evidence.get("provider") != "google-gemini": errors.append("provider must be google-gemini")
135
+ if not str(evidence.get("model", "")).startswith(("gemini-", "veo-")): errors.append("model must be a Gemini or Veo model id")
136
+ if evidence.get("status") != "passed": errors.append("status must be passed")
137
+ if args.require_host and evidence.get("hostVerified") is not True: errors.append("hostVerified must be true")
138
+ acknowledgement = evidence.get("acknowledgement") if isinstance(evidence.get("acknowledgement"), dict) else {}
139
+ if acknowledgement.get("accepted") is not True or acknowledgement.get("providerReferencePresent") is not True: errors.append("provider acknowledgement is incomplete")
140
+ idempotency = evidence.get("idempotency") if isinstance(evidence.get("idempotency"), dict) else {}
141
+ if any(idempotency.get(key) is not True for key in ("replayAccepted", "duplicateChargePrevented", "assetDeduplicated")): errors.append("video idempotency evidence is incomplete")
142
+ moderation = evidence.get("moderation") if isinstance(evidence.get("moderation"), dict) else {}
143
+ if moderation.get("preflightPassed") is not True or moderation.get("postGenerationReviewPassed") is not True: errors.append("moderation evidence is incomplete")
144
+ storage = evidence.get("storage") if isinstance(evidence.get("storage"), dict) else {}
145
+ if any(storage.get(key) is not True for key in ("uploadAccepted", "metadataPersisted", "readbackPassed", "signedOrApprovedUrlPresent")): errors.append("storage readback evidence is incomplete")
146
+ retry = evidence.get("retry") if isinstance(evidence.get("retry"), dict) else {}
147
+ attempts = retry.get("attempts")
148
+ if retry.get("transientFailureObserved") is not True or retry.get("bounded") is not True or not isinstance(attempts, int) or attempts < 1 or attempts > 3: errors.append("retry evidence is incomplete")
149
+ if evidence.get("rawProviderPayloadIncluded") is not False: errors.append("raw provider payloads must be excluded")
150
+ if evidence.get("inputAssetDataIncluded") is not False: errors.append("input asset data must be excluded")
151
+ source = evidence.get("sourceRevision") if isinstance(evidence.get("sourceRevision"), dict) else {}
152
+ if not isinstance(source.get("commit"), str) or not isinstance(source.get("branch"), str) or not isinstance(source.get("dirty"), bool): errors.append("sourceRevision is incomplete")
153
+ if args.environment and evidence.get("environment") != args.environment: errors.append("environment does not match workflow input")
154
+ if args.model and evidence.get("model") != args.model: errors.append("model does not match workflow input")
155
+ return emit({"schemaVersion": "maggie-video-provider-evidence.v1", "passed": not errors, "environment": evidence.get("environment"), "provider": evidence.get("provider"), "model": evidence.get("model"), "errors": errors, "rawProviderPayloadIncluded": False, "redacted": True}, args.output)
156
+
157
+
107
158
  def git_value(project: Path, *args: str) -> str:
108
159
  result = subprocess.run(["git", "-C", str(project), *args], capture_output=True, text=True, check=False)
109
160
  if result.returncode:
@@ -369,6 +420,7 @@ def main() -> int:
369
420
  for kind, relative in (("image", "maggie-media/image-generation-policy-v1.json"), ("video", "maggie-media/video-generation-policy-v1.json")):
370
421
  command = media_sub.add_parser(f"{kind}-policy"); command.add_argument("--policy", default=str(contract_root() / relative)); command.add_argument("--output"); command.set_defaults(func=validate_media, kind=kind)
371
422
  job = media_sub.add_parser("video-job"); job.add_argument("--job", required=True); job.add_argument("--output"); job.set_defaults(func=validate_video_job)
423
+ video_provider = media_sub.add_parser("video-provider-evidence"); video_provider.add_argument("--evidence", required=True); video_provider.add_argument("--environment", choices=["test", "staging", "production"]); video_provider.add_argument("--model"); video_provider.add_argument("--require-host", action="store_true"); video_provider.add_argument("--output"); video_provider.set_defaults(func=validate_video_provider_evidence)
372
424
  provenance = sub.add_parser("provenance"); provenance.add_argument("--project", default="."); provenance.add_argument("--source"); provenance.add_argument("--boundary", choices=["git-source", "host-runtime", "generated-artifact"], default="git-source"); provenance.add_argument("--output"); provenance.set_defaults(func=capture_provenance)
373
425
  scaffold_parser = sub.add_parser("scaffold"); scaffold_parser.add_argument("--project", default="."); scaffold_parser.add_argument("--framework", choices=sorted(FRAMEWORK_PACKAGES), required=True); scaffold_parser.add_argument("--language", choices=["typescript", "javascript"], default="typescript"); scaffold_parser.add_argument("--package-manager", choices=["npm", "pnpm", "yarn", "bun"], default="npm"); scaffold_parser.add_argument("--install-dependencies", action="store_true"); scaffold_parser.add_argument("--write", action="store_true"); scaffold_parser.add_argument("--confirm", action="store_true"); scaffold_parser.add_argument("--force", action="store_true"); scaffold_parser.add_argument("--output"); scaffold_parser.set_defaults(func=scaffold)
374
426
  brand = sub.add_parser("brand-kit"); brand.add_argument("--manifest"); brand.add_argument("--project", default="."); brand.add_argument("--design"); brand.add_argument("--tokens"); brand.add_argument("--logo"); brand.add_argument("--font"); brand.add_argument("--icon-library", default="host-icon-library"); brand.add_argument("--assets"); brand.add_argument("--write", action="store_true"); brand.add_argument("--confirm", action="store_true"); brand.add_argument("--output"); brand.set_defaults(func=lambda args: validate_brand(args) if args.manifest else brand_kit(args))
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@topy-ai/maggie",
3
- "version": "0.7.41",
3
+ "version": "0.7.43",
4
4
  "description": "Install and manage Maggie Skills for AI coding agents",
5
5
  "license": "MIT",
6
6
  "type": "module",