@topy-ai/maggie 0.7.23 → 0.7.25

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.
@@ -0,0 +1,186 @@
1
+ #!/usr/bin/env python3
2
+ """Validate provider-neutral asynchronous service onboarding orchestration evidence."""
3
+
4
+ from __future__ import annotations
5
+
6
+ import argparse
7
+ import json
8
+ import re
9
+ import sys
10
+ from pathlib import Path
11
+ from typing import Any
12
+
13
+
14
+ SCHEMA = "maggie-service-onboarding-orchestration.v1"
15
+ WORKFLOWS = {"import", "search", "research"}
16
+ STATES = {"queued", "running", "succeeded", "failed", "blocked", "cancelled"}
17
+ TRANSITIONS = {
18
+ "queued": {"queued", "running", "blocked", "cancelled"},
19
+ "running": {"running", "succeeded", "failed", "blocked", "cancelled"},
20
+ "failed": {"failed", "queued", "running", "blocked", "cancelled"},
21
+ "blocked": {"blocked", "queued", "cancelled"},
22
+ "succeeded": {"succeeded"},
23
+ "cancelled": {"cancelled"},
24
+ }
25
+ SAFE_ID = re.compile(r"^[A-Za-z0-9][A-Za-z0-9._:-]{0,119}$")
26
+ SAFE_ERROR = re.compile(r"^[a-z0-9][a-z0-9._-]{0,79}$")
27
+
28
+
29
+ def load(path: Path) -> dict[str, Any]:
30
+ try:
31
+ value = json.loads(path.read_text(encoding="utf-8"))
32
+ except (OSError, json.JSONDecodeError) as error:
33
+ raise ValueError(f"cannot read orchestration manifest: {error}") from error
34
+ if not isinstance(value, dict):
35
+ raise ValueError("orchestration manifest root must be an object")
36
+ return value
37
+
38
+
39
+ def safe_status_ref(value: object) -> bool:
40
+ if not isinstance(value, str) or not value.strip() or "?" in value or "#" in value or "://" in value:
41
+ return False
42
+ if value.startswith("/"):
43
+ return True
44
+ return value.startswith(".") and ".." not in value
45
+
46
+
47
+ def validate(payload: dict[str, Any]) -> list[str]:
48
+ errors: list[str] = []
49
+ if payload.get("schemaVersion") != SCHEMA:
50
+ errors.append(f"schemaVersion must be {SCHEMA}")
51
+ workflows = payload.get("workflows")
52
+ if not isinstance(workflows, list):
53
+ errors.append("workflows must be an array")
54
+ workflows = []
55
+ seen_names: set[str] = set()
56
+ seen_jobs: set[str] = set()
57
+ seen_keys: set[str] = set()
58
+ claimed_fields: dict[str, str] = {}
59
+ for index, workflow in enumerate(workflows):
60
+ prefix = f"workflows[{index}]"
61
+ if not isinstance(workflow, dict):
62
+ errors.append(f"{prefix} must be an object")
63
+ continue
64
+ name = workflow.get("name")
65
+ if name not in WORKFLOWS:
66
+ errors.append(f"{prefix}.name must be import, search, or research")
67
+ elif name in seen_names:
68
+ errors.append(f"{prefix}.name is duplicated")
69
+ else:
70
+ seen_names.add(name)
71
+ for field, seen, pattern in (("jobId", seen_jobs, SAFE_ID), ("idempotencyKey", seen_keys, SAFE_ID)):
72
+ value = workflow.get(field)
73
+ if not isinstance(value, str) or not pattern.fullmatch(value):
74
+ errors.append(f"{prefix}.{field} must be a non-empty safe identifier")
75
+ elif value in seen:
76
+ errors.append(f"{prefix}.{field} is duplicated")
77
+ else:
78
+ seen.add(value)
79
+ state = workflow.get("state")
80
+ if state not in STATES:
81
+ errors.append(f"{prefix}.state is unsupported")
82
+ if not isinstance(workflow.get("owner"), str) or not workflow["owner"].strip():
83
+ errors.append(f"{prefix}.owner is required")
84
+ if not safe_status_ref(workflow.get("statusRef")):
85
+ errors.append(f"{prefix}.statusRef must be a pollable local route or relative artifact reference")
86
+ attempt = workflow.get("attempt")
87
+ max_attempts = workflow.get("maxAttempts")
88
+ if not isinstance(attempt, int) or isinstance(attempt, bool) or attempt < 1:
89
+ errors.append(f"{prefix}.attempt must be a positive integer")
90
+ if not isinstance(max_attempts, int) or isinstance(max_attempts, bool) or max_attempts < 1:
91
+ errors.append(f"{prefix}.maxAttempts must be a positive integer")
92
+ if isinstance(attempt, int) and isinstance(max_attempts, int) and attempt > max_attempts:
93
+ errors.append(f"{prefix}.attempt cannot exceed maxAttempts")
94
+ next_action = workflow.get("nextAction")
95
+ if not isinstance(next_action, str) or not next_action.strip():
96
+ errors.append(f"{prefix}.nextAction is required for polling or recovery")
97
+
98
+ writes = workflow.get("writes")
99
+ if not isinstance(writes, list) or not writes or any(not isinstance(field, str) or not field.strip() for field in writes):
100
+ errors.append(f"{prefix}.writes must contain owned field paths")
101
+ writes = []
102
+ for field in writes:
103
+ if field in claimed_fields:
104
+ errors.append(f"field {field!r} is claimed by more than one workflow")
105
+ else:
106
+ claimed_fields[field] = name if isinstance(name, str) else "unknown"
107
+
108
+ history = workflow.get("history")
109
+ if not isinstance(history, list) or not history:
110
+ errors.append(f"{prefix}.history must contain the state transition history")
111
+ history = []
112
+ previous = None
113
+ for history_index, event in enumerate(history):
114
+ event_prefix = f"{prefix}.history[{history_index}]"
115
+ if not isinstance(event, dict) or event.get("state") not in STATES:
116
+ errors.append(f"{event_prefix}.state is unsupported")
117
+ continue
118
+ if not isinstance(event.get("at"), str) or not event["at"].strip():
119
+ errors.append(f"{event_prefix}.at is required")
120
+ if previous is not None and event["state"] not in TRANSITIONS[previous]:
121
+ errors.append(f"{event_prefix} is an invalid state transition")
122
+ previous = event["state"]
123
+ if state in STATES and history and isinstance(history[-1], dict) and history[-1].get("state") != state:
124
+ errors.append(f"{prefix}.history must end at the current state")
125
+ if state == "failed":
126
+ error_code = workflow.get("errorCode")
127
+ if not isinstance(error_code, str) or not SAFE_ERROR.fullmatch(error_code):
128
+ errors.append(f"{prefix}.errorCode is required for a failed workflow")
129
+
130
+ if seen_names != WORKFLOWS:
131
+ errors.append("workflows must contain exactly one import, search, and research state machine")
132
+ ownership = payload.get("fieldOwnership")
133
+ if not isinstance(ownership, dict):
134
+ errors.append("fieldOwnership must map each written field to one workflow")
135
+ ownership = {}
136
+ for field, owner in ownership.items():
137
+ if not isinstance(field, str) or not field.strip() or owner not in WORKFLOWS:
138
+ errors.append("fieldOwnership contains an invalid field owner")
139
+ for field, owner in claimed_fields.items():
140
+ if ownership.get(field) != owner:
141
+ errors.append(f"fieldOwnership does not assign {field!r} to its writing workflow")
142
+ return errors
143
+
144
+
145
+ def orchestration(manifest: Path, output: Path) -> int:
146
+ try:
147
+ payload = load(manifest)
148
+ errors = validate(payload)
149
+ except ValueError as error:
150
+ errors = [str(error)]
151
+ payload = {}
152
+ workflows = payload.get("workflows") if isinstance(payload.get("workflows"), list) else []
153
+ report = {
154
+ "schemaVersion": SCHEMA,
155
+ "status": "passed" if not errors else "failed",
156
+ "passed": not errors,
157
+ "workflowCount": len(workflows),
158
+ "stateMachines": sorted(
159
+ {item.get("name") for item in workflows if isinstance(item, dict) and isinstance(item.get("name"), str)}
160
+ ),
161
+ "errors": errors,
162
+ }
163
+ output.parent.mkdir(parents=True, exist_ok=True)
164
+ output.write_text(json.dumps(report, indent=2, ensure_ascii=False) + "\n", encoding="utf-8")
165
+ print(json.dumps({"status": report["status"], "report": str(output.resolve()), "errors": errors}, indent=2, ensure_ascii=False))
166
+ return 0 if not errors else 1
167
+
168
+
169
+ def main() -> int:
170
+ parser = argparse.ArgumentParser(description=__doc__)
171
+ parser.add_argument("--project", type=Path, default=Path.cwd())
172
+ parser.add_argument("--manifest", type=Path, required=True, help="sanitized orchestration contract JSON")
173
+ parser.add_argument("--output", type=Path, default=Path("docs/service-onboarding-orchestration.json"))
174
+ args = parser.parse_args()
175
+ project = args.project.resolve()
176
+ manifest = args.manifest if args.manifest.is_absolute() else project / args.manifest
177
+ output = args.output if args.output.is_absolute() else project / args.output
178
+ try:
179
+ return orchestration(manifest, output)
180
+ except OSError as error:
181
+ print(f"BLOCKED: maggie service orchestration-validate: {error}", file=sys.stderr)
182
+ return 2
183
+
184
+
185
+ if __name__ == "__main__":
186
+ raise SystemExit(main())
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@topy-ai/maggie",
3
- "version": "0.7.23",
3
+ "version": "0.7.25",
4
4
  "description": "Install and manage Maggie Skills for AI coding agents",
5
5
  "license": "MIT",
6
6
  "type": "module",