motionloom 2.6.0 → 2.7.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/AGENTS.md +3 -1
- package/CHANGELOG.md +68 -0
- package/CONTRIBUTING.md +3 -1
- package/README.md +20 -4
- package/SECURITY.md +5 -5
- package/SKILL.md +15 -5
- package/agent-card.json +77 -14
- package/agent-surfaces.json +30 -7
- package/artifact-adapter-registry.json +568 -20
- package/bin/motionloom.mjs +25 -2
- package/capability-registry.json +58 -234
- package/dev-lab/public/devlab.js +36 -3
- package/dev-lab/public/index.html +6 -2
- package/docs/ACTION-SEPARATION.md +104 -0
- package/docs/ASSET-GENERATION-PLANNER.md +101 -0
- package/docs/BRANCH-PROTECTION.md +44 -0
- package/docs/EXTERNAL-CORPUS.md +26 -0
- package/docs/STATUS.md +3 -2
- package/docs/audits/field-test-after-hardening-2026-08-21.md +25 -0
- package/docs/releases/2.6.1.md +57 -0
- package/docs/releases/2.7.0.md +98 -0
- package/examples/agent-consumer/asset-consistency/action-sequence/envelopes/walk.00.json +32 -0
- package/examples/agent-consumer/asset-consistency/action-sequence/envelopes/walk.01.json +32 -0
- package/examples/agent-consumer/asset-consistency/action-sequence/envelopes/walk.02.json +32 -0
- package/examples/agent-consumer/asset-consistency/action-sequence/envelopes/walk.03.json +32 -0
- package/examples/agent-consumer/asset-consistency/action-sequence/hero-walk-action-manifest.json +81 -0
- package/examples/agent-consumer/asset-consistency/action-sequence/verifier-evidence/walk.00.json +26 -0
- package/examples/agent-consumer/asset-consistency/action-sequence/verifier-evidence/walk.01.json +26 -0
- package/examples/agent-consumer/asset-consistency/action-sequence/verifier-evidence/walk.02.json +26 -0
- package/examples/agent-consumer/asset-consistency/action-sequence/verifier-evidence/walk.03.json +26 -0
- package/examples/agent-consumer/asset-planning/pixellab-hero-256x448-request.json +43 -0
- package/examples/agent-consumer/devlab-live-sprite/README.md +30 -0
- package/examples/agent-consumer/devlab-live-sprite/devlab-runtime.json +61 -0
- package/examples/agent-consumer/devlab-live-sprite/frames/idle-00.png +0 -0
- package/examples/agent-consumer/devlab-live-sprite/frames/idle-01.png +0 -0
- package/examples/agent-consumer/devlab-live-sprite/frames/idle-02.png +0 -0
- package/examples/agent-consumer/devlab-live-sprite/frames/reverse-00.png +0 -0
- package/examples/agent-consumer/devlab-live-sprite/frames/reverse-01.png +0 -0
- package/examples/agent-consumer/devlab-live-sprite/frames/reverse-02.png +0 -0
- package/examples/agent-consumer/frame-generation-lock/hero-walk-lock.json +97 -0
- package/package.json +23 -9
- package/references/multi-frame-asset-generation.md +144 -0
- package/schemas/action-separation-verifier-evidence.schema.json +43 -0
- package/schemas/action-sequence-manifest.schema.json +48 -0
- package/schemas/artifact-adapter-registry.schema.json +1 -1
- package/schemas/asset-adaptation.schema.json +21 -0
- package/schemas/asset-generation-plan.schema.json +77 -0
- package/schemas/asset-generation-request.schema.json +104 -0
- package/schemas/frame-envelope.schema.json +62 -0
- package/schemas/frame-generation-lock.schema.json +189 -0
- package/scripts/action-separation.py +410 -0
- package/scripts/asset-adapt.mjs +92 -0
- package/scripts/asset-generation-plan.py +613 -0
- package/scripts/browser_review_consistency.py +64 -0
- package/scripts/fetch-project-corpus.py +91 -0
- package/scripts/frame-generation-lock.py +358 -0
- package/scripts/frame-set-preflight.py +264 -0
- package/scripts/package-consumer-smoke.mjs +20 -0
- package/scripts/quality-gate.py +7 -0
- package/scripts/release-verify.py +25 -0
- package/scripts/report-contract.py +1 -1
- package/scripts/report.py +19 -4
- package/scripts/resolve-task-bundle.py +11 -3
- package/scripts/review-hook.py +51 -2
- package/scripts/skill-doctor.py +42 -0
- package/src/output/browser-review-smoke/browser-review.json +6 -6
- package/tests/scripts/run_tests.py +45 -2
- package/tests/scripts/test_asset_adapt.py +47 -0
- package/tests/scripts/test_asset_consistency.py +77 -4
- package/tests/scripts/test_asset_generation_plan.py +250 -0
- package/tests/scripts/test_attestation.py +24 -8
package/bin/motionloom.mjs
CHANGED
|
@@ -40,6 +40,10 @@ const PYTHON_COMMANDS = {
|
|
|
40
40
|
"remediation-learning": "scripts/remediation-learning.py",
|
|
41
41
|
"asset-provenance": "scripts/asset-provenance.py",
|
|
42
42
|
"asset-consistency": "scripts/asset-consistency.py",
|
|
43
|
+
"frame-generation-lock": "scripts/frame-generation-lock.py",
|
|
44
|
+
"frame-set-preflight": "scripts/frame-set-preflight.py",
|
|
45
|
+
"action-separation": "scripts/action-separation.py",
|
|
46
|
+
"asset-generation-plan": "scripts/asset-generation-plan.py",
|
|
43
47
|
"artifact-intake": "scripts/artifact-intake.py",
|
|
44
48
|
"runtime-candidate": "scripts/runtime-candidate.py",
|
|
45
49
|
"rig-compatibility": "scripts/rig-compatibility.py",
|
|
@@ -48,6 +52,10 @@ const PYTHON_COMMANDS = {
|
|
|
48
52
|
"alpha-isolate": "scripts/isolate-alpha-background.py",
|
|
49
53
|
};
|
|
50
54
|
|
|
55
|
+
const DIRECT_NODE_COMMANDS = {
|
|
56
|
+
"asset-adapt": "scripts/asset-adapt.mjs",
|
|
57
|
+
};
|
|
58
|
+
|
|
51
59
|
const NODE_COMMANDS = {
|
|
52
60
|
init: "scripts/setup.mjs",
|
|
53
61
|
setup: "scripts/setup.mjs",
|
|
@@ -69,6 +77,7 @@ Start here:
|
|
|
69
77
|
init One safe project setup; no animation gate runs
|
|
70
78
|
status Short read-only readiness report
|
|
71
79
|
doctor Validate the installed Skill package
|
|
80
|
+
doctor --runtime Also verify the Playwright Chromium executable
|
|
72
81
|
|
|
73
82
|
Use when an animation task needs it:
|
|
74
83
|
analyze Run project analysis and refresh Project Memory
|
|
@@ -92,6 +101,11 @@ Use when an animation task needs it:
|
|
|
92
101
|
remediation-learning Record or summarize user-confirmed remediation and benchmark history
|
|
93
102
|
asset-provenance Validate, classify or report asset origin and production readiness
|
|
94
103
|
asset-consistency Validate frame geometry, atlas contamination and layered-map contracts
|
|
104
|
+
frame-generation-lock Validate or compose locked per-frame generation instructions
|
|
105
|
+
frame-set-preflight Fail closed on shared canvases, scale drift, frame contamination and action manifest binding
|
|
106
|
+
action-separation Validate action-scoped frame manifests and independent competitor-action evidence
|
|
107
|
+
asset-generation-plan Produce a MotionLoom project assessment, ranked routes and explicit canvas/frame guidance without invoking APIs
|
|
108
|
+
asset-adapt Apply deterministic transparent padding/integer upscale with a hash-bound report
|
|
95
109
|
artifact-intake Bind generation controls, provenance, adapter metadata and exported bytes
|
|
96
110
|
runtime-candidate Bind intake exports to consistency contracts before runtime testing
|
|
97
111
|
rig-compatibility Validate rig bones, sockets, actions, events and runtime adapter evidence
|
|
@@ -108,12 +122,19 @@ Cross-platform examples:
|
|
|
108
122
|
motionloom memory record-decision --project-root . --id ui-easing \\
|
|
109
123
|
--status accepted --summary "Use ease-out for UI entry" --user-confirmed
|
|
110
124
|
motionloom discovery check --root . --json
|
|
125
|
+
motionloom doctor --runtime --json
|
|
111
126
|
motionloom discovery install-matrix --root . --json
|
|
112
127
|
motionloom capability card --format json
|
|
113
128
|
motionloom visual-truth validate --root . --input src/output/<scene>/visual-truth.json
|
|
114
129
|
motionloom remediation-learning summary --history artifacts/remediation-history.jsonl --json
|
|
115
130
|
motionloom asset-provenance check --input <asset-provenance.json> --root <scene-dir> --mode runtime --json
|
|
116
131
|
motionloom asset-consistency validate --kind frame-geometry --input <frame-geometry.json> --root <scene-dir> --json
|
|
132
|
+
motionloom frame-generation-lock compose --input <frame-generation-lock.json> --root <asset-root> --frame-id walk.00 --json
|
|
133
|
+
motionloom frame-set-preflight --input <frame-geometry.json> --root <asset-root> --action-manifest <action-sequence.json> --json
|
|
134
|
+
motionloom action-separation validate --input <action-sequence.json> --root <asset-root> --json
|
|
135
|
+
motionloom asset-generation-plan plan --request <asset-generation-request.json> --project-root .
|
|
136
|
+
motionloom asset-generation-plan plan --request <asset-generation-request.json> --project-root . --json --strict
|
|
137
|
+
motionloom asset-adapt pad --input source.png --output target.png --width 256 --height 448 --anchor footline --report adaptation.json --json
|
|
117
138
|
motionloom artifact-intake intake --root <asset-dir> --registry artifact-adapter-registry.json \\
|
|
118
139
|
--receipt <generation-receipt.json> --controls <control-track.json> --export-manifest <export-manifest.json> --json
|
|
119
140
|
motionloom alpha-isolate <opaque.png> <isolated.png> --report <alpha-report.json>
|
|
@@ -132,7 +153,7 @@ if (!command || command === "help" || command === "--help" || command === "-h")
|
|
|
132
153
|
}
|
|
133
154
|
|
|
134
155
|
const alias = COMMAND_ALIASES[command];
|
|
135
|
-
const script = alias?.script || NODE_COMMANDS[command] || PYTHON_COMMANDS[command];
|
|
156
|
+
const script = alias?.script || DIRECT_NODE_COMMANDS[command] || NODE_COMMANDS[command] || PYTHON_COMMANDS[command];
|
|
136
157
|
if (!script) {
|
|
137
158
|
console.error(`Unknown MotionLoom command: ${command}`);
|
|
138
159
|
printHelp();
|
|
@@ -142,7 +163,9 @@ if (!script) {
|
|
|
142
163
|
const executable = script.endsWith(".mjs") ? process.execPath : PYTHON;
|
|
143
164
|
const delegatedArgs = alias
|
|
144
165
|
? [...alias.args, ...args]
|
|
145
|
-
:
|
|
166
|
+
: DIRECT_NODE_COMMANDS[command]
|
|
167
|
+
? args
|
|
168
|
+
: NODE_COMMANDS[command] && !["setup", "init"].includes(command)
|
|
146
169
|
? [command, ...args]
|
|
147
170
|
: command === "init"
|
|
148
171
|
? ["init", ...args]
|
package/capability-registry.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"schema_version": "0.1",
|
|
3
|
-
"registry_id": "registry-motionloom-2.
|
|
4
|
-
"generated_at": "2026-08-
|
|
3
|
+
"registry_id": "registry-motionloom-2.7.0",
|
|
4
|
+
"generated_at": "2026-08-22T06:55:00Z",
|
|
5
5
|
"selection_policy": {
|
|
6
6
|
"require_verified": true,
|
|
7
7
|
"allow_scaffold_only": false,
|
|
@@ -12,35 +12,13 @@
|
|
|
12
12
|
"id": "runtime.lottie-json",
|
|
13
13
|
"kind": "renderer",
|
|
14
14
|
"status": "verified",
|
|
15
|
-
"adapter_version": "2.
|
|
16
|
-
"inputs": [
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
],
|
|
21
|
-
"
|
|
22
|
-
"runtime-evidence"
|
|
23
|
-
],
|
|
24
|
-
"compatibility": {
|
|
25
|
-
"browsers": [
|
|
26
|
-
"chromium"
|
|
27
|
-
],
|
|
28
|
-
"os": [
|
|
29
|
-
"linux"
|
|
30
|
-
],
|
|
31
|
-
"node": ">=22"
|
|
32
|
-
},
|
|
33
|
-
"last_verified_at": "2026-08-17T23:50:53Z",
|
|
34
|
-
"evidence": [
|
|
35
|
-
{
|
|
36
|
-
"path": "scripts/runtime-adapters.mjs",
|
|
37
|
-
"sha256": "6f135e75e700a7bb105903db59445fcd3577669b595082a2b542db639f5fe56d",
|
|
38
|
-
"kind": "ci"
|
|
39
|
-
}
|
|
40
|
-
],
|
|
41
|
-
"limitations": [
|
|
42
|
-
"Capability evidence is refreshed by runtime:test."
|
|
43
|
-
],
|
|
15
|
+
"adapter_version": "2.7.0",
|
|
16
|
+
"inputs": ["project-context", "motion-spec", "source-binding"],
|
|
17
|
+
"outputs": ["runtime-evidence"],
|
|
18
|
+
"compatibility": {"browsers": ["chromium"], "os": ["linux"], "node": ">=22"},
|
|
19
|
+
"last_verified_at": "2026-08-21T18:23:59Z",
|
|
20
|
+
"evidence": [{"path": "scripts/runtime-adapters.mjs", "sha256": "6f135e75e700a7bb105903db59445fcd3577669b595082a2b542db639f5fe56d", "kind": "ci"}],
|
|
21
|
+
"limitations": ["Capability evidence is refreshed by runtime:test."],
|
|
44
22
|
"fallback": "",
|
|
45
23
|
"risk_level": "low",
|
|
46
24
|
"side_effect_level": "local_write"
|
|
@@ -49,35 +27,13 @@
|
|
|
49
27
|
"id": "runtime.dotlottie-package",
|
|
50
28
|
"kind": "packager",
|
|
51
29
|
"status": "verified",
|
|
52
|
-
"adapter_version": "2.
|
|
53
|
-
"inputs": [
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
],
|
|
58
|
-
"
|
|
59
|
-
"runtime-evidence"
|
|
60
|
-
],
|
|
61
|
-
"compatibility": {
|
|
62
|
-
"browsers": [
|
|
63
|
-
"chromium"
|
|
64
|
-
],
|
|
65
|
-
"os": [
|
|
66
|
-
"linux"
|
|
67
|
-
],
|
|
68
|
-
"node": ">=22"
|
|
69
|
-
},
|
|
70
|
-
"last_verified_at": "2026-08-17T23:50:53Z",
|
|
71
|
-
"evidence": [
|
|
72
|
-
{
|
|
73
|
-
"path": "scripts/runtime-adapters.mjs",
|
|
74
|
-
"sha256": "6f135e75e700a7bb105903db59445fcd3577669b595082a2b542db639f5fe56d",
|
|
75
|
-
"kind": "ci"
|
|
76
|
-
}
|
|
77
|
-
],
|
|
78
|
-
"limitations": [
|
|
79
|
-
"Capability evidence is refreshed by runtime:test."
|
|
80
|
-
],
|
|
30
|
+
"adapter_version": "2.7.0",
|
|
31
|
+
"inputs": ["project-context", "motion-spec", "source-binding"],
|
|
32
|
+
"outputs": ["runtime-evidence"],
|
|
33
|
+
"compatibility": {"browsers": ["chromium"], "os": ["linux"], "node": ">=22"},
|
|
34
|
+
"last_verified_at": "2026-08-21T18:23:59Z",
|
|
35
|
+
"evidence": [{"path": "scripts/runtime-adapters.mjs", "sha256": "6f135e75e700a7bb105903db59445fcd3577669b595082a2b542db639f5fe56d", "kind": "ci"}],
|
|
36
|
+
"limitations": ["Capability evidence is refreshed by runtime:test."],
|
|
81
37
|
"fallback": "",
|
|
82
38
|
"risk_level": "low",
|
|
83
39
|
"side_effect_level": "local_write"
|
|
@@ -86,35 +42,13 @@
|
|
|
86
42
|
"id": "runtime.svg-cutout-rig",
|
|
87
43
|
"kind": "renderer",
|
|
88
44
|
"status": "verified",
|
|
89
|
-
"adapter_version": "2.
|
|
90
|
-
"inputs": [
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
],
|
|
95
|
-
"
|
|
96
|
-
"runtime-evidence"
|
|
97
|
-
],
|
|
98
|
-
"compatibility": {
|
|
99
|
-
"browsers": [
|
|
100
|
-
"chromium"
|
|
101
|
-
],
|
|
102
|
-
"os": [
|
|
103
|
-
"linux"
|
|
104
|
-
],
|
|
105
|
-
"node": ">=22"
|
|
106
|
-
},
|
|
107
|
-
"last_verified_at": "2026-08-17T23:50:53Z",
|
|
108
|
-
"evidence": [
|
|
109
|
-
{
|
|
110
|
-
"path": "scripts/runtime-adapters.mjs",
|
|
111
|
-
"sha256": "6f135e75e700a7bb105903db59445fcd3577669b595082a2b542db639f5fe56d",
|
|
112
|
-
"kind": "ci"
|
|
113
|
-
}
|
|
114
|
-
],
|
|
115
|
-
"limitations": [
|
|
116
|
-
"Capability evidence is refreshed by runtime:test."
|
|
117
|
-
],
|
|
45
|
+
"adapter_version": "2.7.0",
|
|
46
|
+
"inputs": ["project-context", "motion-spec", "source-binding"],
|
|
47
|
+
"outputs": ["runtime-evidence"],
|
|
48
|
+
"compatibility": {"browsers": ["chromium"], "os": ["linux"], "node": ">=22"},
|
|
49
|
+
"last_verified_at": "2026-08-21T18:23:59Z",
|
|
50
|
+
"evidence": [{"path": "scripts/runtime-adapters.mjs", "sha256": "6f135e75e700a7bb105903db59445fcd3577669b595082a2b542db639f5fe56d", "kind": "ci"}],
|
|
51
|
+
"limitations": ["Capability evidence is refreshed by runtime:test."],
|
|
118
52
|
"fallback": "",
|
|
119
53
|
"risk_level": "low",
|
|
120
54
|
"side_effect_level": "local_write"
|
|
@@ -123,35 +57,13 @@
|
|
|
123
57
|
"id": "runtime.rive",
|
|
124
58
|
"kind": "runtime-adapter",
|
|
125
59
|
"status": "verified",
|
|
126
|
-
"adapter_version": "2.
|
|
127
|
-
"inputs": [
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
],
|
|
132
|
-
"
|
|
133
|
-
"runtime-evidence"
|
|
134
|
-
],
|
|
135
|
-
"compatibility": {
|
|
136
|
-
"browsers": [
|
|
137
|
-
"chromium"
|
|
138
|
-
],
|
|
139
|
-
"os": [
|
|
140
|
-
"linux"
|
|
141
|
-
],
|
|
142
|
-
"node": ">=22"
|
|
143
|
-
},
|
|
144
|
-
"last_verified_at": "2026-08-17T23:50:53Z",
|
|
145
|
-
"evidence": [
|
|
146
|
-
{
|
|
147
|
-
"path": "scripts/runtime-adapters.mjs",
|
|
148
|
-
"sha256": "6f135e75e700a7bb105903db59445fcd3577669b595082a2b542db639f5fe56d",
|
|
149
|
-
"kind": "ci"
|
|
150
|
-
}
|
|
151
|
-
],
|
|
152
|
-
"limitations": [
|
|
153
|
-
"Capability evidence is refreshed by runtime:test."
|
|
154
|
-
],
|
|
60
|
+
"adapter_version": "2.7.0",
|
|
61
|
+
"inputs": ["project-context", "motion-spec", "source-binding"],
|
|
62
|
+
"outputs": ["runtime-evidence"],
|
|
63
|
+
"compatibility": {"browsers": ["chromium"], "os": ["linux"], "node": ">=22"},
|
|
64
|
+
"last_verified_at": "2026-08-21T18:23:59Z",
|
|
65
|
+
"evidence": [{"path": "scripts/runtime-adapters.mjs", "sha256": "6f135e75e700a7bb105903db59445fcd3577669b595082a2b542db639f5fe56d", "kind": "ci"}],
|
|
66
|
+
"limitations": ["Capability evidence is refreshed by runtime:test."],
|
|
155
67
|
"fallback": "",
|
|
156
68
|
"risk_level": "low",
|
|
157
69
|
"side_effect_level": "local_write"
|
|
@@ -160,35 +72,13 @@
|
|
|
160
72
|
"id": "runtime.gsap",
|
|
161
73
|
"kind": "runtime-adapter",
|
|
162
74
|
"status": "verified",
|
|
163
|
-
"adapter_version": "2.
|
|
164
|
-
"inputs": [
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
],
|
|
169
|
-
"
|
|
170
|
-
"runtime-evidence"
|
|
171
|
-
],
|
|
172
|
-
"compatibility": {
|
|
173
|
-
"browsers": [
|
|
174
|
-
"chromium"
|
|
175
|
-
],
|
|
176
|
-
"os": [
|
|
177
|
-
"linux"
|
|
178
|
-
],
|
|
179
|
-
"node": ">=22"
|
|
180
|
-
},
|
|
181
|
-
"last_verified_at": "2026-08-17T23:50:53Z",
|
|
182
|
-
"evidence": [
|
|
183
|
-
{
|
|
184
|
-
"path": "scripts/runtime-adapters.mjs",
|
|
185
|
-
"sha256": "6f135e75e700a7bb105903db59445fcd3577669b595082a2b542db639f5fe56d",
|
|
186
|
-
"kind": "ci"
|
|
187
|
-
}
|
|
188
|
-
],
|
|
189
|
-
"limitations": [
|
|
190
|
-
"Capability evidence is refreshed by runtime:test."
|
|
191
|
-
],
|
|
75
|
+
"adapter_version": "2.7.0",
|
|
76
|
+
"inputs": ["project-context", "motion-spec", "source-binding"],
|
|
77
|
+
"outputs": ["runtime-evidence"],
|
|
78
|
+
"compatibility": {"browsers": ["chromium"], "os": ["linux"], "node": ">=22"},
|
|
79
|
+
"last_verified_at": "2026-08-21T18:23:59Z",
|
|
80
|
+
"evidence": [{"path": "scripts/runtime-adapters.mjs", "sha256": "6f135e75e700a7bb105903db59445fcd3577669b595082a2b542db639f5fe56d", "kind": "ci"}],
|
|
81
|
+
"limitations": ["Capability evidence is refreshed by runtime:test."],
|
|
192
82
|
"fallback": "",
|
|
193
83
|
"risk_level": "low",
|
|
194
84
|
"side_effect_level": "local_write"
|
|
@@ -197,35 +87,13 @@
|
|
|
197
87
|
"id": "runtime.framer-motion",
|
|
198
88
|
"kind": "runtime-adapter",
|
|
199
89
|
"status": "verified",
|
|
200
|
-
"adapter_version": "2.
|
|
201
|
-
"inputs": [
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
],
|
|
206
|
-
"
|
|
207
|
-
"runtime-evidence"
|
|
208
|
-
],
|
|
209
|
-
"compatibility": {
|
|
210
|
-
"browsers": [
|
|
211
|
-
"chromium"
|
|
212
|
-
],
|
|
213
|
-
"os": [
|
|
214
|
-
"linux"
|
|
215
|
-
],
|
|
216
|
-
"node": ">=22"
|
|
217
|
-
},
|
|
218
|
-
"last_verified_at": "2026-08-17T23:50:53Z",
|
|
219
|
-
"evidence": [
|
|
220
|
-
{
|
|
221
|
-
"path": "scripts/runtime-adapters.mjs",
|
|
222
|
-
"sha256": "6f135e75e700a7bb105903db59445fcd3577669b595082a2b542db639f5fe56d",
|
|
223
|
-
"kind": "ci"
|
|
224
|
-
}
|
|
225
|
-
],
|
|
226
|
-
"limitations": [
|
|
227
|
-
"Capability evidence is refreshed by runtime:test."
|
|
228
|
-
],
|
|
90
|
+
"adapter_version": "2.7.0",
|
|
91
|
+
"inputs": ["project-context", "motion-spec", "source-binding"],
|
|
92
|
+
"outputs": ["runtime-evidence"],
|
|
93
|
+
"compatibility": {"browsers": ["chromium"], "os": ["linux"], "node": ">=22"},
|
|
94
|
+
"last_verified_at": "2026-08-21T18:23:59Z",
|
|
95
|
+
"evidence": [{"path": "scripts/runtime-adapters.mjs", "sha256": "6f135e75e700a7bb105903db59445fcd3577669b595082a2b542db639f5fe56d", "kind": "ci"}],
|
|
96
|
+
"limitations": ["Capability evidence is refreshed by runtime:test."],
|
|
229
97
|
"fallback": "",
|
|
230
98
|
"risk_level": "low",
|
|
231
99
|
"side_effect_level": "local_write"
|
|
@@ -234,35 +102,13 @@
|
|
|
234
102
|
"id": "runtime.spine",
|
|
235
103
|
"kind": "renderer",
|
|
236
104
|
"status": "scaffold_only",
|
|
237
|
-
"adapter_version": "2.
|
|
238
|
-
"inputs": [
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
],
|
|
243
|
-
"
|
|
244
|
-
"runtime-evidence"
|
|
245
|
-
],
|
|
246
|
-
"compatibility": {
|
|
247
|
-
"browsers": [
|
|
248
|
-
"chromium"
|
|
249
|
-
],
|
|
250
|
-
"os": [
|
|
251
|
-
"linux"
|
|
252
|
-
],
|
|
253
|
-
"node": ">=22"
|
|
254
|
-
},
|
|
255
|
-
"last_verified_at": "2026-08-17T23:50:53Z",
|
|
256
|
-
"evidence": [
|
|
257
|
-
{
|
|
258
|
-
"path": "scripts/runtime-adapters.mjs",
|
|
259
|
-
"sha256": "6f135e75e700a7bb105903db59445fcd3577669b595082a2b542db639f5fe56d",
|
|
260
|
-
"kind": "ci"
|
|
261
|
-
}
|
|
262
|
-
],
|
|
263
|
-
"limitations": [
|
|
264
|
-
"Scaffold only; do not use for production acceptance."
|
|
265
|
-
],
|
|
105
|
+
"adapter_version": "2.7.0",
|
|
106
|
+
"inputs": ["project-context", "motion-spec", "source-binding"],
|
|
107
|
+
"outputs": ["runtime-evidence"],
|
|
108
|
+
"compatibility": {"browsers": ["chromium"], "os": ["linux"], "node": ">=22"},
|
|
109
|
+
"last_verified_at": "2026-08-21T18:23:59Z",
|
|
110
|
+
"evidence": [{"path": "scripts/runtime-adapters.mjs", "sha256": "6f135e75e700a7bb105903db59445fcd3577669b595082a2b542db639f5fe56d", "kind": "ci"}],
|
|
111
|
+
"limitations": ["Scaffold only; do not use for production acceptance."],
|
|
266
112
|
"fallback": "runtime.lottie-json",
|
|
267
113
|
"risk_level": "high",
|
|
268
114
|
"side_effect_level": "local_write"
|
|
@@ -271,35 +117,13 @@
|
|
|
271
117
|
"id": "runtime.threejs",
|
|
272
118
|
"kind": "renderer",
|
|
273
119
|
"status": "scaffold_only",
|
|
274
|
-
"adapter_version": "2.
|
|
275
|
-
"inputs": [
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
],
|
|
280
|
-
"
|
|
281
|
-
"runtime-evidence"
|
|
282
|
-
],
|
|
283
|
-
"compatibility": {
|
|
284
|
-
"browsers": [
|
|
285
|
-
"chromium"
|
|
286
|
-
],
|
|
287
|
-
"os": [
|
|
288
|
-
"linux"
|
|
289
|
-
],
|
|
290
|
-
"node": ">=22"
|
|
291
|
-
},
|
|
292
|
-
"last_verified_at": "2026-08-17T23:50:53Z",
|
|
293
|
-
"evidence": [
|
|
294
|
-
{
|
|
295
|
-
"path": "scripts/runtime-adapters.mjs",
|
|
296
|
-
"sha256": "6f135e75e700a7bb105903db59445fcd3577669b595082a2b542db639f5fe56d",
|
|
297
|
-
"kind": "ci"
|
|
298
|
-
}
|
|
299
|
-
],
|
|
300
|
-
"limitations": [
|
|
301
|
-
"Scaffold only; do not use for production acceptance."
|
|
302
|
-
],
|
|
120
|
+
"adapter_version": "2.7.0",
|
|
121
|
+
"inputs": ["project-context", "motion-spec", "source-binding"],
|
|
122
|
+
"outputs": ["runtime-evidence"],
|
|
123
|
+
"compatibility": {"browsers": ["chromium"], "os": ["linux"], "node": ">=22"},
|
|
124
|
+
"last_verified_at": "2026-08-21T18:23:59Z",
|
|
125
|
+
"evidence": [{"path": "scripts/runtime-adapters.mjs", "sha256": "6f135e75e700a7bb105903db59445fcd3577669b595082a2b542db639f5fe56d", "kind": "ci"}],
|
|
126
|
+
"limitations": ["Scaffold only; do not use for production acceptance."],
|
|
303
127
|
"fallback": "runtime.lottie-json",
|
|
304
128
|
"risk_level": "high",
|
|
305
129
|
"side_effect_level": "local_write"
|
package/dev-lab/public/devlab.js
CHANGED
|
@@ -9,6 +9,7 @@ let manifest;
|
|
|
9
9
|
let spec;
|
|
10
10
|
let candidate;
|
|
11
11
|
let runtimeDescriptor = null;
|
|
12
|
+
let actionSeparation = null;
|
|
12
13
|
let review;
|
|
13
14
|
let driver;
|
|
14
15
|
let currentAnimationId = null;
|
|
@@ -117,6 +118,17 @@ function validateRuntimeDescriptor(value) {
|
|
|
117
118
|
return value;
|
|
118
119
|
}
|
|
119
120
|
|
|
121
|
+
function validateActionSeparation(value) {
|
|
122
|
+
if (!value) return null;
|
|
123
|
+
if (typeof value !== "object") throw new Error("Action-separation evidence must be an object");
|
|
124
|
+
if (!/^[A-Za-z0-9][A-Za-z0-9._-]{0,95}$/.test(value.action_id || "")) throw new Error("Action-separation evidence requires a safe action_id");
|
|
125
|
+
if (!new Set(["pass", "quarantined"]).has(value.status)) throw new Error("Action-separation evidence has an invalid status");
|
|
126
|
+
if (!Number.isInteger(value.frame_count) || value.frame_count < 1) throw new Error("Action-separation evidence requires a positive frame_count");
|
|
127
|
+
if (!Number.isInteger(value.passing_frame_count) || value.passing_frame_count < 0 || value.passing_frame_count > value.frame_count) throw new Error("Action-separation passing_frame_count is invalid");
|
|
128
|
+
if (!Array.isArray(value.forbidden_action_ids)) throw new Error("Action-separation evidence requires forbidden_action_ids");
|
|
129
|
+
return value;
|
|
130
|
+
}
|
|
131
|
+
|
|
120
132
|
function controlEnabled(name, capability = true) {
|
|
121
133
|
if (!runtimeDescriptor) return false;
|
|
122
134
|
return runtimeDescriptor.controls?.[name] !== false && capability !== false;
|
|
@@ -386,7 +398,8 @@ function currentReview(decision) {
|
|
|
386
398
|
mode: runtimeDescriptor ? "live-runtime" : "captured-evidence",
|
|
387
399
|
descriptor_sha256: candidate.runtime_review?.bundle_sha256 || null,
|
|
388
400
|
selected_animation: currentAnimationId,
|
|
389
|
-
state
|
|
401
|
+
state,
|
|
402
|
+
action_separation: actionSeparation
|
|
390
403
|
},
|
|
391
404
|
spec: { framework: spec.framework, category: spec.category, context_binding: spec.context_binding },
|
|
392
405
|
candidate: { source_sha256: candidate.source_sha256, context_sha256: candidate.context_sha256 }
|
|
@@ -422,6 +435,20 @@ function renderMetadata() {
|
|
|
422
435
|
}));
|
|
423
436
|
}
|
|
424
437
|
|
|
438
|
+
function renderActionSeparation() {
|
|
439
|
+
const node = $("action-separation");
|
|
440
|
+
if (!node) return;
|
|
441
|
+
if (!actionSeparation) {
|
|
442
|
+
node.className = "progress-note";
|
|
443
|
+
node.textContent = "No action-separation evidence bound; legacy review compatibility mode.";
|
|
444
|
+
return;
|
|
445
|
+
}
|
|
446
|
+
const pass = actionSeparation.status === "pass";
|
|
447
|
+
node.className = `progress-note ${pass ? "pass" : "quarantine"}`;
|
|
448
|
+
const forbidden = actionSeparation.forbidden_action_ids.join(", ") || "none";
|
|
449
|
+
node.textContent = `${pass ? "PASS" : "QUARANTINED"} · expected ${actionSeparation.action_id} · ${actionSeparation.passing_frame_count}/${actionSeparation.frame_count} frames separated · competitors: ${forbidden}`;
|
|
450
|
+
}
|
|
451
|
+
|
|
425
452
|
function renderChecks() {
|
|
426
453
|
const nodes = (manifest.checks || []).map((check) => {
|
|
427
454
|
const label = document.createElement("label"); label.className = "check";
|
|
@@ -474,12 +501,17 @@ function updateReviewGate() {
|
|
|
474
501
|
const checks = currentChecks();
|
|
475
502
|
const checksPass = checks.length > 0 && checks.every((item) => item.pass);
|
|
476
503
|
const coverage = reviewCoverageComplete();
|
|
504
|
+
const actionGate = !actionSeparation || actionSeparation.status === "pass";
|
|
477
505
|
const liveRuntimeBlocked = candidate?.runtime_review?.live === true && driver instanceof SnapshotDriver;
|
|
478
|
-
$("confirm").disabled = !(checksPass && coverage && !liveRuntimeBlocked);
|
|
506
|
+
$("confirm").disabled = !(checksPass && coverage && actionGate && !liveRuntimeBlocked);
|
|
479
507
|
if (liveRuntimeBlocked) {
|
|
480
508
|
setStatus("warn", "LIVE RUNTIME UNAVAILABLE · approval is blocked; captured evidence remains inspectable and changes may still be requested.");
|
|
481
509
|
return;
|
|
482
510
|
}
|
|
511
|
+
if (!actionGate) {
|
|
512
|
+
setStatus("warn", "ACTION SEPARATION QUARANTINED · regenerate or independently verify the ambiguous frame before approval.");
|
|
513
|
+
return;
|
|
514
|
+
}
|
|
483
515
|
const missing = requiredAnimationIds().filter((id) => !inspectedAnimations.has(id));
|
|
484
516
|
if (!coverage && missing.length) setStatus("info", `Inspect required animations before approval: ${missing.join(", ")}`);
|
|
485
517
|
}
|
|
@@ -669,6 +701,7 @@ async function load() {
|
|
|
669
701
|
]);
|
|
670
702
|
validateCandidate();
|
|
671
703
|
runtimeDescriptor = validateRuntimeDescriptor(runtimeDescriptor);
|
|
704
|
+
actionSeparation = validateActionSeparation(runtimeDescriptor?.action_separation || null);
|
|
672
705
|
const expectedLiveRuntimeMissing = candidate.runtime_review?.live === true && !runtimeDescriptor;
|
|
673
706
|
|
|
674
707
|
$("title").textContent = `${manifest.name || scene}`;
|
|
@@ -680,7 +713,7 @@ async function load() {
|
|
|
680
713
|
const saved = JSON.parse(localStorage.getItem(`devlab:${taskId}:${candidateId}`) || "null");
|
|
681
714
|
review = saved || { checks: (manifest.checks || []).map((check) => ({ id: check.id, pass: false })), notes: "", decision: "pending", animations_inspected: [] };
|
|
682
715
|
for (const id of review.animations_inspected || []) inspectedAnimations.add(id);
|
|
683
|
-
renderChecks(); $("notes").value = review.notes || ""; expose(review);
|
|
716
|
+
renderActionSeparation(); renderChecks(); $("notes").value = review.notes || ""; expose(review);
|
|
684
717
|
wireControls(); applyViewportOverlays(); setZoom(1);
|
|
685
718
|
|
|
686
719
|
if (expectedLiveRuntimeMissing) {
|
|
@@ -93,6 +93,8 @@
|
|
|
93
93
|
#status { margin-top:10px; font:11px/1.4 ui-monospace,monospace; }
|
|
94
94
|
.ok { color:var(--good); }.warn { color:var(--bad); }.info { color:var(--blue); }
|
|
95
95
|
.progress-note { margin-top:9px; color:var(--muted); font:11px ui-monospace,monospace; }
|
|
96
|
+
#action-separation.pass { color:var(--good); }
|
|
97
|
+
#action-separation.quarantine { color:var(--bad); }
|
|
96
98
|
@media (max-width:1050px) { .app { grid-template-columns:180px minmax(0,1fr) 285px; } }
|
|
97
99
|
@media (max-width:820px) { header { height:auto; min-height:58px; flex-wrap:wrap; padding:10px 12px; } .app { height:auto; min-height:0; grid-template-columns:1fr; } nav { border-right:0; border-bottom:1px solid var(--line); } #animations { flex-direction:row; overflow:auto; } .animation-btn { width:auto; min-width:130px; } aside { border-left:0; border-top:1px solid var(--line); } .workbench { min-height:620px; } }
|
|
98
100
|
</style>
|
|
@@ -150,8 +152,10 @@
|
|
|
150
152
|
<aside>
|
|
151
153
|
<h1 class="inspector-title" id="title">Scene</h1><p id="description">Loading manifest and candidate binding.</p>
|
|
152
154
|
<div class="meta-grid" id="meta"></div>
|
|
153
|
-
|
|
154
|
-
<h2>
|
|
155
|
+
<h2>Runtime inspector</h2><pre class="runtime-state" id="runtime-state">Waiting for runtime…</pre>
|
|
156
|
+
<h2>Action separation</h2><div class="progress-note" id="action-separation">No action-separation evidence bound.</div>
|
|
157
|
+
<h2>Quality checklist</h2>
|
|
158
|
+
<div id="checks"></div>
|
|
155
159
|
<h2>Fix notes</h2><textarea id="notes" placeholder="Describe a precise fix request, timing issue, visual defect, or confirmation…"></textarea>
|
|
156
160
|
<div class="review-actions"><button class="danger" id="request-changes" type="button">Request changes</button><button class="primary" id="confirm" type="button">Approve candidate</button><button id="reset" type="button">Reset review</button></div>
|
|
157
161
|
<div id="status"></div>
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
# Action-scoped isolated frame pipeline
|
|
2
|
+
|
|
3
|
+
MotionLoom keeps generated source frames isolated: one source PNG per frame, no pose sheet as production input, no shared-canvas crop and no post-generation resize. Isolation prevents pixel contamination, but it does not by itself prove that a frame belongs to the intended action. This document adds the action-level contract that closes that gap.
|
|
4
|
+
|
|
5
|
+
## Model
|
|
6
|
+
|
|
7
|
+
A frame belongs to an immutable `sequence_id` and `action_id`. The sequence manifest declares the expected action, explicitly forbidden competitor actions, the ordered frame set, the identity-lock hash and one envelope for every image. A verifier result is evidence only; it cannot grant approval or move a file.
|
|
8
|
+
|
|
9
|
+
The lock and manifest deliberately use two separate layers. Verifier identity is not the same thing as verifier provenance: a different `verifier_id` alone does not prove that a separate verifier ran.
|
|
10
|
+
|
|
11
|
+
| Layer | Source of truth | Purpose |
|
|
12
|
+
|---|---|---|
|
|
13
|
+
| Generation lock | `frame-generation-lock.schema.json` / `frame-generation-lock.py` | Compose one isolated request per frame with shared identity, geometry and action cues |
|
|
14
|
+
| Action manifest | `action-sequence-manifest.schema.json` / `action-separation.py` | Bind frame order, image bytes, envelope metadata and competitor-action separation |
|
|
15
|
+
| Geometry preflight | `frame-set-preflight.py` | Measure actual PNG geometry and optionally require the action manifest to pass |
|
|
16
|
+
| Human review | Dev Lab | Inspect the live sequence and see whether action evidence is pass or quarantined |
|
|
17
|
+
|
|
18
|
+
## CLI
|
|
19
|
+
|
|
20
|
+
Validate the action manifest directly:
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
motionloom action-separation validate \
|
|
24
|
+
--input examples/agent-consumer/asset-consistency/action-sequence/hero-walk-action-manifest.json \
|
|
25
|
+
--root examples/agent-consumer/asset-consistency --json
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
Run geometry and action checks together:
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
motionloom frame-set-preflight \
|
|
32
|
+
--input examples/agent-consumer/asset-consistency/hero-walk-frame-geometry.json \
|
|
33
|
+
--root examples/agent-consumer/asset-consistency \
|
|
34
|
+
--action-manifest examples/agent-consumer/asset-consistency/action-sequence/hero-walk-action-manifest.json \
|
|
35
|
+
--json
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
The equivalent npm scripts are `npm run action:separation` and `npm run frame:preflight`. To create one envelope from a manifest and a real PNG, use:
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
motionloom action-separation envelope \
|
|
42
|
+
--manifest examples/agent-consumer/asset-consistency/action-sequence/hero-walk-action-manifest.json \
|
|
43
|
+
--root examples/agent-consumer/asset-consistency \
|
|
44
|
+
--frame-id walk.00 --expected-action walk --top-competitor run \
|
|
45
|
+
--margin 0.42 --threshold 0.20 \
|
|
46
|
+
--output examples/agent-consumer/asset-consistency/action-sequence/envelopes/walk.00.json --json
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
The `envelope` command is generator-side convenience only: it emits `verification_mode: declared`, keeps the result `quarantined` and returns non-zero even when the supplied numbers meet the rubric. A separate verifier process/agent must create the evidence artifact and a review workflow must bind it into an `independently_bound` envelope. The validator checks those bindings, but cryptographic hashes prove artifact integrity and provenance claims, not the truthfulness of a claimed producer identity.
|
|
50
|
+
|
|
51
|
+
## Frame envelope
|
|
52
|
+
|
|
53
|
+
Every manifest frame points to an envelope. The envelope binds the frame to the exact sequence, action, frame index, identity lock and image bytes. The verifier must provide an expected action, a top competitor, a numeric confidence margin and a declared threshold. Schema 0.2 also classifies the result as `declared` or `independently_bound`. A `declared` result may describe a check, but it is never accepted as independent proof. An `independently_bound` result must reference a separate verifier artifact whose bytes, canonical result, producer provenance and frame binding all match the envelope. A margin below threshold is quarantined; it is not silently assigned to the nearest action.
|
|
54
|
+
|
|
55
|
+
```json
|
|
56
|
+
{
|
|
57
|
+
"sequence_id": "hero-walk-v1",
|
|
58
|
+
"action_id": "walk",
|
|
59
|
+
"frame_id": "walk.01",
|
|
60
|
+
"frame_index": 1,
|
|
61
|
+
"image": "assets/hero-frame-01.png",
|
|
62
|
+
"image_sha256": "<sha256>",
|
|
63
|
+
"identity_lock_sha256": "<lock-sha256>",
|
|
64
|
+
"verifier": {
|
|
65
|
+
"verifier_id": "motionloom-separation-verifier-v1",
|
|
66
|
+
"verification_mode": "independently_bound",
|
|
67
|
+
"expected_action": "walk",
|
|
68
|
+
"top_competitor": "run",
|
|
69
|
+
"margin": 0.42,
|
|
70
|
+
"threshold": 0.20,
|
|
71
|
+
"status": "pass",
|
|
72
|
+
"method": "independent-action-rubric-v1",
|
|
73
|
+
"evidence": {
|
|
74
|
+
"artifact": "action-sequence/verifier-evidence/walk.01.json",
|
|
75
|
+
"artifact_sha256": "<sha256>",
|
|
76
|
+
"result_sha256": "<sha256>",
|
|
77
|
+
"provenance": {
|
|
78
|
+
"kind": "separate_verifier_artifact",
|
|
79
|
+
"producer_id": "motionloom-independent-verifier-v1",
|
|
80
|
+
"run_id": "verifier-run-2026-08-22"
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
},
|
|
84
|
+
"approval": false
|
|
85
|
+
}
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
The validator rejects duplicate images, non-contiguous frame indexes, path escapes, mismatched envelope fields, stale image hashes, missing/tampered verifier provenance, verifier-generator collisions, undeclared competitors, low margins and any verifier status other than `pass`. It never repairs, renames, relocates or approves an asset.
|
|
89
|
+
|
|
90
|
+
## Generation lock 0.2
|
|
91
|
+
|
|
92
|
+
`frame-generation-lock` accepts legacy schema `0.1` and enhanced schema `0.2`. A `0.2` lock requires `sequence_id`, `forbidden_action_ids` and `action_contract.positive_cues`/`negative_cues`. `compose` and `compose-all` include those cues in every provider-facing instruction while preserving the one-image-per-frame rule.
|
|
93
|
+
|
|
94
|
+
The canonical example is [`examples/agent-consumer/frame-generation-lock/hero-walk-lock.json`](../examples/agent-consumer/frame-generation-lock/hero-walk-lock.json). The action manifest and envelopes are under [`examples/agent-consumer/asset-consistency/action-sequence/`](../examples/agent-consumer/asset-consistency/action-sequence/).
|
|
95
|
+
|
|
96
|
+
## Status and approval boundary
|
|
97
|
+
|
|
98
|
+
The manifest may be `review_required` or `quarantined`; it must never claim approval. A passing verifier means that the evidence is sufficiently separated for the next gate, not that the user approved the action. Dev Lab still requires animation coverage and explicit checklist confirmation before enabling Approve.
|
|
99
|
+
|
|
100
|
+
For live runtime fixtures, the optional `action_separation` summary in `devlab-runtime.json` is displayed in Dev Lab. A quarantined summary disables approval and instructs the reviewer to regenerate or independently verify the ambiguous frame. Contact sheets remain review projections only; production packing must use the manifest-approved isolated images.
|
|
101
|
+
|
|
102
|
+
## Adversarial coverage
|
|
103
|
+
|
|
104
|
+
The regression suite covers a valid four-frame sequence with independently bound evidence and rejects cross-action envelopes, verifier-generator collisions, missing provenance, modified verifier results, modified evidence hashes, low-confidence margins, wrong expected actions, undeclared competitors, quarantined evidence, reused images and approval assertions. The canonical fixture also runs through geometry preflight, so a sequence can only pass when both the measured PNG contract and the action-scoped evidence pass.
|