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.
- package/CHANGELOG.md +24 -1
- package/README.md +78 -11
- package/ROADMAP.md +8 -5
- package/SKILL.md +24 -5
- package/agent-card.json +31 -5
- package/agent-surfaces.json +1 -1
- package/artifact-adapter-registry.json +53 -0
- package/bin/motionloom.mjs +29 -5
- package/docs/CHECKLIST.md +16 -0
- package/docs/STATUS.md +2 -2
- package/docs/audits/data/deep-stress-latest.json +65 -65
- package/docs/releases/2.4.0.md +31 -0
- package/docs/releases/npm-publish-from-workstation.md +19 -8
- package/docs/research/agent-skill-ecosystem-notes.md +47 -0
- package/docs/research/ai-animation-tools-2026-notes.md +81 -0
- package/docs/research/ai-animation-tools-2026-report.md +168 -0
- package/docs/research/ai-pilot-ingest-notes.md +29 -0
- package/examples/agent-consumer/ai-pilot-scout/CHATGPT-HANDOFF.md +70 -0
- package/examples/agent-consumer/ai-pilot-scout/CODEX-HANDOFF.md +80 -0
- package/examples/agent-consumer/ai-pilot-scout/README.md +39 -0
- package/examples/agent-consumer/ai-pilot-scout/chatgpt-geometry-correction-prompt.md +55 -0
- package/examples/agent-consumer/ai-pilot-scout/chatgpt-pose-generation-prompt.md +78 -0
- package/examples/agent-consumer/ai-pilot-scout/partial-handoff.json +73 -0
- package/examples/agent-consumer/artifact-intake/hero-motion-controls.json +15 -0
- package/examples/agent-consumer/artifact-intake/hero-motion-export.json +14 -0
- package/examples/agent-consumer/artifact-intake/hero-motion-provenance.json +20 -0
- package/examples/agent-consumer/artifact-intake/hero-motion-receipt.json +16 -0
- package/examples/agent-consumer/asset-consistency/assets/forest-back.png +0 -0
- package/examples/agent-consumer/asset-consistency/assets/forest-front.png +0 -0
- package/examples/agent-consumer/asset-consistency/assets/forest-mid.png +0 -0
- package/examples/agent-consumer/asset-consistency/assets/hero-atlas.png +0 -0
- package/examples/agent-consumer/asset-consistency/assets/hero-frame-00.png +0 -0
- package/examples/agent-consumer/asset-consistency/assets/hero-frame-01.png +0 -0
- package/examples/agent-consumer/asset-consistency/assets/hero-frame-02.png +0 -0
- package/examples/agent-consumer/asset-consistency/assets/hero-frame-03.png +0 -0
- package/examples/agent-consumer/asset-consistency/forest-layered-map.json +53 -0
- package/examples/agent-consumer/asset-consistency/hero-atlas-contract.json +24 -0
- package/examples/agent-consumer/asset-consistency/hero-identity.json +51 -0
- package/examples/agent-consumer/asset-consistency/hero-walk-action-set.json +27 -0
- package/examples/agent-consumer/asset-consistency/hero-walk-frame-geometry.json +63 -0
- package/examples/agent-consumer/rig-compatibility/hero-walk-fixture-rig.json +13 -0
- package/examples/agent-consumer/rive-package-gate/README.md +20 -0
- package/examples/agent-consumer/runtime-candidate/hero-walk-candidate.json +18 -0
- package/examples/agent-consumer/runtime-pilot/action-set.json +19 -0
- package/examples/agent-consumer/runtime-pilot/asset-identity.json +44 -0
- package/examples/agent-consumer/runtime-pilot/candidate.json +21 -0
- package/examples/agent-consumer/runtime-pilot/controls.json +35 -0
- package/examples/agent-consumer/runtime-pilot/export.json +17 -0
- package/examples/agent-consumer/runtime-pilot/provenance.json +38 -0
- package/examples/agent-consumer/runtime-pilot/receipt.json +30 -0
- package/package.json +56 -4
- package/references/browser-review-contract.md +7 -1
- package/references/intelligence-core.md +22 -1
- package/rig-adapter-registry.json +39 -0
- package/schemas/action-set.schema.json +42 -0
- package/schemas/artifact-adapter-registry.schema.json +16 -0
- package/schemas/asset-identity.schema.json +117 -0
- package/schemas/asset-provenance.schema.json +1 -1
- package/schemas/atlas-contract.schema.json +48 -0
- package/schemas/control-track.schema.json +18 -0
- package/schemas/export-manifest.schema.json +19 -0
- package/schemas/frame-geometry.schema.json +79 -0
- package/schemas/generation-receipt.schema.json +20 -0
- package/schemas/layered-map.schema.json +75 -0
- package/schemas/rig-adapter-registry.schema.json +14 -0
- package/schemas/rig-compatibility.schema.json +53 -0
- package/schemas/rive-package-manifest.schema.json +43 -0
- package/schemas/runtime-candidate.schema.json +48 -0
- package/schemas/scene-manifest.schema.json +18 -0
- package/scripts/artifact-intake.py +408 -0
- package/scripts/asset-consistency.py +517 -0
- package/scripts/asset-provenance.py +8 -3
- package/scripts/build-ai-pilot.py +504 -0
- package/scripts/isolate-alpha-background.py +147 -0
- package/scripts/quality-gate.py +88 -2
- package/scripts/report.py +85 -0
- package/scripts/resolve-task-bundle.py +84 -0
- package/scripts/review-hook.py +26 -4
- package/scripts/rig-compatibility.py +251 -0
- package/scripts/rive-package-gate.py +245 -0
- package/scripts/runtime-candidate.py +301 -0
- package/scripts/setup.mjs +54 -26
- package/src/output/runtime-pilot-framer/asset-provenance.json +38 -0
- package/src/output/runtime-pilot-framer/browser-review.json +20 -0
- package/src/output/runtime-pilot-framer/framer-motion/runtime-telemetry.json +103 -0
- package/src/output/runtime-pilot-framer/manifest.json +31 -0
- package/src/output/runtime-pilot-framer/motion-spec.json +25 -0
- package/src/output/runtime-pilot-framer/runtime-evidence.json +96 -0
- package/src/output/runtime-pilot-framer/runtime-telemetry.json +100 -0
- package/src/output/runtime-pilot-framer/scene.jsx +52 -0
- package/src/output/runtime-pilot-framer/snapshot/.render-meta.json +9 -0
- package/src/output/runtime-pilot-framer/snapshot/frame-00.png +0 -0
- package/src/output/runtime-pilot-framer/snapshot/frame-100.png +0 -0
- package/src/output/runtime-pilot-framer/snapshot/frame-50.png +0 -0
- package/tests/scripts/run_tests.py +107 -0
- package/tests/scripts/test_ai_pilot_builder.py +128 -0
- package/tests/scripts/test_alpha_isolation.py +60 -0
- package/tests/scripts/test_artifact_intake.py +146 -0
- package/tests/scripts/test_asset_consistency.py +199 -0
- package/tests/scripts/test_rig_compatibility.py +105 -0
- package/tests/scripts/test_rive_package_gate.py +101 -0
- 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
|
+
}
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -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())
|