motionloom 2.6.1 → 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 +2 -2
- package/CHANGELOG.md +42 -0
- package/CONTRIBUTING.md +3 -1
- package/README.md +19 -3
- package/SECURITY.md +5 -5
- package/SKILL.md +6 -3
- package/agent-card.json +18 -4
- package/agent-surfaces.json +1 -1
- package/artifact-adapter-registry.json +568 -20
- package/bin/motionloom.mjs +21 -4
- 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.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 +14 -1
- package/package.json +15 -1
- package/references/multi-frame-asset-generation.md +37 -2
- 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 +24 -1
- 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 +35 -5
- package/scripts/frame-set-preflight.py +52 -2
- 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_generation_plan.py +250 -0
- package/tests/scripts/test_attestation.py +24 -8
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.
|