motionloom 2.3.0 → 2.4.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 (102) hide show
  1. package/CHANGELOG.md +24 -1
  2. package/README.md +78 -11
  3. package/ROADMAP.md +8 -5
  4. package/SKILL.md +24 -5
  5. package/agent-card.json +31 -5
  6. package/agent-surfaces.json +1 -1
  7. package/artifact-adapter-registry.json +53 -0
  8. package/bin/motionloom.mjs +29 -5
  9. package/docs/CHECKLIST.md +16 -0
  10. package/docs/STATUS.md +2 -2
  11. package/docs/audits/data/deep-stress-latest.json +65 -65
  12. package/docs/releases/2.4.0.md +31 -0
  13. package/docs/releases/npm-publish-from-workstation.md +19 -8
  14. package/docs/research/agent-skill-ecosystem-notes.md +47 -0
  15. package/docs/research/ai-animation-tools-2026-notes.md +81 -0
  16. package/docs/research/ai-animation-tools-2026-report.md +168 -0
  17. package/docs/research/ai-pilot-ingest-notes.md +29 -0
  18. package/examples/agent-consumer/ai-pilot-scout/CHATGPT-HANDOFF.md +70 -0
  19. package/examples/agent-consumer/ai-pilot-scout/CODEX-HANDOFF.md +80 -0
  20. package/examples/agent-consumer/ai-pilot-scout/README.md +39 -0
  21. package/examples/agent-consumer/ai-pilot-scout/chatgpt-geometry-correction-prompt.md +55 -0
  22. package/examples/agent-consumer/ai-pilot-scout/chatgpt-pose-generation-prompt.md +78 -0
  23. package/examples/agent-consumer/ai-pilot-scout/partial-handoff.json +73 -0
  24. package/examples/agent-consumer/artifact-intake/hero-motion-controls.json +15 -0
  25. package/examples/agent-consumer/artifact-intake/hero-motion-export.json +14 -0
  26. package/examples/agent-consumer/artifact-intake/hero-motion-provenance.json +20 -0
  27. package/examples/agent-consumer/artifact-intake/hero-motion-receipt.json +16 -0
  28. package/examples/agent-consumer/asset-consistency/assets/forest-back.png +0 -0
  29. package/examples/agent-consumer/asset-consistency/assets/forest-front.png +0 -0
  30. package/examples/agent-consumer/asset-consistency/assets/forest-mid.png +0 -0
  31. package/examples/agent-consumer/asset-consistency/assets/hero-atlas.png +0 -0
  32. package/examples/agent-consumer/asset-consistency/assets/hero-frame-00.png +0 -0
  33. package/examples/agent-consumer/asset-consistency/assets/hero-frame-01.png +0 -0
  34. package/examples/agent-consumer/asset-consistency/assets/hero-frame-02.png +0 -0
  35. package/examples/agent-consumer/asset-consistency/assets/hero-frame-03.png +0 -0
  36. package/examples/agent-consumer/asset-consistency/forest-layered-map.json +53 -0
  37. package/examples/agent-consumer/asset-consistency/hero-atlas-contract.json +24 -0
  38. package/examples/agent-consumer/asset-consistency/hero-identity.json +51 -0
  39. package/examples/agent-consumer/asset-consistency/hero-walk-action-set.json +27 -0
  40. package/examples/agent-consumer/asset-consistency/hero-walk-frame-geometry.json +63 -0
  41. package/examples/agent-consumer/rig-compatibility/hero-walk-fixture-rig.json +13 -0
  42. package/examples/agent-consumer/rive-package-gate/README.md +20 -0
  43. package/examples/agent-consumer/runtime-candidate/hero-walk-candidate.json +18 -0
  44. package/examples/agent-consumer/runtime-pilot/action-set.json +19 -0
  45. package/examples/agent-consumer/runtime-pilot/asset-identity.json +44 -0
  46. package/examples/agent-consumer/runtime-pilot/candidate.json +21 -0
  47. package/examples/agent-consumer/runtime-pilot/controls.json +35 -0
  48. package/examples/agent-consumer/runtime-pilot/export.json +17 -0
  49. package/examples/agent-consumer/runtime-pilot/provenance.json +38 -0
  50. package/examples/agent-consumer/runtime-pilot/receipt.json +30 -0
  51. package/package.json +56 -4
  52. package/references/browser-review-contract.md +7 -1
  53. package/references/intelligence-core.md +22 -1
  54. package/rig-adapter-registry.json +39 -0
  55. package/schemas/action-set.schema.json +42 -0
  56. package/schemas/artifact-adapter-registry.schema.json +16 -0
  57. package/schemas/asset-identity.schema.json +117 -0
  58. package/schemas/asset-provenance.schema.json +1 -1
  59. package/schemas/atlas-contract.schema.json +48 -0
  60. package/schemas/control-track.schema.json +18 -0
  61. package/schemas/export-manifest.schema.json +19 -0
  62. package/schemas/frame-geometry.schema.json +79 -0
  63. package/schemas/generation-receipt.schema.json +20 -0
  64. package/schemas/layered-map.schema.json +75 -0
  65. package/schemas/rig-adapter-registry.schema.json +14 -0
  66. package/schemas/rig-compatibility.schema.json +53 -0
  67. package/schemas/rive-package-manifest.schema.json +43 -0
  68. package/schemas/runtime-candidate.schema.json +48 -0
  69. package/schemas/scene-manifest.schema.json +18 -0
  70. package/scripts/artifact-intake.py +408 -0
  71. package/scripts/asset-consistency.py +517 -0
  72. package/scripts/asset-provenance.py +8 -3
  73. package/scripts/build-ai-pilot.py +504 -0
  74. package/scripts/isolate-alpha-background.py +147 -0
  75. package/scripts/quality-gate.py +88 -2
  76. package/scripts/report.py +85 -0
  77. package/scripts/resolve-task-bundle.py +84 -0
  78. package/scripts/review-hook.py +26 -4
  79. package/scripts/rig-compatibility.py +251 -0
  80. package/scripts/rive-package-gate.py +245 -0
  81. package/scripts/runtime-candidate.py +301 -0
  82. package/scripts/setup.mjs +54 -26
  83. package/src/output/runtime-pilot-framer/asset-provenance.json +38 -0
  84. package/src/output/runtime-pilot-framer/browser-review.json +20 -0
  85. package/src/output/runtime-pilot-framer/framer-motion/runtime-telemetry.json +103 -0
  86. package/src/output/runtime-pilot-framer/manifest.json +31 -0
  87. package/src/output/runtime-pilot-framer/motion-spec.json +25 -0
  88. package/src/output/runtime-pilot-framer/runtime-evidence.json +96 -0
  89. package/src/output/runtime-pilot-framer/runtime-telemetry.json +100 -0
  90. package/src/output/runtime-pilot-framer/scene.jsx +52 -0
  91. package/src/output/runtime-pilot-framer/snapshot/.render-meta.json +9 -0
  92. package/src/output/runtime-pilot-framer/snapshot/frame-00.png +0 -0
  93. package/src/output/runtime-pilot-framer/snapshot/frame-100.png +0 -0
  94. package/src/output/runtime-pilot-framer/snapshot/frame-50.png +0 -0
  95. package/tests/scripts/run_tests.py +107 -0
  96. package/tests/scripts/test_ai_pilot_builder.py +128 -0
  97. package/tests/scripts/test_alpha_isolation.py +60 -0
  98. package/tests/scripts/test_artifact_intake.py +146 -0
  99. package/tests/scripts/test_asset_consistency.py +199 -0
  100. package/tests/scripts/test_rig_compatibility.py +105 -0
  101. package/tests/scripts/test_rive_package_gate.py +101 -0
  102. package/tests/scripts/test_runtime_candidate.py +95 -0
@@ -0,0 +1,25 @@
1
+ {
2
+ "version": "1.0",
3
+ "scene": "runtime-pilot-framer",
4
+ "task_id": "runtime-pilot-001",
5
+ "category": "runtime-pilot",
6
+ "framework": "framer-motion",
7
+ "duration_s": 1.2,
8
+ "fps": 60,
9
+ "total_frames": 72,
10
+ "loop": true,
11
+ "easing": "piecewise scrubbed transform",
12
+ "camera": null,
13
+ "theme": {
14
+ "primary": "#2563EB",
15
+ "accent": "#F59E0B"
16
+ },
17
+ "performance": {
18
+ "max_file_kb": 300,
19
+ "max_layers": 8
20
+ },
21
+ "source_binding": "hash-bound code-authored scene.jsx; no generated visual asset is asserted",
22
+ "accessibility": {
23
+ "reduced_motion": "manual review uses discrete checkpoints"
24
+ }
25
+ }
@@ -0,0 +1,96 @@
1
+ {
2
+ "schema_version": "1.1",
3
+ "run_id": "1786808870894-145958",
4
+ "generated_at": "2026-08-15T15:47:52.510Z",
5
+ "mode": "runtime",
6
+ "harness": "tests/runtime-harness",
7
+ "status": "pass",
8
+ "frameworks": [
9
+ {
10
+ "run_id": "1786808870894-145958",
11
+ "framework": "framer-motion",
12
+ "url": "http://127.0.0.1:4179/tests/runtime-harness/index.html?framework=framer-motion",
13
+ "status": "pass",
14
+ "ready": true,
15
+ "runtime": "framer-motion@13.1.0",
16
+ "frames": [
17
+ {
18
+ "percent": 0,
19
+ "file": "artifacts/runtime-adapters/framer-motion/frame-00.png",
20
+ "state": {
21
+ "progress": 0,
22
+ "transform": "matrix(0.82, 0, 0, 0.82, 0, 0)",
23
+ "opacity": "0.28",
24
+ "contract": {
25
+ "framework": "framer-motion",
26
+ "checkpoints": [
27
+ 0,
28
+ 50,
29
+ 100
30
+ ],
31
+ "durationSeconds": 1.2,
32
+ "loop": true,
33
+ "sourceAuthority": "code_authored"
34
+ }
35
+ }
36
+ },
37
+ {
38
+ "percent": 50,
39
+ "file": "artifacts/runtime-adapters/framer-motion/frame-50.png",
40
+ "state": {
41
+ "progress": 0.5,
42
+ "transform": "matrix(1.02851, -0.256437, 0.256437, 1.02851, 176, -48)",
43
+ "opacity": "1",
44
+ "contract": {
45
+ "framework": "framer-motion",
46
+ "checkpoints": [
47
+ 0,
48
+ 50,
49
+ 100
50
+ ],
51
+ "durationSeconds": 1.2,
52
+ "loop": true,
53
+ "sourceAuthority": "code_authored"
54
+ }
55
+ }
56
+ },
57
+ {
58
+ "percent": 100,
59
+ "file": "artifacts/runtime-adapters/framer-motion/frame-100.png",
60
+ "state": {
61
+ "progress": 1,
62
+ "transform": "matrix(0.882948, 0.469472, -0.469472, 0.882948, 240, -36)",
63
+ "opacity": "1",
64
+ "contract": {
65
+ "framework": "framer-motion",
66
+ "checkpoints": [
67
+ 0,
68
+ 50,
69
+ 100
70
+ ],
71
+ "durationSeconds": 1.2,
72
+ "loop": true,
73
+ "sourceAuthority": "code_authored"
74
+ }
75
+ }
76
+ }
77
+ ],
78
+ "console_errors": [],
79
+ "telemetry": {
80
+ "file": "framer-motion/runtime-telemetry.json",
81
+ "sha256": "1c11a49eac8fb6c87977ccb84cccc59162fa015c73424a07690785bac094e773",
82
+ "metrics": {
83
+ "sample_count": 3,
84
+ "raf_interval_count": 9,
85
+ "max_raf_interval_ms": 16.799999999999955,
86
+ "p95_raf_interval_ms": 16.799999999999955
87
+ }
88
+ }
89
+ }
90
+ ],
91
+ "scene": "runtime-pilot-framer",
92
+ "task_id": "runtime-pilot-001",
93
+ "source_sha256": "f4c0e56de842cae6b234566b556e4f07154f8109dde382a474218a9de8b45a8b",
94
+ "manifest_sha256": "2b53bf3b6fc8dfe16a271ecd52ec51c2acb5b612c0b77d9fd4e485696989011d",
95
+ "motion_ir_sha256": "1d339ca2ec9c510e54e53c37ef32c2ddf1485dbaec97c5e9aa178a8f6c9c3287"
96
+ }
@@ -0,0 +1,100 @@
1
+ {
2
+ "schema_version": "1.0",
3
+ "run_id": "1786808553918-145614",
4
+ "generated_at": "2026-08-15T15:42:35.907Z",
5
+ "mode": "runtime-telemetry",
6
+ "task_id": "runtime-pilot-001",
7
+ "scene": "runtime-pilot-framer",
8
+ "framework": "framer-motion",
9
+ "runtime": "framer-motion@13.1.0",
10
+ "samples": [
11
+ {
12
+ "sequence": 0,
13
+ "percent": 0,
14
+ "captured_at_ms": 760.7000000476837,
15
+ "raf_intervals_ms": [
16
+ 16.700000000000045,
17
+ 16.600000000000023,
18
+ 16.699999999999932
19
+ ],
20
+ "state_sha256": "0769bb6867251ff8d951b01e7d817ab4644d1f7d4a38b0e91ad54ec5b0f81f6b",
21
+ "state": {
22
+ "progress": 0,
23
+ "transform": "matrix(0.82, 0, 0, 0.82, 0, 0)",
24
+ "opacity": "0.28",
25
+ "contract": {
26
+ "framework": "framer-motion",
27
+ "checkpoints": [
28
+ 0,
29
+ 50,
30
+ 100
31
+ ],
32
+ "durationSeconds": 1.2,
33
+ "loop": true,
34
+ "sourceAuthority": "code_authored"
35
+ }
36
+ }
37
+ },
38
+ {
39
+ "sequence": 1,
40
+ "percent": 50,
41
+ "captured_at_ms": 844.1000000238419,
42
+ "raf_intervals_ms": [
43
+ 16.600000000000023,
44
+ 16.799999999999955,
45
+ 16.600000000000023
46
+ ],
47
+ "state_sha256": "015ad2af517be79a3cf102b48b5f61282acd1843c7b5efe20472c2752d79c890",
48
+ "state": {
49
+ "progress": 0.5,
50
+ "transform": "matrix(1.02851, -0.256437, 0.256437, 1.02851, 176, -48)",
51
+ "opacity": "1",
52
+ "contract": {
53
+ "framework": "framer-motion",
54
+ "checkpoints": [
55
+ 0,
56
+ 50,
57
+ 100
58
+ ],
59
+ "durationSeconds": 1.2,
60
+ "loop": true,
61
+ "sourceAuthority": "code_authored"
62
+ }
63
+ }
64
+ },
65
+ {
66
+ "sequence": 2,
67
+ "percent": 100,
68
+ "captured_at_ms": 927.7000000476837,
69
+ "raf_intervals_ms": [
70
+ 16.700000000000045,
71
+ 16.699999999999932,
72
+ 16.700000000000045
73
+ ],
74
+ "state_sha256": "275a832573d3c660f6d820256f95412e253f0a29c02e8f535dfdec2c47e56de2",
75
+ "state": {
76
+ "progress": 1,
77
+ "transform": "matrix(0.882948, 0.469472, -0.469472, 0.882948, 240, -36)",
78
+ "opacity": "1",
79
+ "contract": {
80
+ "framework": "framer-motion",
81
+ "checkpoints": [
82
+ 0,
83
+ 50,
84
+ 100
85
+ ],
86
+ "durationSeconds": 1.2,
87
+ "loop": true,
88
+ "sourceAuthority": "code_authored"
89
+ }
90
+ }
91
+ }
92
+ ],
93
+ "metrics": {
94
+ "sample_count": 3,
95
+ "raf_interval_count": 9,
96
+ "max_raf_interval_ms": 16.799999999999955,
97
+ "p95_raf_interval_ms": 16.799999999999955
98
+ },
99
+ "status": "pass"
100
+ }
@@ -0,0 +1,52 @@
1
+ import React, { useEffect, useRef } from "react";
2
+ import { motion, motionValue, useTransform } from "framer-motion";
3
+
4
+ /**
5
+ * MotionLoom runtime-first pilot source.
6
+ * Design: code-authored, deterministic scrub only; review remains human-gated.
7
+ */
8
+ export const RUNTIME_PILOT_CONTRACT = Object.freeze({
9
+ framework: "framer-motion",
10
+ checkpoints: [0, 50, 100],
11
+ durationSeconds: 1.2,
12
+ loop: true,
13
+ sourceAuthority: "code_authored",
14
+ });
15
+
16
+ export function FramerRuntimePilot({ expose }) {
17
+ const progress = useRef(motionValue(0)).current;
18
+ const x = useTransform(progress, [0, 0.5, 1], [0, 176, 240]);
19
+ const y = useTransform(progress, [0, 0.5, 1], [0, -48, -36]);
20
+ const rotate = useTransform(progress, [0, 0.5, 1], [0, -14, 28]);
21
+ const opacity = useTransform(progress, [0, 0.15, 1], [0.28, 1, 1]);
22
+ const scale = useTransform(progress, [0, 0.5, 1], [0.82, 1.06, 1]);
23
+
24
+ useEffect(() => {
25
+ const box = document.querySelector(".runtime-pilot-box");
26
+ const adapter = {
27
+ framework: RUNTIME_PILOT_CONTRACT.framework,
28
+ runtime: "framer-motion@13.1.0",
29
+ status: "ready",
30
+ ready: true,
31
+ setProgress(value) {
32
+ progress.set(Math.max(0, Math.min(1, Number(value))));
33
+ },
34
+ getState() {
35
+ return {
36
+ progress: progress.get(),
37
+ transform: box ? getComputedStyle(box).transform : "",
38
+ opacity: box ? getComputedStyle(box).opacity : "",
39
+ contract: RUNTIME_PILOT_CONTRACT,
40
+ };
41
+ },
42
+ };
43
+ expose(adapter);
44
+ adapter.setProgress(0);
45
+ }, [expose, progress]);
46
+
47
+ return (
48
+ <div className="stage" aria-label="MotionLoom Framer Motion runtime pilot">
49
+ <motion.div className="motion-box runtime-pilot-box" style={{ x, y, rotate, opacity, scale }} />
50
+ </div>
51
+ );
52
+ }
@@ -0,0 +1,9 @@
1
+ {
2
+ "mode": "runtime",
3
+ "scene": "runtime-pilot-framer",
4
+ "framework": "framer-motion",
5
+ "run_id": "1786808553918-145614",
6
+ "source_sha256": "f4c0e56de842cae6b234566b556e4f07154f8109dde382a474218a9de8b45a8b",
7
+ "runtime_evidence": "../runtime-evidence.json",
8
+ "checkpoints": [0, 50, 100]
9
+ }
@@ -359,6 +359,42 @@ def test_deep_audit_contracts():
359
359
  ], capture_output=True, text=True)
360
360
  check("review hook rejects expired candidate", expired.returncode != 0 and "expired" in expired.stdout)
361
361
 
362
+
363
+ def test_task_bundle_resolver_contract():
364
+ with tempfile.TemporaryDirectory() as td:
365
+ root = Path(td)
366
+ opaque_task = root / "artifacts" / "opaque-task-id"
367
+ shutil.copytree(ROOT / "artifacts/runtime-pilot-001", opaque_task)
368
+ resolver = ROOT / "scripts/resolve-task-bundle.py"
369
+ resolved = subprocess.run([
370
+ sys.executable, str(resolver), "--root", str(root), "--scene", "runtime-pilot-framer",
371
+ ], capture_output=True, text=True)
372
+ check(
373
+ "task bundle resolver binds scene to opaque task directory",
374
+ resolved.returncode == 0 and resolved.stdout.strip() == "artifacts/opaque-task-id",
375
+ resolved.stdout.strip() or resolved.stderr.strip(),
376
+ )
377
+
378
+ duplicate = root / "artifacts" / "second-task-id"
379
+ shutil.copytree(opaque_task, duplicate)
380
+ ambiguous = subprocess.run([
381
+ sys.executable, str(resolver), "--root", str(root), "--scene", "runtime-pilot-framer",
382
+ ], capture_output=True, text=True)
383
+ check(
384
+ "task bundle resolver rejects ambiguous scene bindings",
385
+ ambiguous.returncode != 0 and "ambiguous task bundles" in ambiguous.stderr,
386
+ ambiguous.stdout.strip() or ambiguous.stderr.strip(),
387
+ )
388
+
389
+ unsafe = subprocess.run([
390
+ sys.executable, str(resolver), "--root", str(root), "--scene", "../runtime-pilot-framer",
391
+ ], capture_output=True, text=True)
392
+ check(
393
+ "task bundle resolver rejects unsafe scene identifiers",
394
+ unsafe.returncode != 0 and "unsafe scene" in unsafe.stderr,
395
+ unsafe.stdout.strip() or unsafe.stderr.strip(),
396
+ )
397
+
362
398
  unsafe_runtime = subprocess.run(
363
399
  ["node", str(ROOT / "scripts/runtime-adapters.mjs")],
364
400
  env={**os.environ, "RUNTIME_FRAMEWORKS": "../../escape"},
@@ -945,6 +981,76 @@ def test_quality_workflow_rebuilds_replay_after_generated_artifacts():
945
981
  asset_provenance_tests.returncode == 0 and "asset provenance contract tests: PASS" in asset_provenance_tests.stdout,
946
982
  asset_provenance_tests.stdout.strip() or asset_provenance_tests.stderr.strip(),
947
983
  )
984
+ asset_consistency_tests = subprocess.run(
985
+ [sys.executable, str(ROOT / "tests/scripts/test_asset_consistency.py")],
986
+ capture_output=True,
987
+ text=True,
988
+ )
989
+ check(
990
+ "Asset consistency preserves deterministic geometry, atlas and layered-map boundaries",
991
+ asset_consistency_tests.returncode == 0 and "asset consistency contract tests: PASS" in asset_consistency_tests.stdout,
992
+ asset_consistency_tests.stdout.strip() or asset_consistency_tests.stderr.strip(),
993
+ )
994
+ artifact_intake_tests = subprocess.run(
995
+ [sys.executable, str(ROOT / "tests/scripts/test_artifact_intake.py")],
996
+ capture_output=True,
997
+ text=True,
998
+ )
999
+ check(
1000
+ "Artifact intake preserves provider-neutral controls, hash binding and approval boundaries",
1001
+ artifact_intake_tests.returncode == 0 and "artifact intake contract tests: PASS" in artifact_intake_tests.stdout,
1002
+ artifact_intake_tests.stdout.strip() or artifact_intake_tests.stderr.strip(),
1003
+ )
1004
+ runtime_candidate_tests = subprocess.run(
1005
+ [sys.executable, str(ROOT / "tests/scripts/test_runtime_candidate.py")],
1006
+ capture_output=True,
1007
+ text=True,
1008
+ )
1009
+ check(
1010
+ "Runtime candidate bridge binds intake exports, consistency and human review boundaries",
1011
+ runtime_candidate_tests.returncode == 0 and "runtime candidate contract tests: PASS" in runtime_candidate_tests.stdout,
1012
+ runtime_candidate_tests.stdout.strip() or runtime_candidate_tests.stderr.strip(),
1013
+ )
1014
+ rig_compatibility_tests = subprocess.run(
1015
+ [sys.executable, str(ROOT / "tests/scripts/test_rig_compatibility.py")],
1016
+ capture_output=True,
1017
+ text=True,
1018
+ )
1019
+ check(
1020
+ "Rig compatibility preserves skeleton, socket, event, runtime evidence and review boundaries",
1021
+ rig_compatibility_tests.returncode == 0 and "rig compatibility contract tests: PASS" in rig_compatibility_tests.stdout,
1022
+ rig_compatibility_tests.stdout.strip() or rig_compatibility_tests.stderr.strip(),
1023
+ )
1024
+ rive_package_gate_tests = subprocess.run(
1025
+ [sys.executable, str(ROOT / "tests/scripts/test_rive_package_gate.py")],
1026
+ capture_output=True,
1027
+ text=True,
1028
+ )
1029
+ check(
1030
+ "Rive package gate requires authentic package bytes, runtime proof and human review",
1031
+ rive_package_gate_tests.returncode == 0 and "rive package gate contract tests: PASS" in rive_package_gate_tests.stdout,
1032
+ rive_package_gate_tests.stdout.strip() or rive_package_gate_tests.stderr.strip(),
1033
+ )
1034
+ ai_pilot_builder_tests = subprocess.run(
1035
+ [sys.executable, str(ROOT / "tests/scripts/test_ai_pilot_builder.py")],
1036
+ capture_output=True,
1037
+ text=True,
1038
+ )
1039
+ check(
1040
+ "AI pilot builder requires real alpha, hash-binds artifacts and preserves review-first status",
1041
+ ai_pilot_builder_tests.returncode == 0 and "AI pilot builder tests: PASS" in ai_pilot_builder_tests.stdout,
1042
+ ai_pilot_builder_tests.stdout.strip() or ai_pilot_builder_tests.stderr.strip(),
1043
+ )
1044
+ alpha_isolation_tests = subprocess.run(
1045
+ [sys.executable, str(ROOT / "tests/scripts/test_alpha_isolation.py")],
1046
+ capture_output=True,
1047
+ text=True,
1048
+ )
1049
+ check(
1050
+ "Alpha isolation only removes reviewed edge-connected background and rejects residual contamination",
1051
+ alpha_isolation_tests.returncode == 0 and "alpha isolation tests: PASS" in alpha_isolation_tests.stdout,
1052
+ alpha_isolation_tests.stdout.strip() or alpha_isolation_tests.stderr.strip(),
1053
+ )
948
1054
 
949
1055
 
950
1056
  if __name__ == "__main__":
@@ -961,6 +1067,7 @@ if __name__ == "__main__":
961
1067
  test_placeholder_is_not_runtime_evidence()
962
1068
  test_runtime_evidence_binding()
963
1069
  test_deep_audit_contracts()
1070
+ test_task_bundle_resolver_contract()
964
1071
  test_runtime_telemetry_verifier_contract()
965
1072
  test_approved_browser_review_e2e_contract()
966
1073
  test_intelligence_core_contracts()
@@ -0,0 +1,128 @@
1
+ from __future__ import annotations
2
+
3
+ import json
4
+ import shutil
5
+ import struct
6
+ import subprocess
7
+ import sys
8
+ import tempfile
9
+ import zlib
10
+ from pathlib import Path
11
+
12
+
13
+ ROOT = Path(__file__).resolve().parents[2]
14
+ BUILDER = ROOT / "scripts" / "build-ai-pilot.py"
15
+
16
+
17
+ def check(condition: bool, message: str) -> None:
18
+ if not condition:
19
+ raise AssertionError(message)
20
+
21
+
22
+ def write_png(path: Path, alpha: bool, offset: int = 0, contamination: bool = False) -> None:
23
+ """Write one compact, non-interlaced 4x4 PNG without third-party libraries."""
24
+ width = height = 4
25
+ channels = 4 if alpha else 3
26
+ rows = bytearray()
27
+ for y in range(height):
28
+ rows.append(0)
29
+ for x in range(width):
30
+ visible = (x == 1 + offset and y in {1, 2}) or (contamination and x == 3 and y == 1)
31
+ rows.extend((244, 124, 21))
32
+ if alpha:
33
+ rows.append(255 if visible else 0)
34
+ def chunk(kind: bytes, data: bytes) -> bytes:
35
+ return struct.pack(">I", len(data)) + kind + data + struct.pack(">I", zlib.crc32(kind + data) & 0xFFFFFFFF)
36
+ color_type = 6 if alpha else 2
37
+ path.write_bytes(b"\x89PNG\r\n\x1a\n" + chunk(b"IHDR", struct.pack(">IIBBBBB", width, height, 8, color_type, 0, 0, 0)) + chunk(b"IDAT", zlib.compress(bytes(rows))) + chunk(b"IEND", b""))
38
+
39
+
40
+ def build(root: Path, sources: dict[str, Path], output: str, provider: str = "internal-imagegen") -> subprocess.CompletedProcess[str]:
41
+ return subprocess.run(
42
+ [
43
+ sys.executable, str(BUILDER),
44
+ "--idle", str(sources["idle"]),
45
+ "--contact-right", str(sources["contact-right"]),
46
+ "--passing", str(sources["passing"]),
47
+ "--contact-left", str(sources["contact-left"]),
48
+ "--root", str(root),
49
+ "--output", output,
50
+ "--generated-at", "2026-08-15T00:00:00Z",
51
+ "--provider", provider,
52
+ "--overwrite",
53
+ ],
54
+ capture_output=True,
55
+ text=True,
56
+ )
57
+
58
+
59
+ def main() -> int:
60
+ output = ".motionloom/pilots/test-ai-pilot-builder"
61
+ workspace = ROOT / output
62
+ shutil.rmtree(workspace, ignore_errors=True)
63
+ with tempfile.TemporaryDirectory() as td:
64
+ source_dir = Path(td)
65
+ sources: dict[str, Path] = {}
66
+ for index, frame_id in enumerate(("idle", "contact-right", "passing", "contact-left")):
67
+ source = source_dir / f"{frame_id}.png"
68
+ write_png(source, alpha=True, offset=index % 2)
69
+ sources[frame_id] = source
70
+ result = build(ROOT, sources, output)
71
+ check(result.returncode == 0, result.stderr or result.stdout)
72
+ provenance = json.loads((workspace / "provenance.json").read_text())
73
+ candidate = json.loads((workspace / "candidate.json").read_text())
74
+ evidence = json.loads((workspace / "devlab-pilot-evidence.json").read_text())
75
+ check(provenance["authority"] == "ai_generated" and provenance["readiness"] == "runtime_ready", "builder must preserve AI authority/readiness")
76
+ check("human_approval" not in provenance and candidate["runtime"]["review_required"] is True, "builder must not self-approve a candidate")
77
+ check(evidence["state"] == "review_required" and evidence["production_approved"] is False, "Dev Lab evidence must retain review boundary")
78
+ intake = subprocess.run(
79
+ [sys.executable, str(ROOT / "scripts/artifact-intake.py"), "intake", "--root", str(ROOT), "--registry", str(ROOT / "artifact-adapter-registry.json"), "--controls", str(workspace / "controls.json"), "--receipt", str(workspace / "receipt.json"), "--export-manifest", str(workspace / "export.json"), "--json"],
80
+ capture_output=True,
81
+ text=True,
82
+ )
83
+ intake_data = json.loads(intake.stdout)
84
+ check(intake.returncode == 0 and intake_data["status"] == "review_required", intake.stderr or intake.stdout)
85
+ runtime = subprocess.run(
86
+ [sys.executable, str(ROOT / "scripts/runtime-candidate.py"), "validate", "--root", str(ROOT), "--input", str(workspace / "candidate.json"), "--json"],
87
+ capture_output=True,
88
+ text=True,
89
+ )
90
+ runtime_data = json.loads(runtime.stdout)
91
+ check(runtime.returncode == 0 and runtime_data["status"] == "review_required" and runtime_data["runtime_verified"] is False, runtime.stderr or runtime.stdout)
92
+
93
+ chatgpt_output = ".motionloom/pilots/test-ai-pilot-chatgpt"
94
+ chatgpt = build(ROOT, sources, chatgpt_output, provider="chatgpt")
95
+ check(chatgpt.returncode == 0, chatgpt.stderr or chatgpt.stdout)
96
+ chatgpt_workspace = ROOT / chatgpt_output
97
+ chatgpt_provenance = json.loads((chatgpt_workspace / "provenance.json").read_text())
98
+ chatgpt_receipt = json.loads((chatgpt_workspace / "receipt.json").read_text())
99
+ check(chatgpt_provenance["generator"]["source"] == "openai-chatgpt", "ChatGPT import must not be relabelled internal-imagegen")
100
+ check(chatgpt_receipt["provider"]["adapter_id"] == "openai.chatgpt" and chatgpt_receipt["provider"]["invocation_mode"] == "manual", "ChatGPT receipt must preserve user-mediated adapter truth")
101
+
102
+ opaque = source_dir / "opaque.png"
103
+ write_png(opaque, alpha=False)
104
+ opaque_output = ".motionloom/pilots/test-ai-pilot-opaque"
105
+ rejected = build(ROOT, {key: opaque for key in sources}, opaque_output)
106
+ check(rejected.returncode != 0 and "has no alpha channel" in (rejected.stderr + rejected.stdout), "RGB checkered source must be rejected as non-alpha")
107
+ opaque_handoff = json.loads((ROOT / opaque_output / "partial-handoff.json").read_text())
108
+ check(opaque_handoff["state"] == "partial" and opaque_handoff["production_approved"] is False, "early RGB rejection must emit a non-approving partial handoff")
109
+ check(len(opaque_handoff["rejected_source_attempts"][0]["frames"]) == 4, "partial handoff must bind every supplied source hash")
110
+
111
+ contaminated = source_dir / "contaminated.png"
112
+ write_png(contaminated, alpha=True, contamination=True)
113
+ contaminated_output = ".motionloom/pilots/test-ai-pilot-contaminated"
114
+ rejected = build(ROOT, {key: contaminated for key in sources}, contaminated_output, provider="chatgpt")
115
+ check(rejected.returncode != 0 and "clean padding" in (rejected.stderr + rejected.stdout), "detached canvas-edge contamination must be rejected")
116
+ contaminated_handoff = json.loads((ROOT / contaminated_output / "partial-handoff.json").read_text())
117
+ attempt = contaminated_handoff["rejected_source_attempts"][0]
118
+ check(attempt["provider"] == "openai-chatgpt" and attempt["state"] == "rejected_pre_ingest", "partial handoff must preserve ChatGPT provenance")
119
+ shutil.rmtree(workspace, ignore_errors=True)
120
+ shutil.rmtree(ROOT / ".motionloom/pilots/test-ai-pilot-chatgpt", ignore_errors=True)
121
+ shutil.rmtree(ROOT / ".motionloom/pilots/test-ai-pilot-opaque", ignore_errors=True)
122
+ shutil.rmtree(ROOT / ".motionloom/pilots/test-ai-pilot-contaminated", ignore_errors=True)
123
+ print("AI pilot builder tests: PASS")
124
+ return 0
125
+
126
+
127
+ if __name__ == "__main__":
128
+ raise SystemExit(main())
@@ -0,0 +1,60 @@
1
+ from __future__ import annotations
2
+
3
+ import json
4
+ import struct
5
+ import subprocess
6
+ import sys
7
+ import tempfile
8
+ import zlib
9
+ from pathlib import Path
10
+
11
+
12
+ ROOT = Path(__file__).resolve().parents[2]
13
+ ISOLATOR = ROOT / "scripts" / "isolate-alpha-background.py"
14
+
15
+
16
+ def check(condition: bool, message: str) -> None:
17
+ if not condition:
18
+ raise AssertionError(message)
19
+
20
+
21
+ def write_png(path: Path, contamination: bool = False) -> None:
22
+ width = height = 8
23
+ raw = bytearray()
24
+ for y in range(height):
25
+ raw.append(0)
26
+ for x in range(width):
27
+ if 2 <= x <= 5 and 2 <= y <= 5:
28
+ color = (244, 124, 21, 255)
29
+ elif contamination and x == width - 1 and y == 3:
30
+ color = (180, 24, 180, 255)
31
+ else:
32
+ color = (29, 32, 32, 255)
33
+ raw.extend(color)
34
+ def chunk(kind: bytes, content: bytes) -> bytes:
35
+ return struct.pack(">I", len(content)) + kind + content + struct.pack(">I", zlib.crc32(kind + content) & 0xFFFFFFFF)
36
+ payload = b"\x89PNG\r\n\x1a\n" + chunk(b"IHDR", struct.pack(">IIBBBBB", width, height, 8, 6, 0, 0, 0)) + chunk(b"IDAT", zlib.compress(bytes(raw))) + chunk(b"IEND", b"")
37
+ path.write_bytes(payload)
38
+
39
+
40
+ def main() -> int:
41
+ with tempfile.TemporaryDirectory() as td:
42
+ root = Path(td)
43
+ source, output, report = root / "source.png", root / "isolated.png", root / "report.json"
44
+ write_png(source)
45
+ result = subprocess.run([sys.executable, str(ISOLATOR), str(source), str(output), "--report", str(report), "--tolerance", "24"], capture_output=True, text=True)
46
+ check(result.returncode == 0, result.stderr or result.stdout)
47
+ data = json.loads(report.read_text())
48
+ check(data["removed_pixels"] == 48 and data["foreground_alpha_pixels"] == 16, "isolator must remove only the edge-connected flat background")
49
+ check(data["residual_edge_foreground_pixels"] == 0 and data["human_review_required"] is True and data["production_approved"] is False, "isolation must preserve review/approval boundary")
50
+
51
+ contaminated = root / "contaminated.png"
52
+ write_png(contaminated, contamination=True)
53
+ rejected = subprocess.run([sys.executable, str(ISOLATOR), str(contaminated), str(root / "rejected.png"), "--tolerance", "24"], capture_output=True, text=True)
54
+ check(rejected.returncode != 0 and "residual background contamination" in (rejected.stderr + rejected.stdout), "edge contamination must fail closed")
55
+ print("alpha isolation tests: PASS")
56
+ return 0
57
+
58
+
59
+ if __name__ == "__main__":
60
+ raise SystemExit(main())