@topy-ai/maggie 0.7.26 → 0.7.28

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 (33) hide show
  1. package/README.md +50 -4
  2. package/README.zh-TW.md +4 -2
  3. package/bin/maggie.js +17 -5
  4. package/bundled-contracts/maggie-deployment/runtime-parity-v1.schema.json +20 -2
  5. package/bundled-contracts/maggie-seo/model-policy-v1.schema.json +17 -0
  6. package/bundled-contracts/maggie-service-booking/resolver-v1.schema.json +17 -0
  7. package/bundled-contracts/maggie-service-booking/worker-health-v1.schema.json +17 -0
  8. package/bundled-contracts/maggiedash/README.md +5 -0
  9. package/bundled-contracts/maggiedash/migration-preflight-v1.schema.json +16 -0
  10. package/bundled-references/blog-translation-ingestion.md +7 -0
  11. package/bundled-references/maggiedash-integration.md +19 -0
  12. package/bundled-skills/maggie-blog-bootstrap/SKILL.md +21 -0
  13. package/bundled-skills/maggie-dash/SKILL.md +34 -0
  14. package/bundled-skills/maggie-deployment/SKILL.md +22 -7
  15. package/bundled-skills/maggie-deployment/references/vps.md +12 -2
  16. package/bundled-skills/maggie-feedback/SKILL.md +13 -3
  17. package/bundled-skills/maggie-marketplace/SKILL.md +20 -1
  18. package/bundled-skills/maggie-seo-geo/SKILL.md +11 -0
  19. package/bundled-skills/maggie-service-booking/SKILL.md +19 -0
  20. package/bundled-tools/clis/maggie.py +29 -1
  21. package/bundled-tools/clis/maggie_dash.py +108 -0
  22. package/bundled-tools/clis/maggie_deployment.py +43 -4
  23. package/bundled-tools/clis/maggie_deployment_parity.py +42 -0
  24. package/bundled-tools/clis/maggie_feedback.py +108 -0
  25. package/bundled-tools/clis/maggie_localization.py +18 -1
  26. package/bundled-tools/clis/maggie_marketplace.py +58 -4
  27. package/bundled-tools/clis/maggie_migration.py +43 -0
  28. package/bundled-tools/clis/maggie_service_booking.py +84 -1
  29. package/bundled-tools/integrations/maggie-api-pull.md +8 -4
  30. package/bundled-tools/runtime/maggie_dash_store.py +20 -0
  31. package/package.json +1 -1
  32. package/references/blog-translation-ingestion.md +7 -0
  33. package/references/maggiedash-integration.md +19 -0
@@ -25,7 +25,7 @@ DEFAULT_SAMPLE_ID = "medical-spa-landing-001"
25
25
  PUBLISHED_STATUSES = {"published", "ready"}
26
26
  PHOSPHOR_BRAND_ICONS = {"ph-instagram-logo", "ph-facebook-logo", "ph-x-logo", "ph-linkedin-logo", "ph-youtube-logo", "ph-tiktok-logo"}
27
27
  PHOSPHOR_CDN = "https://cdn.jsdelivr.net/npm/@phosphor-icons/web@2.1.1/src/regular/style.css"
28
- JOB_PHASES = ("CREATED", "IMPORTED", "WAITING_FOR_ASSETS", "COMPLETED", "VALIDATED", "READY", "FAILED")
28
+ JOB_PHASES = ("CREATED", "IMPORTED", "WAITING_FOR_ASSETS", "COMPLETED", "VALIDATED", "READY", "STALE", "FAILED")
29
29
 
30
30
 
31
31
  class MetadataParser(HTMLParser):
@@ -1738,8 +1738,16 @@ def run_marketplace_job(args: argparse.Namespace) -> int:
1738
1738
  if existing_path.exists() and not args.force:
1739
1739
  existing = load_job(root, args.id)
1740
1740
  if existing.get("phase") == "READY":
1741
- print(f"marketplace job already ready: {args.id}")
1742
- return 0
1741
+ destination = template_path(root, args.id)
1742
+ # READY is a claim, not a permanent exemption from newer
1743
+ # validators. Re-check it on every reuse and make a stale package
1744
+ # explicitly resumable when the contract has moved on.
1745
+ if validate_template(destination, args.sample.resolve() if args.sample else None) == 0:
1746
+ print(f"marketplace job already ready: {args.id}")
1747
+ return 0
1748
+ set_job_phase(root, existing, "STALE", staleReason="current validator rejected the recorded READY package")
1749
+ print(f"STALE: marketplace job {args.id} must be resumed or rebuilt")
1750
+ return 1
1743
1751
  args = args_from_job(existing)
1744
1752
  job = load_job(root, args.id) if existing_path.exists() else {
1745
1753
  "id": args.id,
@@ -1823,7 +1831,7 @@ def resume_marketplace_job(args: argparse.Namespace) -> int:
1823
1831
  print(f"marketplace job already ready: {args.job_id}")
1824
1832
  return 0
1825
1833
  resumed = args_from_job(job)
1826
- if job.get("phase") == "FAILED":
1834
+ if job.get("phase") in {"FAILED", "STALE"}:
1827
1835
  destination = template_path(root, args.job_id)
1828
1836
  # A post-import failure must resume from the completion checkpoint;
1829
1837
  # importing again would erase generated assets and task evidence.
@@ -2053,6 +2061,47 @@ def apply_template(args: argparse.Namespace) -> int:
2053
2061
  return 0
2054
2062
 
2055
2063
 
2064
+ def enrichment_report(args: argparse.Namespace) -> int:
2065
+ """Normalize privacy-safe enrichment evidence for a marketplace candidate."""
2066
+ try:
2067
+ payload = read_json(args.input.resolve())
2068
+ except (OSError, ValueError, json.JSONDecodeError) as error:
2069
+ print(json.dumps({"status": "failed", "errors": [f"cannot read enrichment evidence: {error}"]})); return 1
2070
+ errors = []
2071
+ if payload.get("schemaVersion") != "maggie-marketplace-enrichment-input.v1": errors.append("schemaVersion is unsupported")
2072
+ candidate = payload.get("candidate") if isinstance(payload.get("candidate"), dict) else {}
2073
+ candidate_id = str(candidate.get("candidateId") or "")
2074
+ if not ID_PATTERN.fullmatch(candidate_id): errors.append("candidate.candidateId must be marketplace-safe")
2075
+ crawl = payload.get("crawl") if isinstance(payload.get("crawl"), dict) else {}
2076
+ audit = payload.get("audit") if isinstance(payload.get("audit"), dict) else {}
2077
+ snapshot = payload.get("snapshot") if isinstance(payload.get("snapshot"), dict) else {}
2078
+ for field, item in (("crawl", crawl), ("audit", audit), ("snapshot", snapshot)):
2079
+ if not item: errors.append(f"{field} evidence is required")
2080
+ if item.get("status") not in {"passed", "failed", "not-found", "blocked", "unavailable"}: errors.append(f"{field}.status is unsupported")
2081
+ if item.get("error") and not re.fullmatch(r"[a-z0-9][a-z0-9._:-]{0,119}", str(item["error"])): errors.append(f"{field}.error must be a safe error code")
2082
+ fields = payload.get("rawAuditFields", {})
2083
+ if not isinstance(fields, dict): errors.append("rawAuditFields must be an object")
2084
+ safe_fields = {}
2085
+ for key, value in (fields.items() if isinstance(fields, dict) else []):
2086
+ if re.fullmatch(r"[A-Za-z][A-Za-z0-9_.:-]{0,79}", str(key)) and not re.search(r"path|secret|token|password|cookie|header|body|html", str(key), re.I) and isinstance(value, (str, int, float, bool)) and not (isinstance(value, str) and re.search(r"(?:^|/)(?:home|Users|tmp|var|private)/", value)):
2087
+ safe_fields[str(key)] = str(value)[:240] if isinstance(value, str) else value
2088
+ backfill = payload.get("seoBackfill") if isinstance(payload.get("seoBackfill"), dict) else {}
2089
+ report = {
2090
+ "schemaVersion": "maggie-marketplace-enrichment.v1", "candidateId": candidate_id,
2091
+ "status": "passed" if not errors else "failed", "passed": not errors, "errors": errors,
2092
+ "candidate": {"source": str(candidate.get("source") or "")[:120], "market": str(candidate.get("market") or "global")[:20], "locale": str(candidate.get("locale") or "en")[:20]},
2093
+ "crawl": {"status": crawl.get("status"), "pages": crawl.get("pages", 0) if isinstance(crawl.get("pages", 0), int) else 0, "error": crawl.get("error")},
2094
+ "audit": {"status": audit.get("status"), "fields": safe_fields, "error": audit.get("error")},
2095
+ "snapshot": {"status": snapshot.get("status"), "revision": str(snapshot.get("revision") or "")[:120], "error": snapshot.get("error")},
2096
+ "seoBackfill": {"status": backfill.get("status"), "updated": backfill.get("updated", 0) if isinstance(backfill.get("updated", 0), int) else 0, "error": backfill.get("error")},
2097
+ "retry": {"allowed": bool(errors), "reason": ";".join(errors)[:240] if errors else None},
2098
+ "privacy": {"rawHtmlIncluded": False, "responseBodiesIncluded": False, "localPathsIncluded": False, "secretsRedacted": True},
2099
+ }
2100
+ output = args.output.resolve() if args.output else args.project.resolve() / "docs" / "marketplace-enrichment.json"
2101
+ output.parent.mkdir(parents=True, exist_ok=True); output.write_text(json.dumps(report, indent=2, ensure_ascii=False) + "\n", encoding="utf-8")
2102
+ print(json.dumps({"status": report["status"], "candidateId": candidate_id, "report": str(output), "retry": report["retry"]}, ensure_ascii=False)); return 0 if not errors else 1
2103
+
2104
+
2056
2105
  def main() -> int:
2057
2106
  parser = argparse.ArgumentParser(description=__doc__)
2058
2107
  parser.add_argument("--marketplace", type=Path, default=Path("marketplace"))
@@ -2122,6 +2171,11 @@ def main() -> int:
2122
2171
  apply.add_argument("--project", type=Path, default=Path.cwd())
2123
2172
  apply.add_argument("--allow-draft", action="store_true")
2124
2173
  apply.set_defaults(func=apply_template)
2174
+ enrichment = sub.add_parser("enrichment-report", help="normalize safe candidate crawl and audit evidence")
2175
+ enrichment.add_argument("--project", type=Path, default=Path.cwd())
2176
+ enrichment.add_argument("--input", type=Path, required=True)
2177
+ enrichment.add_argument("--output", type=Path)
2178
+ enrichment.set_defaults(func=enrichment_report)
2125
2179
  args = parser.parse_args()
2126
2180
  try:
2127
2181
  return int(args.func(args) or 0)
@@ -56,6 +56,8 @@ def validate_project(project: Path, environment: str) -> dict:
56
56
 
57
57
 
58
58
  IDENTITY_SCHEMA = "maggie-database-identity.v1"
59
+ PREFLIGHT_SCHEMA = "maggie-migration-preflight.v1"
60
+ SAFE_NAME = re.compile(r"^[A-Za-z][A-Za-z0-9_.:-]{0,119}$")
59
61
 
60
62
 
61
63
  def _identity_fingerprint(identity: dict) -> str:
@@ -119,7 +121,48 @@ def identity_main(argv: list[str]) -> int:
119
121
  return 0 if result["passed"] else 1
120
122
 
121
123
 
124
+ def preflight_main(argv: list[str]) -> int:
125
+ parser = argparse.ArgumentParser(description="Validate secret-free migration privilege, column, and constraint evidence.")
126
+ parser.add_argument("--evidence", required=True)
127
+ parser.add_argument("--output")
128
+ args = parser.parse_args(argv)
129
+ errors: list[str] = []
130
+ try:
131
+ payload = json.loads(Path(args.evidence).read_text(encoding="utf-8"))
132
+ except (OSError, json.JSONDecodeError) as error:
133
+ payload = {}; errors.append(f"cannot read migration preflight evidence: {error}")
134
+ if not isinstance(payload, dict): payload = {}; errors.append("preflight evidence must be an object")
135
+ if payload.get("schemaVersion") != PREFLIGHT_SCHEMA: errors.append(f"schemaVersion must be {PREFLIGHT_SCHEMA}")
136
+ if payload.get("environment") not in {"development", "staging", "production"}: errors.append("environment is invalid")
137
+ privileges = payload.get("privileges") if isinstance(payload.get("privileges"), dict) else {}
138
+ for field in ("canCreateTable", "canAlterTable", "canCreateIndex", "passed"):
139
+ if privileges.get(field) is not True: errors.append(f"privileges.{field} must be true")
140
+ columns = payload.get("requiredColumns")
141
+ if not isinstance(columns, list) or not columns: errors.append("requiredColumns must not be empty")
142
+ for index, item in enumerate(columns if isinstance(columns, list) else []):
143
+ if not isinstance(item, dict) or not SAFE_NAME.fullmatch(str(item.get("table") or "")) or not SAFE_NAME.fullmatch(str(item.get("column") or "")) or item.get("present") is not True:
144
+ errors.append(f"requiredColumns[{index}] must identify a present table column")
145
+ constraints = payload.get("uniqueConstraints")
146
+ if not isinstance(constraints, list) or not constraints: errors.append("uniqueConstraints must not be empty")
147
+ for index, item in enumerate(constraints if isinstance(constraints, list) else []):
148
+ if not isinstance(item, dict) or not SAFE_NAME.fullmatch(str(item.get("table") or "")) or not isinstance(item.get("columns"), list) or not item["columns"] or any(not SAFE_NAME.fullmatch(str(value)) for value in item["columns"]) or item.get("present") is not True:
149
+ errors.append(f"uniqueConstraints[{index}] must identify a present unique constraint")
150
+ idempotency = payload.get("idempotency") if isinstance(payload.get("idempotency"), dict) else {}
151
+ if idempotency.get("safe") is not True or idempotency.get("onConflictHandled") is not True: errors.append("idempotency must be safe and handle conflicts")
152
+ migration = payload.get("migration") if isinstance(payload.get("migration"), dict) else {}
153
+ for field in ("currentVersion", "targetVersion"):
154
+ if not SAFE_NAME.fullmatch(str(migration.get(field) or "")): errors.append(f"migration.{field} must be a safe identifier")
155
+ if migration.get("currentVersion") == migration.get("targetVersion"): errors.append("migration targetVersion must advance")
156
+ if migration.get("passed") is not True: errors.append("migration evidence did not pass")
157
+ result = {"schemaVersion": PREFLIGHT_SCHEMA, "environment": payload.get("environment"), "status": "passed" if not errors else "failed", "passed": not errors, "errors": errors, "checks": {"privileges": True, "requiredColumns": True, "uniqueConstraints": True, "idempotency": True, "migration": True}, "mutation": "not executed"}
158
+ if args.output:
159
+ output = Path(args.output); output.parent.mkdir(parents=True, exist_ok=True); output.write_text(json.dumps(result, indent=2) + "\n", encoding="utf-8")
160
+ print(json.dumps(result, indent=2)); return 0 if not errors else 1
161
+
162
+
122
163
  def main() -> int:
164
+ if sys.argv[1:2] == ["preflight"]:
165
+ return preflight_main(sys.argv[2:])
123
166
  if sys.argv[1:2] == ["identity"]:
124
167
  return identity_main(sys.argv[2:])
125
168
  parser = argparse.ArgumentParser(description=__doc__)
@@ -7,7 +7,7 @@ import xml.etree.ElementTree as ET
7
7
  from datetime import datetime, timezone
8
8
  from html.parser import HTMLParser
9
9
  from pathlib import Path
10
- from urllib.parse import quote, urljoin, urlsplit
10
+ from urllib.parse import quote, urljoin, urlsplit, urlparse
11
11
  from urllib.request import Request, urlopen
12
12
 
13
13
  sys.path.insert(0, str(Path(__file__).resolve().parents[1] / "runtime"))
@@ -1148,6 +1148,85 @@ def cmd_variant_render_audit(args):
1148
1148
  result={"schemaVersion":"1.0","generatedAt":NOW(),"environment":args.environment,"status":"passed" if not errors else "failed","variants":results,"errors":errors,"passed":not errors}
1149
1149
  out=project/"docs"/"service-variant-render-audit.json"; out.parent.mkdir(parents=True,exist_ok=True); out.write_text(json.dumps(result,indent=2)+"\n",encoding="utf-8")
1150
1150
  print(json.dumps({**result,"report":str(out)},indent=2)); return 0 if not errors else 1
1151
+ SAFE_WORKER_ID = re.compile(r"^[A-Za-z0-9][A-Za-z0-9._:-]{0,119}$")
1152
+ SAFE_EVIDENCE_ID = re.compile(r"^[A-Za-z0-9][A-Za-z0-9._:/=-]{0,159}$")
1153
+ RESOLVER_OUTCOMES = {"resolved", "resolved-no-menu", "not-found", "blocked", "timeout", "unavailable"}
1154
+
1155
+
1156
+ def cmd_worker_health(args):
1157
+ """Validate bounded queue, lease, retry, timeout, and cancellation evidence."""
1158
+ try:
1159
+ payload = json.loads(Path(args.evidence).resolve().read_text(encoding="utf-8"))
1160
+ except (OSError, ValueError, json.JSONDecodeError) as exc:
1161
+ print(json.dumps({"status": "failed", "errors": [f"cannot read worker evidence: {exc}"]}, indent=2))
1162
+ return 1
1163
+ errors = []
1164
+ if payload.get("schemaVersion") != "maggie-booking-worker-health-input.v1": errors.append("schemaVersion is unsupported")
1165
+ if payload.get("environment") not in {"development", "staging", "production"}: errors.append("environment is invalid")
1166
+ worker_id = str(payload.get("workerId") or "")
1167
+ if not SAFE_WORKER_ID.fullmatch(worker_id): errors.append("workerId must be a safe identifier")
1168
+ queue = payload.get("queue") if isinstance(payload.get("queue"), dict) else {}
1169
+ for field in ("maxDepth", "maxConcurrency"):
1170
+ if not isinstance(queue.get(field), int) or isinstance(queue.get(field), bool) or queue[field] < 1: errors.append(f"queue.{field} must be a positive integer")
1171
+ if queue.get("bounded") is not True: errors.append("queue must be bounded")
1172
+ leases = payload.get("leases") if isinstance(payload.get("leases"), dict) else {}
1173
+ if leases.get("enabled") is not True or not isinstance(leases.get("ttlSeconds"), int) or leases.get("ttlSeconds", 0) < 1: errors.append("leases must have a positive TTL")
1174
+ if leases.get("staleReclaimed") is not True: errors.append("stale leases must be reclaimed")
1175
+ timeouts = payload.get("timeouts") if isinstance(payload.get("timeouts"), dict) else {}
1176
+ for field in ("jobSeconds", "providerSeconds"):
1177
+ if not isinstance(timeouts.get(field), int) or isinstance(timeouts.get(field), bool) or timeouts[field] < 1: errors.append(f"timeouts.{field} must be positive")
1178
+ retry = payload.get("retry") if isinstance(payload.get("retry"), dict) else {}
1179
+ if not isinstance(retry.get("maxAttempts"), int) or isinstance(retry.get("maxAttempts"), bool) or retry["maxAttempts"] < 0: errors.append("retry.maxAttempts must be non-negative")
1180
+ if retry.get("backoff") not in {"fixed", "exponential", "exponential-jitter"}: errors.append("retry.backoff must be an approved strategy")
1181
+ cancellation = payload.get("cancellation") if isinstance(payload.get("cancellation"), dict) else {}
1182
+ if cancellation.get("supported") is not True or cancellation.get("cleanupVerified") is not True: errors.append("cancellation cleanup is not verified")
1183
+ health = payload.get("health") if isinstance(payload.get("health"), dict) else {}
1184
+ if not isinstance(health.get("path"), str) or not health["path"].startswith("/") or health.get("passed") is not True: errors.append("health endpoint did not pass")
1185
+ if payload.get("orphanedJobs", 0) != 0: errors.append("orphanedJobs must be zero")
1186
+ report = {"schemaVersion": "maggie-booking-worker-health.v1", "environment": payload.get("environment"), "workerId": worker_id, "status": "passed" if not errors else "failed", "passed": not errors, "errors": errors, "checks": {"boundedQueue": True, "leases": True, "timeouts": True, "retryBackoff": True, "cancellationCleanup": True, "healthEndpoint": True}}
1187
+ project = root(args)
1188
+ output = Path(args.output) if args.output else project / "docs/booking-worker-health.json"
1189
+ if not output.is_absolute(): output = project / output
1190
+ output = output.resolve()
1191
+ output.parent.mkdir(parents=True, exist_ok=True); output.write_text(json.dumps(report, indent=2) + "\n", encoding="utf-8")
1192
+ print(json.dumps({"status": report["status"], "report": str(output), "errors": errors}, indent=2)); return 0 if not errors else 1
1193
+
1194
+
1195
+ def cmd_resolver_audit(args):
1196
+ """Classify booking URL outcomes using safe canonical URL evidence."""
1197
+ try:
1198
+ payload = json.loads(Path(args.evidence).resolve().read_text(encoding="utf-8"))
1199
+ except (OSError, ValueError, json.JSONDecodeError) as exc:
1200
+ print(json.dumps({"status": "failed", "errors": [f"cannot read resolver evidence: {exc}"]}, indent=2)); return 1
1201
+ errors = []
1202
+ if payload.get("schemaVersion") != "maggie-booking-resolver-input.v1": errors.append("schemaVersion is unsupported")
1203
+ observations = payload.get("observations")
1204
+ if not isinstance(observations, list) or not observations or len(observations) > 200: errors.append("observations must contain 1–200 items")
1205
+ safe = []
1206
+ for index, item in enumerate(observations if isinstance(observations, list) else []):
1207
+ prefix = f"observations[{index}]"
1208
+ if not isinstance(item, dict): errors.append(f"{prefix} must be an object"); continue
1209
+ service_id = str(item.get("serviceId") or ""); outcome = item.get("outcome")
1210
+ if not SAFE_WORKER_ID.fullmatch(service_id): errors.append(f"{prefix}.serviceId must be a safe identifier")
1211
+ if outcome not in RESOLVER_OUTCOMES: errors.append(f"{prefix}.outcome is unsupported")
1212
+ canonical = item.get("canonicalUrl")
1213
+ if outcome in {"resolved", "resolved-no-menu"}:
1214
+ parsed = urlparse(str(canonical or ""))
1215
+ if parsed.scheme != "https" or not parsed.netloc or parsed.username or parsed.password: errors.append(f"{prefix}.canonicalUrl must be a public HTTPS URL")
1216
+ elif canonical: errors.append(f"{prefix}.canonicalUrl is only allowed for resolved outcomes")
1217
+ evidence = item.get("evidenceIds")
1218
+ if not isinstance(evidence, list) or not evidence or any(not SAFE_EVIDENCE_ID.fullmatch(str(value)) for value in evidence[:20]): errors.append(f"{prefix}.evidenceIds must contain safe identifiers")
1219
+ if not isinstance(item.get("durationMs"), int) or isinstance(item.get("durationMs"), bool) or item["durationMs"] < 0: errors.append(f"{prefix}.durationMs must be non-negative")
1220
+ safe.append({"serviceId": service_id, "outcome": outcome, "canonicalUrl": canonical if outcome in {"resolved", "resolved-no-menu"} else None, "evidenceIds": [str(value) for value in evidence[:20]] if isinstance(evidence, list) else [], "durationMs": item.get("durationMs")})
1221
+ counts = {outcome: sum(item.get("outcome") == outcome for item in safe) for outcome in sorted(RESOLVER_OUTCOMES)}
1222
+ report = {"schemaVersion": "maggie-booking-resolver.v1", "status": "passed" if not errors else "failed", "passed": not errors, "errors": errors, "counts": counts, "observations": safe, "privacy": {"rawResponsesIncluded": False, "credentialsIncluded": False}}
1223
+ project = root(args)
1224
+ output = Path(args.output) if args.output else project / "docs/booking-resolver-audit.json"
1225
+ if not output.is_absolute(): output = project / output
1226
+ output = output.resolve(); output.parent.mkdir(parents=True, exist_ok=True); output.write_text(json.dumps(report, indent=2) + "\n", encoding="utf-8")
1227
+ print(json.dumps({"status": report["status"], "report": str(output), "counts": counts, "errors": errors}, indent=2)); return 0 if not errors else 1
1228
+
1229
+
1151
1230
  def main():
1152
1231
  p=argparse.ArgumentParser(); sub=p.add_subparsers(dest="command",required=True)
1153
1232
  for name in ("import","sync","run"):
@@ -1173,6 +1252,8 @@ def main():
1173
1252
  q=sub.add_parser("variant-audit"); q.add_argument("--project",default="."); q.add_argument("--variants",required=True,help="draft service-page variant JSON"); q.add_argument("--as-of",help="ISO timestamp for expiry checks"); q.add_argument("--environment",choices=("development","staging","production"),default="staging")
1174
1253
  q=sub.add_parser("category-visual-audit"); q.add_argument("--project",default="."); q.add_argument("--screenshot-dir",required=True); q.add_argument("--min-bytes",type=int,default=5000); q.add_argument("--environment",choices=("development","staging","production"),default="staging")
1175
1254
  q=sub.add_parser("variant-render-audit"); q.add_argument("--project",default="."); q.add_argument("--variants",required=True); q.add_argument("--rendered-dir",required=True); q.add_argument("--environment",choices=("development","staging","production"),default="staging")
1255
+ q=sub.add_parser("worker-health"); q.add_argument("--project",default="."); q.add_argument("--evidence",required=True); q.add_argument("--output")
1256
+ q=sub.add_parser("resolver-audit"); q.add_argument("--project",default="."); q.add_argument("--evidence",required=True); q.add_argument("--output")
1176
1257
  for name in ("validate","inspect","status"):
1177
1258
  q=sub.add_parser(name); q.add_argument("--project",default=".")
1178
1259
  a=p.parse_args(); project=root(a)
@@ -1198,6 +1279,8 @@ def main():
1198
1279
  if a.command=="variant-audit": return cmd_variant_audit(a)
1199
1280
  if a.command=="category-visual-audit": return cmd_category_visual_audit(a)
1200
1281
  if a.command=="variant-render-audit": return cmd_variant_render_audit(a)
1282
+ if a.command=="worker-health": return cmd_worker_health(a)
1283
+ if a.command=="resolver-audit": return cmd_resolver_audit(a)
1201
1284
  if a.command=="inspect": return cmd_inspect(a)
1202
1285
  if a.command=="status": return cmd_status(a)
1203
1286
  data=load(project) if path(project).exists() else {}; errors=validate(data); print(json.dumps({"status":"passed" if not errors else "failed","errors":errors,"services":len(data.get("services",[]))},indent=2)); return 0 if not errors else 1
@@ -23,8 +23,11 @@ feedback payloads, or public repositories.
23
23
 
24
24
  `/history` rejects invalid pagination with `422`; do not request more than 50
25
25
  records per page. Empty `/posts` or `/updates` responses are normal and mean
26
- there is currently no queued item. The public OpenAPI contract does not include
27
- `/whoami`; resolve configuration from the API key and project context instead.
26
+ there is currently no queued item. Quota responses must expose the effective
27
+ delivery allowance: when `quota_exhausted` is true, report the relevant daily
28
+ remaining value as zero and preserve a bounded provider `message`/error
29
+ summary. The public OpenAPI contract does not include `/whoami`; resolve
30
+ configuration from the API key and project context instead.
28
31
 
29
32
  Returned content may expose provider-specific fields such as `content`,
30
33
  `content_markdown`, `content_html`, `content_portable_text`, `body`, or `html`.
@@ -56,8 +59,9 @@ python3 tools/clis/maggie.py api lifecycle --project . \
56
59
  The first command is always dry-run. The executing command persists key-scoped
57
60
  context, remote IDs, versions, slugs, canonicals, and hashes under
58
61
  `.maggie/ai-cmo-lifecycle.json`. Raw provider responses are not copied into
59
- the checkpoint. Pulled content is projected as draft-only state; no lifecycle
60
- step publishes local content.
62
+ the checkpoint; only bounded messages, effective quota fields, and safe scalar
63
+ outcomes are retained. Pulled content is projected as draft-only state; no
64
+ lifecycle step publishes local content.
61
65
 
62
66
  To prepare and explicitly import the projection into MaggieDash drafts:
63
67
 
@@ -108,6 +108,26 @@ class MaggieDashStore:
108
108
 
109
109
  def init_project(self, project_id: str, name: str, adapter: str = "maggiedash-sqlite", locale: str = "en-GB") -> dict[str, Any]:
110
110
  timestamp = now()
111
+ # The default CLI identity is a provisional row. Once the operator
112
+ # supplies a real project id, converge that row instead of leaving an
113
+ # orphan project behind in the local store.
114
+ if project_id != "local-project":
115
+ has_target = self.connection.execute("SELECT 1 FROM maggiedash_projects WHERE id=?", (project_id,)).fetchone()
116
+ has_default = self.connection.execute("SELECT 1 FROM maggiedash_projects WHERE id=?", ("local-project",)).fetchone()
117
+ if not has_target and has_default:
118
+ # Create the destination first so SQLite's foreign-key checks
119
+ # remain enabled while project-scoped rows are moved.
120
+ self.connection.execute(
121
+ "INSERT INTO maggiedash_projects (id,name,adapter,locale,status,created_at,updated_at) "
122
+ "SELECT ?,name,adapter,locale,status,created_at,updated_at FROM maggiedash_projects WHERE id=?",
123
+ (project_id, "local-project"),
124
+ )
125
+ existing_tables = {row[0] for row in self.connection.execute("SELECT name FROM sqlite_master WHERE type='table'")}
126
+ for table in ("maggiedash_memberships", "maggiedash_documents", "maggiedash_redirects", "maggiedash_audit_events", "maggiedash_section_presets"):
127
+ if table not in existing_tables:
128
+ continue
129
+ self.connection.execute(f"UPDATE {table} SET project_id=? WHERE project_id=?", (project_id, "local-project"))
130
+ self.connection.execute("DELETE FROM maggiedash_projects WHERE id=?", ("local-project",))
111
131
  self.connection.execute(
112
132
  "INSERT INTO maggiedash_projects (id,name,adapter,locale,created_at,updated_at) VALUES (?,?,?,?,?,?) "
113
133
  "ON CONFLICT(id) DO UPDATE SET name=excluded.name, adapter=excluded.adapter, locale=excluded.locale, updated_at=excluded.updated_at",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@topy-ai/maggie",
3
- "version": "0.7.26",
3
+ "version": "0.7.28",
4
4
  "description": "Install and manage Maggie Skills for AI coding agents",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -25,6 +25,13 @@ return partial/nonzero and store only an error category. Use one worker per
25
25
  project. Host database/API schedulers must explicitly integrate this boundary;
26
26
  installing the skill does not patch an existing host ingestion implementation.
27
27
 
28
+ When a host imports JavaScript helpers from strict TypeScript, ship an adjacent
29
+ `.d.mts` declaration for every `.mjs` module that is part of the public
30
+ translation/ingest boundary. Keep the declaration's nullable/error outcome
31
+ types aligned with the runtime and include it in the typecheck fixture. This
32
+ prevents a successful runtime helper from becoming an implicit `any` or a
33
+ nullability mismatch in the scheduled path.
34
+
28
35
  ## Durable work
29
36
 
30
37
  After source persistence, enqueue translation work using an idempotency key
@@ -5,6 +5,14 @@ approvals, jobs, audit, API, and MCP boundaries. External systems are adapters;
5
5
  they never become the local source of truth without an explicit migration and
6
6
  rollback decision.
7
7
 
8
+ The first-party distribution installs the React admin under `./_maggie/admin`
9
+ and portable host starters under `maggiedash/`: an idempotent PostgreSQL
10
+ schema, type-only adapter boundary, and the screen-level
11
+ `maggiedash-host-adapter.v2` contract. The schema is a starting migration for
12
+ the host's normal migration system; it must not drop legacy tables or receive
13
+ unbound project IDs. The type file is intentionally not a fake database
14
+ implementation.
15
+
8
16
  ## Foundation
9
17
 
10
18
  Initialize and migrate the local project contract before content or provider
@@ -35,6 +43,17 @@ in environment variables and are never copied into reports.
35
43
  explicitly declares that mirror required.
36
44
  - API Pull, booking, analytics, email, and deployment providers expose health,
37
45
  normalized errors, and explicit configuration schemas.
46
+ - Dashboard hosts must expose the exact method, route, response content type,
47
+ and required fields declared by `host-adapter-v2`. Create sanitized runtime
48
+ observations and validate them with `maggie dash conformance`; a static
49
+ component scan is not evidence that a screen works. Astro hosts that exclude
50
+ underscore filesystem segments must rewrite `/_maggie/section-preview` once
51
+ to the internal preview route without disabling origin checks.
52
+
53
+ For API Pull, retain only bounded provider messages and effective quota values
54
+ in lifecycle state. A quota-exhausted response means daily delivery remaining
55
+ is zero even if an older allowance field says otherwise; raw provider bodies
56
+ never enter `.maggie` reports.
38
57
 
39
58
  ## Ordered workflow
40
59