Files
Cathryn Lavery dbc4abd965 feat(skill): pre-draw checkpoint, docs-sync gate, packaged self-check, ADRs
- SKILL.md: confirm type/pattern/size/cuts with the user before rendering;
  checklist hooks for the checkpoint and the packaged self-check
- scripts/verify-docs-sync.py: CI now fails when the frontmatter description
  loses a type's lexical hook, the gallery can't reach a shipped example, or
  the README tree names a missing file
- skills/diagram-design/scripts/self_check.py: distilled output checker that
  ships with the skill so installed agents can verify their own diagrams;
  adversarial suite in scripts/test-self-check.py
- assets/index.html: six new tabs (OAuth sequence + five single-variant
  examples with a data-single variant lock) so every shipped example is
  browsable
- docs/adr/0001-0004: settled policies recorded (pinned controller, taxonomy
  freeze, reveal-only autoplay, byte cap + trigger-rich description)
- README: It's-working-if section, new gates documented; CONTRIBUTING: gate
  table, ADR guidance, type-addition invariants; CI: two new steps
2026-08-12 17:00:08 -07:00

139 lines
4.2 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env python3
"""Adversarial tests for the packaged skill self-check (self_check.py)."""
from __future__ import annotations
import importlib.util
import sys
import tempfile
from pathlib import Path
ROOT = Path(__file__).resolve().parent.parent
SELF_CHECK = ROOT / "skills/diagram-design/scripts/self_check.py"
ASSET_DIR = ROOT / "skills/diagram-design/assets"
TEMPLATE = ASSET_DIR / "template-motion.html"
EXAMPLE = ASSET_DIR / "example-policy-trace-animated.html"
STATIC_EXAMPLE = ASSET_DIR / "example-architecture.html"
def load_self_check():
spec = importlib.util.spec_from_file_location("self_check", SELF_CHECK)
assert spec is not None and spec.loader is not None
module = importlib.util.module_from_spec(spec)
spec.loader.exec_module(module)
return module
def main() -> int:
module = load_self_check()
failures: list[str] = []
def check_pass(label: str, path: Path) -> None:
errors = module.verify(path)
if errors:
failures.append(f"{label}: expected pass, got {errors}")
else:
print(f"OK: {label} passes")
def check_fail(label: str, source: str, needle: str) -> None:
with tempfile.TemporaryDirectory() as scratch:
candidate = Path(scratch) / "candidate.html"
candidate.write_text(source, encoding="utf-8")
errors = module.verify(candidate)
if not any(needle in error for error in errors):
failures.append(f"{label}: expected an error containing {needle!r}, got {errors}")
else:
print(f"OK: {label} rejected")
check_pass("shipped template", TEMPLATE)
check_pass("shipped animated example", EXAMPLE)
check_pass("shipped static example", STATIC_EXAMPLE)
static = STATIC_EXAMPLE.read_text(encoding="utf-8")
animated = EXAMPLE.read_text(encoding="utf-8")
check_fail(
"executable attribute",
static.replace("<body>", '<body onload="fetch(1)">', 1),
"executable attribute",
)
check_fail(
"remote image",
static.replace("<body>", '<body><img src="https://tracker.example/p.gif">', 1),
"remote reference",
)
check_fail(
"lookalike fonts host",
static.replace(
"https://fonts.googleapis.com/css2",
"https://fonts.googleapis.com.evil.tld/css2",
1,
),
"approved Google Fonts",
)
check_fail(
"javascript URL",
static.replace("<body>", '<body><a href="javascript:alert(1)">x</a>', 1),
"executable URL",
)
check_fail(
"data html URL",
static.replace("<body>", '<body><a href="data:text/html,x">x</a>', 1),
"executable URL",
)
check_fail(
"iframe injection",
static.replace("<body>", "<body><iframe></iframe>", 1),
"not allowed",
)
check_fail(
"arbitrary script",
static.replace("</body>", "<script>alert(1)</script></body>", 1),
"canonical data-diagram-controls attribute",
)
check_fail(
"missing svg role",
static.replace('role="img"', 'role="presentation"', 1),
"role=img",
)
check_fail(
"modified controller",
animated.replace("'motion-ready'", "'motion-ready' /* patched */", 1),
"exactly match the controller",
)
check_fail(
"second script",
animated.replace("</body>", "<script data-x></script></body>", 1),
"at most one script",
)
check_fail(
"hidden motion item",
animated.replace(
'<g data-motion-item data-step="1"',
'<g style="opacity:0" data-motion-item data-step="1"',
1,
),
"hidden in source",
)
check_fail(
"missing noscript",
animated.replace("<noscript>", "<div>", 1).replace("</noscript>", "</div>", 1),
"<noscript>",
)
check_fail(
"non-decimal step count",
animated.replace('data-step-count="5"', 'data-step-count="٥"', 1),
"ASCII decimal",
)
if failures:
for failure in failures:
print(f"FAIL: {failure}")
return 1
print("All self-check tests passed")
return 0
if __name__ == "__main__":
sys.exit(main())