design-playbook 0.12.0 → 0.13.0
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/design_playbook.py +20 -0
- package/mcp/evidence/capture_contract.py +268 -0
- package/mcp/evidence/capture_runtime.py +505 -0
- package/mcp/evidence/containment.py +164 -0
- package/mcp/evidence/ledger_syntax.py +131 -0
- package/mcp/evidence/server.py +16 -570
- package/mcp/evidence/test_capture_contract.py +355 -0
- package/mcp/evidence/test_containment.py +581 -0
- package/mcp/evidence/test_ledger_syntax.py +252 -0
- package/mcp/evidence/test_server_stdio.py +254 -426
- package/mcp/preview/compatibility.py +158 -0
- package/mcp/preview/control.js +7 -9
- package/mcp/preview/control.py +3 -7
- package/mcp/preview/integrity.py +362 -0
- package/mcp/preview/{browser.py → review_session.py} +61 -15
- package/mcp/preview/server.py +14 -9
- package/mcp/preview/test_browser_control.py +194 -177
- package/mcp/preview/test_integrity.py +208 -0
- package/mcp/preview/test_server_stdio.py +10 -449
- package/mcp/preview/test_transaction.py +23 -18
- package/mcp/preview/test_versions.py +77 -24
- package/mcp/preview/test_versions_freeze.py +176 -0
- package/mcp/preview/transaction.py +32 -56
- package/mcp/preview/util.py +1 -21
- package/mcp/preview/versions.py +78 -132
- package/mcp/test_transport.py +6 -2
- package/package.json +2 -1
- package/scripts/__init__.py +0 -0
- package/scripts/g1_spec.py +81 -0
- package/scripts/g2_g4_pointback.py +310 -0
- package/scripts/g5_preview.py +265 -0
- package/scripts/g6_evidence.py +252 -0
- package/scripts/g6_records.py +56 -0
- package/scripts/g6_warnings.py +104 -0
- package/scripts/g7_contract_drift.py +2 -2
- package/scripts/run_facts.py +212 -0
- package/scripts/run_status.py +127 -147
- package/scripts/stages.py +90 -0
- package/scripts/test_verdict_syntax.py +239 -0
- package/scripts/validate_run.py +67 -978
- package/scripts/verdict_syntax.py +96 -0
- package/mcp/preview/test_anchor_v2.py +0 -69
- package/scripts/_preview_integrity.py +0 -288
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
"""Namespace alias: absolute ``design_playbook.*`` imports inside this plugin.
|
|
2
|
+
|
|
3
|
+
The plugin directory is ``design-playbook`` (hyphen), so Python's import
|
|
4
|
+
system cannot load it under the identifier ``design_playbook`` — FileFinder
|
|
5
|
+
matches directory names literally. This module aliases the package root onto
|
|
6
|
+
``design_playbook.__path__`` so that, once the one bootstrap (ADR-0022) puts
|
|
7
|
+
the package root on ``sys.path``, absolute imports such as
|
|
8
|
+
``design_playbook.mcp.preview.integrity`` and
|
|
9
|
+
``design_playbook.scripts.stages`` resolve to the real ``mcp/`` and
|
|
10
|
+
``scripts/`` trees below.
|
|
11
|
+
|
|
12
|
+
The alias also keeps the import seam namespaced: a bare ``mcp.*`` import
|
|
13
|
+
would collide with the PyPI ``mcp`` SDK when the host has it installed.
|
|
14
|
+
"""
|
|
15
|
+
from __future__ import annotations
|
|
16
|
+
|
|
17
|
+
from pathlib import Path
|
|
18
|
+
|
|
19
|
+
__path__ = [str(Path(__file__).resolve().parent)]
|
|
20
|
+
__all__: list[str] = []
|
|
@@ -0,0 +1,268 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Capture contract v1 rules, owned by the bundled Evidence runtime.
|
|
3
|
+
|
|
4
|
+
ADR-0018 enforcement site 1: this module is the single owner of the v1
|
|
5
|
+
contract surface — the write-side parse/normalize authority, the read-side
|
|
6
|
+
full-shape snapshot validator, and the contract-fields JSON Schema fragment
|
|
7
|
+
the provider tool schema composes. The provider (server.py) keeps only
|
|
8
|
+
Runtime Object fields, path/overwrite boundaries, and Playwright I/O; G6
|
|
9
|
+
(scripts/validate_run.py) validates bound manifest request snapshots through
|
|
10
|
+
``validate_capture_snapshot`` instead of hand-written partial checks.
|
|
11
|
+
|
|
12
|
+
Named ``capture_contract.py`` to avoid collision with
|
|
13
|
+
``scripts/contract_v1.py`` (the persistent contract, ADR-0017).
|
|
14
|
+
"""
|
|
15
|
+
from __future__ import annotations
|
|
16
|
+
|
|
17
|
+
import math
|
|
18
|
+
from dataclasses import dataclass
|
|
19
|
+
from typing import Any
|
|
20
|
+
|
|
21
|
+
CAPTURE_SCHEMA_VERSION = 1
|
|
22
|
+
COLOR_SCHEMES = frozenset({"light", "dark", "no-preference"})
|
|
23
|
+
MIN_VIEWPORT_DPR = 0.1
|
|
24
|
+
RECAPTURE_HINT = "recapture with capture contract schemaVersion=1"
|
|
25
|
+
FREEZE_DEFAULTS = {
|
|
26
|
+
"enabled": True,
|
|
27
|
+
"waitFonts": True,
|
|
28
|
+
"networkIdle": False,
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
@dataclass(frozen=True)
|
|
33
|
+
class CaptureFact:
|
|
34
|
+
"""One host-neutral capture-contract violation (empty facts = valid)."""
|
|
35
|
+
|
|
36
|
+
code: str
|
|
37
|
+
detail: str
|
|
38
|
+
expected: str = ""
|
|
39
|
+
actual: str = ""
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def _is_schema_version(value: object) -> bool:
|
|
43
|
+
return type(value) is int and value == CAPTURE_SCHEMA_VERSION
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def _bad_viewport(viewport: dict[str, Any]) -> str | None:
|
|
47
|
+
"""First malformed viewport field, or None when the shape is valid.
|
|
48
|
+
|
|
49
|
+
Mirrors the parser's field rules exactly so the read side and write side
|
|
50
|
+
cannot disagree on what a valid viewport is.
|
|
51
|
+
"""
|
|
52
|
+
width = viewport.get("width")
|
|
53
|
+
height = viewport.get("height")
|
|
54
|
+
dpr = viewport.get("devicePixelRatio")
|
|
55
|
+
scheme = viewport.get("colorScheme")
|
|
56
|
+
if type(width) is not int or width < 1:
|
|
57
|
+
return "viewport.width must be a positive integer"
|
|
58
|
+
if type(height) is not int or height < 1:
|
|
59
|
+
return "viewport.height must be a positive integer"
|
|
60
|
+
if (
|
|
61
|
+
type(dpr) not in (int, float)
|
|
62
|
+
or not math.isfinite(dpr)
|
|
63
|
+
or dpr < MIN_VIEWPORT_DPR
|
|
64
|
+
):
|
|
65
|
+
return (
|
|
66
|
+
"viewport.devicePixelRatio must be a number greater than or equal "
|
|
67
|
+
f"to {MIN_VIEWPORT_DPR}"
|
|
68
|
+
)
|
|
69
|
+
if not isinstance(scheme, str) or scheme not in COLOR_SCHEMES:
|
|
70
|
+
return (
|
|
71
|
+
f"viewport.colorScheme must be one of {sorted(COLOR_SCHEMES)}; "
|
|
72
|
+
f"got {scheme!r}"
|
|
73
|
+
)
|
|
74
|
+
return None
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def _bad_freeze(freeze: dict[str, Any]) -> str | None:
|
|
78
|
+
"""First malformed freeze field, or None when the shape is valid."""
|
|
79
|
+
for key in FREEZE_DEFAULTS:
|
|
80
|
+
if not isinstance(freeze.get(key), bool):
|
|
81
|
+
return f"freeze.{key} must be a boolean"
|
|
82
|
+
return None
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def parse_capture_contract(args: dict[str, Any]) -> dict[str, Any]:
|
|
86
|
+
"""Validate capture contract v1 fields and return a normalized request.
|
|
87
|
+
|
|
88
|
+
Write authority (ADR-0018): raises ValueError with a recapture instruction
|
|
89
|
+
for missing/unknown versions or an incomplete viewport. Pure — no browser
|
|
90
|
+
side effects. The normalized output is what the provider echoes into the
|
|
91
|
+
manifest request snapshot, so real snapshots always carry freeze defaults.
|
|
92
|
+
"""
|
|
93
|
+
if "schemaVersion" not in args:
|
|
94
|
+
raise ValueError(
|
|
95
|
+
f"capture contract schemaVersion is required; {RECAPTURE_HINT}"
|
|
96
|
+
)
|
|
97
|
+
version = args.get("schemaVersion")
|
|
98
|
+
if not _is_schema_version(version):
|
|
99
|
+
raise ValueError(
|
|
100
|
+
f"unsupported capture schemaVersion {version!r}; {RECAPTURE_HINT}"
|
|
101
|
+
)
|
|
102
|
+
viewport = args.get("viewport")
|
|
103
|
+
if not isinstance(viewport, dict):
|
|
104
|
+
raise ValueError(
|
|
105
|
+
f"viewport object is required for schemaVersion=1; {RECAPTURE_HINT}"
|
|
106
|
+
)
|
|
107
|
+
bad_viewport = _bad_viewport(viewport)
|
|
108
|
+
if bad_viewport is not None:
|
|
109
|
+
raise ValueError(bad_viewport)
|
|
110
|
+
width = viewport["width"]
|
|
111
|
+
height = viewport["height"]
|
|
112
|
+
dpr = viewport["devicePixelRatio"]
|
|
113
|
+
scheme = viewport["colorScheme"]
|
|
114
|
+
|
|
115
|
+
freeze_raw = args.get("freeze")
|
|
116
|
+
if freeze_raw is None:
|
|
117
|
+
freeze_raw = {}
|
|
118
|
+
if not isinstance(freeze_raw, dict):
|
|
119
|
+
raise ValueError("freeze must be an object when provided")
|
|
120
|
+
freeze = {
|
|
121
|
+
key: freeze_raw.get(key, FREEZE_DEFAULTS[key])
|
|
122
|
+
for key in FREEZE_DEFAULTS
|
|
123
|
+
}
|
|
124
|
+
bad_freeze = _bad_freeze(freeze)
|
|
125
|
+
if bad_freeze is not None:
|
|
126
|
+
raise ValueError(bad_freeze)
|
|
127
|
+
|
|
128
|
+
return {
|
|
129
|
+
"schemaVersion": CAPTURE_SCHEMA_VERSION,
|
|
130
|
+
"viewport": {
|
|
131
|
+
"width": width,
|
|
132
|
+
"height": height,
|
|
133
|
+
"devicePixelRatio": float(dpr),
|
|
134
|
+
"colorScheme": scheme,
|
|
135
|
+
},
|
|
136
|
+
"freeze": freeze,
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
def validate_capture_snapshot(snapshot: object) -> list[CaptureFact]:
|
|
141
|
+
"""Read authority: full-shape validation of a bound manifest snapshot.
|
|
142
|
+
|
|
143
|
+
Host-neutral — never raises, returns facts (empty list = valid). Strict on
|
|
144
|
+
the v1 full shape: schemaVersion=1, a complete typed viewport, and a
|
|
145
|
+
complete boolean freeze. The parser normalizes defaults at capture time;
|
|
146
|
+
the read side requires the recorded snapshot to be self-contained so the
|
|
147
|
+
manifest alone can reproduce the capture (ADR-0018). Malformed viewport
|
|
148
|
+
shape or missing freeze therefore fail closed (sanctioned correction; was
|
|
149
|
+
lax in the old hand-written G6 checks). Unknown extra keys are tolerated
|
|
150
|
+
(host-neutral forward compatibility).
|
|
151
|
+
"""
|
|
152
|
+
if not isinstance(snapshot, dict):
|
|
153
|
+
return [CaptureFact(
|
|
154
|
+
"missing_schema_version",
|
|
155
|
+
"no request snapshot on the bound entry",
|
|
156
|
+
expected="schemaVersion=1 with viewport and freeze",
|
|
157
|
+
actual=("None" if snapshot is None else type(snapshot).__name__),
|
|
158
|
+
)]
|
|
159
|
+
version = snapshot.get("schemaVersion")
|
|
160
|
+
if not _is_schema_version(version):
|
|
161
|
+
return [CaptureFact(
|
|
162
|
+
("missing_schema_version" if version is None
|
|
163
|
+
else "unsupported_schema_version"),
|
|
164
|
+
("missing schemaVersion" if version is None
|
|
165
|
+
else f"unsupported schemaVersion {version!r}"),
|
|
166
|
+
expected="schemaVersion=1",
|
|
167
|
+
actual=repr(version),
|
|
168
|
+
)]
|
|
169
|
+
viewport = snapshot.get("viewport")
|
|
170
|
+
if not isinstance(viewport, dict):
|
|
171
|
+
return [CaptureFact(
|
|
172
|
+
"missing_viewport",
|
|
173
|
+
"viewport object is required for schemaVersion=1",
|
|
174
|
+
expected="viewport width/height/devicePixelRatio/colorScheme",
|
|
175
|
+
actual=type(viewport).__name__ if viewport is not None else "missing",
|
|
176
|
+
)]
|
|
177
|
+
bad_viewport = _bad_viewport(viewport)
|
|
178
|
+
if bad_viewport is not None:
|
|
179
|
+
return [CaptureFact(
|
|
180
|
+
"bad_viewport_shape",
|
|
181
|
+
bad_viewport,
|
|
182
|
+
expected="viewport width/height/devicePixelRatio/colorScheme",
|
|
183
|
+
actual=bad_viewport,
|
|
184
|
+
)]
|
|
185
|
+
freeze = snapshot.get("freeze")
|
|
186
|
+
if not isinstance(freeze, dict):
|
|
187
|
+
return [CaptureFact(
|
|
188
|
+
"missing_freeze",
|
|
189
|
+
"freeze snapshot is required on the bound entry",
|
|
190
|
+
expected="freeze enabled/waitFonts/networkIdle booleans",
|
|
191
|
+
actual=type(freeze).__name__ if freeze is not None else "missing",
|
|
192
|
+
)]
|
|
193
|
+
bad_freeze = _bad_freeze(freeze)
|
|
194
|
+
if bad_freeze is not None:
|
|
195
|
+
return [CaptureFact(
|
|
196
|
+
"bad_freeze_shape",
|
|
197
|
+
bad_freeze,
|
|
198
|
+
expected="freeze enabled/waitFonts/networkIdle booleans",
|
|
199
|
+
actual=bad_freeze,
|
|
200
|
+
)]
|
|
201
|
+
return []
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
def capture_contract_schema_fragment() -> dict[str, Any]:
|
|
205
|
+
"""JSON Schema fragment for the contract fields (schemaVersion/viewport/freeze).
|
|
206
|
+
|
|
207
|
+
The provider composes this into its tool schema alongside its Runtime
|
|
208
|
+
Object fields. const/enum/required/default all come from the same module
|
|
209
|
+
constants the parser uses, so the schema and the parser cannot drift.
|
|
210
|
+
"""
|
|
211
|
+
return {
|
|
212
|
+
"properties": {
|
|
213
|
+
"schemaVersion": {
|
|
214
|
+
"type": "integer",
|
|
215
|
+
"description": "Capture contract version. Only 1 is supported.",
|
|
216
|
+
"const": CAPTURE_SCHEMA_VERSION,
|
|
217
|
+
},
|
|
218
|
+
"viewport": {
|
|
219
|
+
"type": "object",
|
|
220
|
+
"description": (
|
|
221
|
+
"Required capture viewport. Provider does not invent "
|
|
222
|
+
"desktop defaults."
|
|
223
|
+
),
|
|
224
|
+
"properties": {
|
|
225
|
+
"width": {"type": "integer", "minimum": 1},
|
|
226
|
+
"height": {"type": "integer", "minimum": 1},
|
|
227
|
+
"devicePixelRatio": {
|
|
228
|
+
"type": "number",
|
|
229
|
+
"minimum": MIN_VIEWPORT_DPR,
|
|
230
|
+
},
|
|
231
|
+
"colorScheme": {
|
|
232
|
+
"type": "string",
|
|
233
|
+
"enum": sorted(COLOR_SCHEMES),
|
|
234
|
+
},
|
|
235
|
+
},
|
|
236
|
+
"required": [
|
|
237
|
+
"width",
|
|
238
|
+
"height",
|
|
239
|
+
"devicePixelRatio",
|
|
240
|
+
"colorScheme",
|
|
241
|
+
],
|
|
242
|
+
"additionalProperties": False,
|
|
243
|
+
},
|
|
244
|
+
"freeze": {
|
|
245
|
+
"type": "object",
|
|
246
|
+
"description": (
|
|
247
|
+
"Deterministic freeze controls. Defaults: "
|
|
248
|
+
"enabled=true, waitFonts=true, networkIdle=false."
|
|
249
|
+
),
|
|
250
|
+
"properties": {
|
|
251
|
+
"enabled": {
|
|
252
|
+
"type": "boolean",
|
|
253
|
+
"default": FREEZE_DEFAULTS["enabled"],
|
|
254
|
+
},
|
|
255
|
+
"waitFonts": {
|
|
256
|
+
"type": "boolean",
|
|
257
|
+
"default": FREEZE_DEFAULTS["waitFonts"],
|
|
258
|
+
},
|
|
259
|
+
"networkIdle": {
|
|
260
|
+
"type": "boolean",
|
|
261
|
+
"default": FREEZE_DEFAULTS["networkIdle"],
|
|
262
|
+
},
|
|
263
|
+
},
|
|
264
|
+
"additionalProperties": False,
|
|
265
|
+
},
|
|
266
|
+
},
|
|
267
|
+
"required": ["schemaVersion", "viewport"],
|
|
268
|
+
}
|