motionloom 2.0.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.
Files changed (112) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +179 -0
  3. package/SKILL.md +122 -0
  4. package/agent-card.json +161 -0
  5. package/assets/library/ATTRIBUTION.md +6 -0
  6. package/assets/library/README.md +20 -0
  7. package/assets/library/avatar-base.svg +19 -0
  8. package/assets/library/error-alert.json +1 -0
  9. package/assets/library/rive/ATTRIBUTION.md +12 -0
  10. package/assets/library/rive/state-machine-test.riv +0 -0
  11. package/assets/library/success-check.json +1 -0
  12. package/bin/motionloom.mjs +81 -0
  13. package/docs/BROWSER-REVIEW-E2E.md +78 -0
  14. package/docs/CATEGORIES.md +16 -0
  15. package/docs/CHECKLIST.md +31 -0
  16. package/docs/DEEP-AUDIT-WORKING-NOTES.md +22 -0
  17. package/docs/FRAMEWORK-SELECTION.md +26 -0
  18. package/docs/PROJECT-MANIFEST.md +37 -0
  19. package/docs/ROADMAP-INTELLIGENCE.md +224 -0
  20. package/docs/audits/1.10.0-attestation-research-notes.md +19 -0
  21. package/docs/audits/1.8.0-trust-boundary-hardening.md +56 -0
  22. package/docs/audits/1.9.0-evidence-interoperability-threat-model.md +37 -0
  23. package/docs/audits/2.0.0-attestation-acceptance.md +30 -0
  24. package/docs/releases/1.5.0.md +25 -0
  25. package/docs/releases/1.6.0.md +27 -0
  26. package/docs/releases/1.7.0.md +23 -0
  27. package/docs/releases/1.8.0.md +23 -0
  28. package/docs/releases/1.9.0.md +21 -0
  29. package/docs/releases/2.0.0.md +21 -0
  30. package/docs/releases/npm-publish-from-workstation.md +88 -0
  31. package/docs/research/AGENT-PROTOCOL-FINDINGS.md +43 -0
  32. package/examples/report-demo/REPORT.md +50 -0
  33. package/examples/report-demo/artifact-manifest.json +25 -0
  34. package/examples/report-demo/decision-log.jsonl +0 -0
  35. package/examples/report-demo/execution-report.json +70 -0
  36. package/examples/report-demo/handoff.json +22 -0
  37. package/examples/report-demo/issue-register.json +5 -0
  38. package/examples/report-demo/task.json +13 -0
  39. package/package.json +95 -0
  40. package/project-context.example.json +26 -0
  41. package/references/browser-review-contract.md +32 -0
  42. package/references/dotlottie-source-notes.md +21 -0
  43. package/references/intelligence-core.md +98 -0
  44. package/references/reporting-contract.md +38 -0
  45. package/references/runtime-capability.md +12 -0
  46. package/references/signed-attestation.md +31 -0
  47. package/schemas/artifact-manifest.schema.json +22 -0
  48. package/schemas/browser-review-candidate.schema.json +24 -0
  49. package/schemas/capability-registry.schema.json +61 -0
  50. package/schemas/continuity-report.schema.json +52 -0
  51. package/schemas/evidence-verifier-report.schema.json +35 -0
  52. package/schemas/execution-report.schema.json +35 -0
  53. package/schemas/fix-plan.schema.json +44 -0
  54. package/schemas/handoff.schema.json +19 -0
  55. package/schemas/motion-ir.schema.json +74 -0
  56. package/schemas/project-graph.schema.json +70 -0
  57. package/schemas/provenance.schema.json +76 -0
  58. package/schemas/runtime-evidence.schema.json +48 -0
  59. package/schemas/runtime-telemetry.schema.json +50 -0
  60. package/schemas/scene-manifest.schema.json +45 -0
  61. package/schemas/semantic-benchmark.schema.json +26 -0
  62. package/schemas/semantic-lint-report.schema.json +48 -0
  63. package/schemas/signed-attestation.schema.json +95 -0
  64. package/schemas/task.schema.json +39 -0
  65. package/schemas/trust-policy.schema.json +53 -0
  66. package/scripts/analyze.sh +13 -0
  67. package/scripts/attestation-keygen.py +63 -0
  68. package/scripts/attestation-verifier.py +178 -0
  69. package/scripts/attestation.py +288 -0
  70. package/scripts/capture-runtime-telemetry.sh +37 -0
  71. package/scripts/devlab.sh +77 -0
  72. package/scripts/eval-intelligence.py +377 -0
  73. package/scripts/evidence-verifier.py +222 -0
  74. package/scripts/fetch-library.sh +57 -0
  75. package/scripts/intelligence.py +1543 -0
  76. package/scripts/manifest.py +61 -0
  77. package/scripts/pr.sh +103 -0
  78. package/scripts/quality-gate.py +378 -0
  79. package/scripts/render-node.mjs +53 -0
  80. package/scripts/render.sh +37 -0
  81. package/scripts/report-contract.py +181 -0
  82. package/scripts/report.py +588 -0
  83. package/scripts/review-hook.py +199 -0
  84. package/scripts/runtime-adapters.mjs +187 -0
  85. package/scripts/skill-doctor.py +150 -0
  86. package/scripts/to-dotlottie.mjs +99 -0
  87. package/scripts/to-dotlottie.sh +25 -0
  88. package/scripts/validate-lottie.py +102 -0
  89. package/src/core/analyzer.py +226 -0
  90. package/src/core/snapshot.py +124 -0
  91. package/src/core/spec.py +240 -0
  92. package/src/output/browser-review-smoke/animation.json +57 -0
  93. package/src/output/browser-review-smoke/browser-review.json +21 -0
  94. package/src/output/browser-review-smoke/manifest.json +22 -0
  95. package/src/output/browser-review-smoke/motion-spec.json +28 -0
  96. package/src/output/browser-review-smoke/snapshot/.render-meta.json +10 -0
  97. package/src/output/browser-review-smoke/snapshot/frame-00.png +0 -0
  98. package/src/output/browser-review-smoke/snapshot/frame-100.png +0 -0
  99. package/src/output/browser-review-smoke/snapshot/frame-50.png +0 -0
  100. package/src/rig/README.md +35 -0
  101. package/src/rig/cutout_rig.py +211 -0
  102. package/templates/framer-motion/ui-micro.tsx +50 -0
  103. package/templates/gsap/scroll-scene.js +54 -0
  104. package/templates/lottie/README.md +21 -0
  105. package/templates/lottie/react-component.tsx +82 -0
  106. package/templates/lottie/scaffold/animation.json +57 -0
  107. package/templates/lottie/scaffold/character-rig.svg +19 -0
  108. package/templates/lottie/vanilla.js +68 -0
  109. package/templates/rive/README.md +36 -0
  110. package/tests/evals/intelligence-cases.json +131 -0
  111. package/tests/scripts/run_tests.py +843 -0
  112. package/tests/scripts/test_attestation.py +172 -0
@@ -0,0 +1,172 @@
1
+ #!/usr/bin/env python3
2
+ """Focused DSSE/trust-policy contract tests for MotionLoom."""
3
+
4
+ from __future__ import annotations
5
+
6
+ import base64
7
+ import hashlib
8
+ import json
9
+ import subprocess
10
+ import sys
11
+ import tempfile
12
+ from datetime import datetime, timedelta, timezone
13
+ from pathlib import Path
14
+
15
+ from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PrivateKey
16
+
17
+
18
+ ROOT = Path(__file__).resolve().parents[2]
19
+ BUILDER = ROOT / "scripts/attestation.py"
20
+ VERIFIER = ROOT / "scripts/attestation-verifier.py"
21
+
22
+
23
+ def iso(value: datetime) -> str:
24
+ return value.astimezone(timezone.utc).isoformat().replace("+00:00", "Z")
25
+
26
+
27
+ def run(command: list[str]) -> subprocess.CompletedProcess[str]:
28
+ return subprocess.run(command, cwd=ROOT, capture_output=True, text=True)
29
+
30
+
31
+ def make_statement(**overrides: object) -> dict:
32
+ digest = "a" * 64
33
+ statement = {
34
+ "type": "https://motionloom.dev/attestation/v1",
35
+ "predicate_type": "https://motionloom.dev/predicate/animation-evidence/v1",
36
+ "subject": [{"name": "browser-review-smoke", "digest": {"sha256": digest}}],
37
+ "predicate": {
38
+ "task_id": "browser-review-smoke-task",
39
+ "scene": "browser-review-smoke",
40
+ "context_hash": digest,
41
+ "source_sha256": digest,
42
+ "manifest_sha256": digest,
43
+ "motion_ir_sha256": digest,
44
+ "evidence": {
45
+ "runtime_evidence_sha256": digest,
46
+ "runtime_telemetry_sha256": digest,
47
+ "verifier_report_sha256": digest,
48
+ },
49
+ "provenance_chain_hash": digest,
50
+ "policy_version": "1.0",
51
+ "generated_at": iso(datetime.now(timezone.utc)),
52
+ "builder": {"name": "motionloom-test-builder", "version": "1.0.0"},
53
+ },
54
+ }
55
+ predicate = statement["predicate"]
56
+ assert isinstance(predicate, dict)
57
+ predicate.update(overrides)
58
+ return statement
59
+
60
+
61
+ def sha256_file(path: Path) -> str:
62
+ return hashlib.sha256(path.read_bytes()).hexdigest()
63
+
64
+
65
+ def telemetry_bundle_sha256(task_dir: Path) -> str:
66
+ entries = []
67
+ for path in sorted(task_dir.glob("runtime-adapters/**/runtime-telemetry.json")):
68
+ entries.append({"path": path.resolve().relative_to(task_dir.resolve()).as_posix(), "sha256": sha256_file(path)})
69
+ body = json.dumps(entries, ensure_ascii=False, sort_keys=True, separators=(",", ":")).encode("utf-8")
70
+ return hashlib.sha256(body).hexdigest()
71
+
72
+
73
+ def make_policy(key_id: str, public_key: bytes, status: str = "active") -> dict:
74
+ now = datetime.now(timezone.utc)
75
+ key = {
76
+ "key_id": key_id,
77
+ "algorithm": "ed25519",
78
+ "public_key_base64": base64.b64encode(public_key).decode("ascii"),
79
+ "status": status,
80
+ "valid_from": iso(now - timedelta(days=1)),
81
+ }
82
+ if status == "revoked":
83
+ key["revoked_at"] = iso(now - timedelta(hours=1))
84
+ key["revocation_reason"] = "fixture compromise"
85
+ return {
86
+ "schema_version": "1.0",
87
+ "policy_id": "motionloom-test-policy",
88
+ "trust_domain": "https://motionloom.dev/trust/test",
89
+ "keys": [key],
90
+ "rotation": {"max_key_age_days": 90, "overlap_days": 7, "require_active_signer": True},
91
+ "revocation": {"mode": "local-policy", "fail_closed": True, "sources": ["fixture"]},
92
+ }
93
+
94
+
95
+ def main() -> int:
96
+ with tempfile.TemporaryDirectory() as temp_dir:
97
+ temp = Path(temp_dir)
98
+ private_key = Ed25519PrivateKey.generate()
99
+ key_id = "test-signer-v1"
100
+ key_file = temp / "private.key"
101
+ key_file.write_text(base64.b64encode(private_key.private_bytes_raw()).decode("ascii") + "\n", encoding="utf-8")
102
+ statement_file = temp / "statement.json"
103
+ statement_file.write_text(json.dumps(make_statement(), indent=2) + "\n", encoding="utf-8")
104
+ policy_file = temp / "trust-policy.json"
105
+ policy_file.write_text(json.dumps(make_policy(key_id, private_key.public_key().public_bytes_raw()), indent=2) + "\n", encoding="utf-8")
106
+ bundle_file = temp / "attestation.json"
107
+
108
+ built = run([sys.executable, str(BUILDER), "build", "--statement", str(statement_file), "--private-key", str(key_file), "--key-id", key_id, "--output", str(bundle_file)])
109
+ assert built.returncode == 0, built.stderr
110
+ valid = run([sys.executable, str(VERIFIER), "--attestation", str(bundle_file), "--trust-policy", str(policy_file), "--expected-task-id", "browser-review-smoke-task", "--expected-scene", "browser-review-smoke"])
111
+ valid_report = json.loads(valid.stdout)
112
+ assert valid.returncode == 0 and valid_report["verified"] is True and valid_report["approval"] is False
113
+
114
+ wrong_binding = run([sys.executable, str(VERIFIER), "--attestation", str(bundle_file), "--trust-policy", str(policy_file), "--expected-task-id", "different-task"])
115
+ assert wrong_binding.returncode == 14
116
+
117
+ tampered = json.loads(bundle_file.read_text(encoding="utf-8"))
118
+ tampered["envelope"]["payload_base64"] = base64.b64encode(b"tampered").decode("ascii")
119
+ tampered_file = temp / "tampered.json"
120
+ tampered_file.write_text(json.dumps(tampered, indent=2) + "\n", encoding="utf-8")
121
+ tamper_result = run([sys.executable, str(VERIFIER), "--attestation", str(tampered_file), "--trust-policy", str(policy_file)])
122
+ assert tamper_result.returncode == 11
123
+
124
+ revoked_file = temp / "revoked-policy.json"
125
+ revoked_file.write_text(json.dumps(make_policy(key_id, private_key.public_key().public_bytes_raw(), "revoked"), indent=2) + "\n", encoding="utf-8")
126
+ revoked = run([sys.executable, str(VERIFIER), "--attestation", str(bundle_file), "--trust-policy", str(revoked_file)])
127
+ assert revoked.returncode == 13
128
+
129
+ unknown_policy = make_policy("other-signer-v1", private_key.public_key().public_bytes_raw())
130
+ unknown_file = temp / "unknown-policy.json"
131
+ unknown_file.write_text(json.dumps(unknown_policy, indent=2) + "\n", encoding="utf-8")
132
+ unknown = run([sys.executable, str(VERIFIER), "--attestation", str(bundle_file), "--trust-policy", str(unknown_file)])
133
+ assert unknown.returncode == 13
134
+
135
+ scene_dir = ROOT / "src" / "output" / "browser-review-smoke"
136
+ task_dir = ROOT / "artifacts" / "browser-review-smoke-task"
137
+ task_statement = make_statement(
138
+ task_id="browser-review-smoke-task",
139
+ scene="browser-review-smoke",
140
+ context_hash=sha256_file(task_dir / "project-context.json"),
141
+ source_sha256=sha256_file(scene_dir / "animation.json"),
142
+ manifest_sha256=sha256_file(scene_dir / "manifest.json"),
143
+ motion_ir_sha256=sha256_file(task_dir / "motion-ir.json"),
144
+ evidence={
145
+ "runtime_evidence_sha256": sha256_file(task_dir / "runtime-adapters" / "runtime-evidence.json"),
146
+ "runtime_telemetry_sha256": telemetry_bundle_sha256(task_dir),
147
+ "verifier_report_sha256": sha256_file(task_dir / "evidence-verifier-report.json"),
148
+ },
149
+ provenance_chain_hash=sha256_file(task_dir / "provenance.json"),
150
+ )
151
+ smoke_statement_file = temp / "smoke-statement.json"
152
+ smoke_statement_file.write_text(json.dumps(task_statement, indent=2) + "\n", encoding="utf-8")
153
+ smoke_bundle_file = temp / "smoke-attestation.json"
154
+ smoke_built = run([sys.executable, str(BUILDER), "build", "--statement", str(smoke_statement_file), "--private-key", str(key_file), "--key-id", key_id, "--output", str(smoke_bundle_file)])
155
+ assert smoke_built.returncode == 0, smoke_built.stderr
156
+ quality_gate = run([
157
+ sys.executable,
158
+ str(ROOT / "scripts" / "quality-gate.py"),
159
+ "--scene", "browser-review-smoke",
160
+ "--context", str(task_dir / "project-context.json"),
161
+ "--task-dir", str(task_dir),
162
+ "--require-attestation",
163
+ "--attestation", str(smoke_bundle_file),
164
+ "--trust-policy", str(policy_file),
165
+ ])
166
+ assert quality_gate.returncode == 0, quality_gate.stdout + quality_gate.stderr
167
+ print("attestation contract tests: PASS")
168
+ return 0
169
+
170
+
171
+ if __name__ == "__main__":
172
+ raise SystemExit(main())