motionloom 2.5.0 → 2.6.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.
@@ -0,0 +1,21 @@
1
+ # MotionLoom 2.5.1
2
+
3
+ ## Summary
4
+
5
+ MotionLoom 2.5.1 is a hardening patch for the installed npm package, browser review boundary and runtime-output policy. It preserves the 2.5.0 capability surface while making the advertised onboarding, Dev Lab and runtime evidence paths work safely from an isolated consumer project.
6
+
7
+ ## Fixed
8
+
9
+ - The npm CLI preserves the caller's working directory, so default setup and onboarding target the consumer project instead of the installed package directory. The displayed CLI version is read from package metadata.
10
+ - The npm tarball includes the Dev Lab assets, runtime harness, Python requirements and optional browser-runtime dependencies required by its documented commands. Release validation installs the packed tarball into an isolated consumer project.
11
+ - Dev Lab renders manifest values through safe DOM APIs, enforces a same-origin script content-security policy and rejects invalid, expired or terminal browser-review candidates before review can be enabled.
12
+ - Runtime evidence cleanup is constrained to a dedicated child directory below an explicit policy root and cannot remove caller data outside that boundary.
13
+ - The protected GitHub Actions release uses npm Trusted Publishing/OIDC with a compatible pinned npm client instead of a long-lived publish token. npm generates public-package provenance automatically for the trusted workflow.
14
+
15
+ ## Verified behavior
16
+
17
+ The release candidate passes the full regression harness, quality and signed-attestation gate, isolated package-consumer smoke test, Dev Lab browser-security smoke test, real Rive/GSAP/Framer Motion runtime capture at 0/50/100%, a 6,900-case deep stress run, twelve Swift package tests, Skill Doctor, documentation audit, discovery, release verification and package inspection. The pull request carrying the implementation passed all twelve GitHub checks before merge.
18
+
19
+ ## Trust boundary
20
+
21
+ This patch does not add a framework capability, sign or distribute the Apple alpha, grant production approval or authorize a pull request or release from machine-generated evidence. Browser review remains identity-bound and human-governed. npm publication and GitHub Release creation remain explicit maintainer actions after the prepared commit is green on `main`.
@@ -0,0 +1,38 @@
1
+ # MotionLoom 2.6.0
2
+
3
+ ## Summary
4
+
5
+ MotionLoom 2.6.0 turns Dev Lab from a mostly checkpoint-oriented browser reviewer into an interactive runtime review workbench. The release keeps deterministic snapshots as evidence, but adds a live runtime lane for real playback, scalable project-defined action sets, and optional state/transition review flows so a user can inspect behavior much closer to the target runtime before approving a candidate.
6
+
7
+ ## Added
8
+
9
+ - Interactive Dev Lab transport controls for live candidates: play, pause, restart, normalized scrub, frame stepping, playback speed and loop control.
10
+ - Stage inspection tools including fullscreen, zoom/fit, background selection, grid, bounds, baseline and pivot overlays; sprite candidates can opt into crisp pixel-art rendering.
11
+ - Data-driven animation discovery from `devlab-runtime.json`. Action names such as `idle`, `walk`, `run`, `attack`, `jump`, skills, emotes or project-specific clips are not hard-coded into MotionLoom.
12
+ - Scalable Action Library support with project-defined groups/categories, tags, search, collapsible sections and filters for review-required, unreviewed, looping and one-shot actions.
13
+ - Optional hash-bound `devlab-state-machine.json` contract with explicit states, legal transitions, multi-step review sequences and independent transition/sequence review coverage.
14
+ - Portable `select-animation` transitions for sprite/clip runtimes and strict `runtime-trigger` transitions for iframe runtimes through `triggerTransition(request)`.
15
+ - Transition history and review evidence for inspected actions, transitions, sequences and observed runtime state.
16
+
17
+ ## Hardened
18
+
19
+ - Live runtime files, descriptors and state-machine bytes are bound into the browser-review candidate hash when declared; runtime-byte drift invalidates the candidate.
20
+ - Path traversal, absolute paths, symlink escapes, duplicate animation ids, undeclared runtime files and mismatched candidate/runtime animation sets fail closed.
21
+ - A declared live runtime that cannot load is shown as `LIVE RUNTIME UNAVAILABLE`; captured snapshots remain inspectable for diagnosis but approval is blocked.
22
+ - `runtime-trigger` transitions are counted only after the runtime reports the declared target state. MotionLoom never silently downgrades an unsupported runtime trigger into a clip switch.
23
+ - Search, grouping and filters affect presentation only. Hidden required actions or transitions still count as unreviewed.
24
+ - `runtime-bridge.js` is included in the npm package allowlist so installed-package iframe/state-machine review does not depend on source-checkout-only files.
25
+
26
+ ## Verified behavior
27
+
28
+ The Dev Lab CI harness covers deterministic snapshots, live sprite playback, iframe runtime bridging, a four-action interactive dogfood set, a twelve-action Action Library fixture, sprite state transitions, real iframe runtime-trigger transitions and browser-security smoke tests. The state/transition fixtures exercise flows such as `Idle -> Run -> Attack -> Hurt -> Idle` and verify that completing runtime coverage does not create a user approval decision.
29
+
30
+ The protected release workflow reruns documentation/package validation, Skill Doctor, the full regression harness, isolated installed-package consumer smoke and release traceability before npm publication.
31
+
32
+ ## Trust boundary
33
+
34
+ MotionLoom 2.6.0 does not turn runtime success into visual approval, production approval or PR authorization. Playback controls, action coverage, transition coverage, runtime state, hashes, snapshots, telemetry and automated sequence completion are evidence only. `approved` remains an explicit user decision bound to the exact browser-review candidate.
35
+
36
+ AI-generated or otherwise non-authoritative source assets retain their declared provenance and authority tier. Dev Lab review does not promote `ai_generated` material to `artist_authored`, `production_eligible` or `production_approved`.
37
+
38
+ The native macOS/iOS companion remains an unsigned source alpha. npm publication and GitHub Release creation remain explicit maintainer release actions through the protected GitHub Actions workflow.
@@ -1,6 +1,6 @@
1
1
  # Publish MotionLoom to npm from your workstation
2
2
 
3
- This guide publishes the prepared `motionloom@2.5.0` package from your own computer. The npm password, authenticator code and access token must stay on your computer; never paste them into chat, GitHub issues or repository files.
3
+ This guide publishes the prepared `motionloom@2.5.1` package from your own computer. The npm password, authenticator code and access token must stay on your computer; never paste them into chat, GitHub issues or repository files.
4
4
 
5
5
  ## 1. Install prerequisites
6
6
 
@@ -48,7 +48,7 @@ npm pack --dry-run --json --ignore-scripts
48
48
  npm publish --dry-run --access public
49
49
  ```
50
50
 
51
- The dry-run should report `motionloom@2.5.0`, public access, and the current package file list. Do not hard-code a historical file count: inspect the JSON output and compare it with the checked-in package allowlist. The prepack hook removes generated Python bytecode before packaging. Do not publish if the dry-run shows private keys, `.env` files, `artifacts/`, `dev-lab/` or `__pycache__/` entries.
51
+ The dry-run should report `motionloom@2.5.1`, public access, and the current package file list. Do not hard-code a historical file count: inspect the JSON output and compare it with the checked-in package allowlist. The prepack hook removes generated Python bytecode before packaging. Do not publish if the dry-run shows private keys, `.env` files, `artifacts/`, `dev-lab/` or `__pycache__/` entries.
52
52
 
53
53
  ## 5. Publish the package
54
54
 
@@ -60,15 +60,15 @@ npm publish --access public
60
60
 
61
61
  Do not add `--provenance` unless the local npm client and its CI/provider integration explicitly support automatic npm provenance. Some workstation setups report `Automatic provenance generation not supported for provider: null`; in that case the package itself is still valid, and the supported fallback for this release is the command above without `--provenance`. Never retry by changing the version or by bypassing the package verification steps.
62
62
 
63
- The version `2.5.0` becomes immutable on npm after a successful publish. If npm reports that the version already exists, stop and verify the registry instead of trying to overwrite it.
63
+ The version `2.5.1` becomes immutable on npm after a successful publish. If npm reports that the version already exists, stop and verify the registry instead of trying to overwrite it.
64
64
 
65
65
  ## 6. Verify the registry publication
66
66
 
67
- Run the following commands and confirm that they return `2.5.0` and a tarball URL:
67
+ Run the following commands and confirm that they return `2.5.1` and a tarball URL:
68
68
 
69
69
  ```bash
70
70
  npm view motionloom version --registry=https://registry.npmjs.org/
71
- npm view motionloom@2.5.0 name version license dist.tarball dist.shasum --json \
71
+ npm view motionloom@2.5.1 name version license dist.tarball dist.shasum --json \
72
72
  --registry=https://registry.npmjs.org/
73
73
  ```
74
74
 
@@ -78,7 +78,7 @@ Then test installation in a clean temporary directory:
78
78
  TMP_DIR="$(mktemp -d)"
79
79
  cd "$TMP_DIR"
80
80
  npm init --yes
81
- npm install motionloom@2.5.0
81
+ npm install motionloom@2.5.1
82
82
  motionloom --help
83
83
  motionloom doctor
84
84
  ```
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "motionloom",
3
- "version": "2.5.0",
3
+ "version": "2.6.0",
4
4
  "type": "module",
5
5
  "packageManager": "pnpm@11.20.0",
6
6
  "description": "MotionLoom: project-aware animation Skill with Agent interoperability, graph, provenance, Motion IR, runtime telemetry, Visual Truth, Remediation Learning, DSSE-compatible signed attestation, external evidence verification and Dev Lab review.",
@@ -33,6 +33,12 @@
33
33
  "schemas/asset-provenance.schema.json",
34
34
  "examples/agent-consumer/ai-generated-pilot-provenance.json",
35
35
  "project-context.example.json",
36
+ "requirements.txt",
37
+ "dev-lab/public/index.html",
38
+ "dev-lab/public/devlab.js",
39
+ "dev-lab/public/action-library.js",
40
+ "dev-lab/public/runtime-bridge.js",
41
+ "tests/runtime-harness",
36
42
  "tests/evals",
37
43
  "tests/scripts/run_tests.py",
38
44
  "tests/scripts/test_attestation.py",
@@ -124,7 +130,7 @@
124
130
  },
125
131
  "scripts": {
126
132
  "prepack": "node scripts/prepack-clean.mjs",
127
- "test": "node bin/motionloom.mjs test",
133
+ "test": "python3 tests/scripts/run_tests.py",
128
134
  "validate": "npm run quality:attestation",
129
135
  "doctor": "node bin/motionloom.mjs doctor --json",
130
136
  "init": "node bin/motionloom.mjs init",
@@ -147,7 +153,8 @@
147
153
  "intelligence:p1": "node bin/motionloom.mjs intelligence",
148
154
  "quality:p1": "node bin/motionloom.mjs quality-gate --require-intelligence --require-p1 --require-benchmark",
149
155
  "eval:intelligence": "node bin/motionloom.mjs eval-intelligence",
150
- "audit:deep": "node bin/motionloom.mjs deep-audit --iterations 5000 --seed 20260813 --report docs/audits/data/deep-stress-latest.json",
156
+ "audit:deep": "python3 tests/scripts/deep-stress.py --iterations 5000 --seed 20260813 --report docs/audits/data/deep-stress-latest.json",
157
+ "package:smoke": "node scripts/package-consumer-smoke.mjs",
151
158
  "memory:init": "node bin/motionloom.mjs memory init --project-root .",
152
159
  "memory:bootstrap": "node bin/motionloom.mjs analyze . --init-memory",
153
160
  "memory:recover": "node bin/motionloom.mjs memory recover --project-root .",
@@ -191,9 +198,10 @@
191
198
  "react": "^19.2.8",
192
199
  "react-dom": "^19.2.8"
193
200
  },
194
- "devDependencies": {
201
+ "optionalDependencies": {
195
202
  "playwright": "^1.62.1",
196
203
  "vite": "^8.2.1"
197
204
  },
205
+ "devDependencies": {},
198
206
  "license": "MIT"
199
207
  }
@@ -0,0 +1 @@
1
+ cryptography>=42.0,<47.0
@@ -16,7 +16,7 @@
16
16
  "target": "rive",
17
17
  "framework": "rive",
18
18
  "status": "project_integrated",
19
- "evidence": [{"path": "scripts/runtime-adapters.mjs", "sha256": "711a64c07b4d82f646d066c7b0d91deaefc5c9c0fb701358706bf31d558b4d89"}],
19
+ "evidence": [{"path": "scripts/runtime-adapters.mjs", "sha256": "6f135e75e700a7bb105903db59445fcd3577669b595082a2b542db639f5fe56d"}],
20
20
  "limitations": ["Runtime harness evidence must be captured for the concrete scene and source bytes."]
21
21
  },
22
22
  {
@@ -24,7 +24,7 @@
24
24
  "target": "gsap",
25
25
  "framework": "gsap",
26
26
  "status": "project_integrated",
27
- "evidence": [{"path": "scripts/runtime-adapters.mjs", "sha256": "711a64c07b4d82f646d066c7b0d91deaefc5c9c0fb701358706bf31d558b4d89"}],
27
+ "evidence": [{"path": "scripts/runtime-adapters.mjs", "sha256": "6f135e75e700a7bb105903db59445fcd3577669b595082a2b542db639f5fe56d"}],
28
28
  "limitations": ["Runtime harness evidence must be captured for the concrete scene and source bytes."]
29
29
  },
30
30
  {
@@ -32,7 +32,7 @@
32
32
  "target": "framer-motion",
33
33
  "framework": "framer-motion",
34
34
  "status": "project_integrated",
35
- "evidence": [{"path": "scripts/runtime-adapters.mjs", "sha256": "711a64c07b4d82f646d066c7b0d91deaefc5c9c0fb701358706bf31d558b4d89"}],
35
+ "evidence": [{"path": "scripts/runtime-adapters.mjs", "sha256": "6f135e75e700a7bb105903db59445fcd3577669b595082a2b542db639f5fe56d"}],
36
36
  "limitations": ["Runtime harness evidence must be captured for the concrete scene and source bytes."]
37
37
  }
38
38
  ]
@@ -15,6 +15,43 @@
15
15
  "source_sha256": {"type": "string", "pattern": "^[a-f0-9]{64}$"},
16
16
  "runtime": {"type": "string"},
17
17
  "checkpoints": {"type": "array", "items": {"type": "integer", "enum": [0, 50, 100]}},
18
+ "runtime_review": {
19
+ "oneOf": [
20
+ {
21
+ "type": "object",
22
+ "additionalProperties": false,
23
+ "required": ["live", "mode", "checkpoints"],
24
+ "properties": {
25
+ "live": {"const": false},
26
+ "mode": {"const": "captured-evidence"},
27
+ "checkpoints": {"type": "array", "items": {"type": "integer", "enum": [0, 50, 100]}}
28
+ }
29
+ },
30
+ {
31
+ "type": "object",
32
+ "additionalProperties": false,
33
+ "required": ["live", "mode", "descriptor", "bundle_sha256", "animations", "review_policy"],
34
+ "properties": {
35
+ "live": {"const": true},
36
+ "mode": {"enum": ["sprite-sequence", "iframe"]},
37
+ "descriptor": {"const": "devlab-runtime.json"},
38
+ "bundle_sha256": {"type": "string", "pattern": "^[a-f0-9]{64}$"},
39
+ "animations": {
40
+ "type": "array",
41
+ "minItems": 1,
42
+ "uniqueItems": true,
43
+ "items": {"type": "string", "pattern": "^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$"}
44
+ },
45
+ "review_policy": {
46
+ "type": "object",
47
+ "additionalProperties": false,
48
+ "required": ["require_all_animations"],
49
+ "properties": {"require_all_animations": {"type": "boolean"}}
50
+ }
51
+ }
52
+ }
53
+ ]
54
+ },
18
55
  "review_artifact": {"type": "string"},
19
56
  "requires_user_approval": {"type": "boolean"},
20
57
  "prepared_at": {"type": "string", "format": "date-time"},
@@ -0,0 +1,140 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "$id": "https://motionloom.dev/schemas/devlab-runtime.schema.json",
4
+ "title": "MotionLoom Dev Lab live runtime descriptor",
5
+ "type": "object",
6
+ "additionalProperties": false,
7
+ "required": ["schema_version", "mode", "files", "animations", "default_animation", "controls", "review_policy"],
8
+ "properties": {
9
+ "schema_version": { "const": "1.0" },
10
+ "mode": { "enum": ["sprite-sequence", "iframe"] },
11
+ "files": {
12
+ "type": "array",
13
+ "minItems": 1,
14
+ "uniqueItems": true,
15
+ "items": { "$ref": "#/$defs/relativePath" }
16
+ },
17
+ "entrypoint": { "$ref": "#/$defs/relativePath" },
18
+ "default_animation": { "$ref": "#/$defs/animationId" },
19
+ "groups": {
20
+ "type": "array",
21
+ "uniqueItems": true,
22
+ "items": { "$ref": "#/$defs/group" }
23
+ },
24
+ "animations": {
25
+ "type": "array",
26
+ "minItems": 1,
27
+ "items": { "$ref": "#/$defs/animation" }
28
+ },
29
+ "controls": {
30
+ "type": "object",
31
+ "additionalProperties": false,
32
+ "required": ["play", "pause", "restart", "seek", "step", "speed", "loop"],
33
+ "properties": {
34
+ "play": { "type": "boolean" },
35
+ "pause": { "type": "boolean" },
36
+ "restart": { "type": "boolean" },
37
+ "seek": { "type": "boolean" },
38
+ "step": { "type": "boolean" },
39
+ "speed": { "type": "boolean" },
40
+ "loop": { "type": "boolean" }
41
+ }
42
+ },
43
+ "viewport": {
44
+ "type": "object",
45
+ "additionalProperties": false,
46
+ "properties": {
47
+ "canvas_width": { "type": "number", "exclusiveMinimum": 0 },
48
+ "canvas_height": { "type": "number", "exclusiveMinimum": 0 },
49
+ "pixel_art": { "type": "boolean" },
50
+ "baseline_y": { "type": "number" },
51
+ "pivot": {
52
+ "type": "object",
53
+ "additionalProperties": false,
54
+ "required": ["x", "y"],
55
+ "properties": {
56
+ "x": { "type": "number" },
57
+ "y": { "type": "number" }
58
+ }
59
+ },
60
+ "background": { "enum": ["checker", "dark", "light", "transparent", "project"] }
61
+ }
62
+ },
63
+ "review_policy": {
64
+ "type": "object",
65
+ "additionalProperties": false,
66
+ "required": ["require_all_animations"],
67
+ "properties": {
68
+ "require_all_animations": { "type": "boolean" }
69
+ }
70
+ }
71
+ },
72
+ "allOf": [
73
+ {
74
+ "if": { "properties": { "mode": { "const": "iframe" } } },
75
+ "then": { "required": ["entrypoint"] }
76
+ },
77
+ {
78
+ "if": { "properties": { "mode": { "const": "sprite-sequence" } } },
79
+ "then": {
80
+ "properties": {
81
+ "animations": {
82
+ "items": {
83
+ "required": ["id", "fps", "frames", "loop"]
84
+ }
85
+ }
86
+ }
87
+ }
88
+ }
89
+ ],
90
+ "$defs": {
91
+ "animationId": {
92
+ "type": "string",
93
+ "pattern": "^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$"
94
+ },
95
+ "groupId": {
96
+ "type": "string",
97
+ "pattern": "^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$"
98
+ },
99
+ "relativePath": {
100
+ "type": "string",
101
+ "minLength": 1,
102
+ "pattern": "^(?!/)(?!.*(?:^|/)\\.\\.(?:/|$))(?!.*\\\\).+"
103
+ },
104
+ "group": {
105
+ "type": "object",
106
+ "additionalProperties": false,
107
+ "required": ["id"],
108
+ "properties": {
109
+ "id": { "$ref": "#/$defs/groupId" },
110
+ "label": { "type": "string", "minLength": 1, "maxLength": 120 },
111
+ "order": { "type": "integer", "minimum": -10000, "maximum": 10000 }
112
+ }
113
+ },
114
+ "animation": {
115
+ "type": "object",
116
+ "additionalProperties": false,
117
+ "required": ["id", "loop"],
118
+ "properties": {
119
+ "id": { "$ref": "#/$defs/animationId" },
120
+ "label": { "type": "string", "minLength": 1, "maxLength": 120 },
121
+ "group": { "$ref": "#/$defs/groupId" },
122
+ "tags": {
123
+ "type": "array",
124
+ "uniqueItems": true,
125
+ "items": { "type": "string", "minLength": 1, "maxLength": 64 }
126
+ },
127
+ "fps": { "type": "number", "exclusiveMinimum": 0 },
128
+ "frames": {
129
+ "type": "array",
130
+ "minItems": 1,
131
+ "items": { "$ref": "#/$defs/relativePath" }
132
+ },
133
+ "loop": { "type": "boolean" },
134
+ "duration_s": { "type": "number", "exclusiveMinimum": 0 },
135
+ "review_required": { "type": "boolean", "default": true },
136
+ "events": { "type": "array", "items": { "type": "string" } }
137
+ }
138
+ }
139
+ }
140
+ }
@@ -0,0 +1,103 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "$id": "https://motionloom.dev/schemas/devlab-state-machine.schema.json",
4
+ "title": "MotionLoom Dev Lab state/transition review contract",
5
+ "type": "object",
6
+ "additionalProperties": false,
7
+ "required": ["schema_version", "initial_state", "states", "transitions", "review_policy"],
8
+ "properties": {
9
+ "schema_version": { "const": "1.0" },
10
+ "initial_state": { "$ref": "#/$defs/id" },
11
+ "states": {
12
+ "type": "array",
13
+ "minItems": 1,
14
+ "maxItems": 256,
15
+ "items": { "$ref": "#/$defs/state" }
16
+ },
17
+ "transitions": {
18
+ "type": "array",
19
+ "minItems": 1,
20
+ "maxItems": 512,
21
+ "items": { "$ref": "#/$defs/transition" }
22
+ },
23
+ "sequences": {
24
+ "type": "array",
25
+ "maxItems": 64,
26
+ "items": { "$ref": "#/$defs/sequence" }
27
+ },
28
+ "review_policy": {
29
+ "type": "object",
30
+ "additionalProperties": false,
31
+ "required": ["require_all_transitions", "require_all_sequences"],
32
+ "properties": {
33
+ "require_all_transitions": { "type": "boolean" },
34
+ "require_all_sequences": { "type": "boolean" }
35
+ }
36
+ }
37
+ },
38
+ "$defs": {
39
+ "id": {
40
+ "type": "string",
41
+ "pattern": "^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$"
42
+ },
43
+ "state": {
44
+ "type": "object",
45
+ "additionalProperties": false,
46
+ "required": ["id", "animation"],
47
+ "properties": {
48
+ "id": { "$ref": "#/$defs/id" },
49
+ "label": { "type": "string", "minLength": 1, "maxLength": 120 },
50
+ "animation": { "$ref": "#/$defs/id" },
51
+ "description": { "type": "string", "maxLength": 500 }
52
+ }
53
+ },
54
+ "transition": {
55
+ "type": "object",
56
+ "additionalProperties": false,
57
+ "required": ["id", "from", "to", "mode"],
58
+ "properties": {
59
+ "id": { "$ref": "#/$defs/id" },
60
+ "label": { "type": "string", "minLength": 1, "maxLength": 120 },
61
+ "from": {
62
+ "oneOf": [
63
+ { "$ref": "#/$defs/id" },
64
+ { "const": "*" }
65
+ ]
66
+ },
67
+ "to": { "$ref": "#/$defs/id" },
68
+ "trigger": { "type": "string", "minLength": 1, "maxLength": 120 },
69
+ "mode": { "enum": ["select-animation", "runtime-trigger"] },
70
+ "auto_play": { "type": "boolean", "default": true },
71
+ "review_required": { "type": "boolean", "default": true },
72
+ "wait_ms": { "type": "integer", "minimum": 0, "maximum": 10000 },
73
+ "payload": { "type": "object" }
74
+ }
75
+ },
76
+ "sequenceStep": {
77
+ "type": "object",
78
+ "additionalProperties": false,
79
+ "required": ["transition"],
80
+ "properties": {
81
+ "transition": { "$ref": "#/$defs/id" },
82
+ "wait_ms": { "type": "integer", "minimum": 0, "maximum": 10000 }
83
+ }
84
+ },
85
+ "sequence": {
86
+ "type": "object",
87
+ "additionalProperties": false,
88
+ "required": ["id", "steps"],
89
+ "properties": {
90
+ "id": { "$ref": "#/$defs/id" },
91
+ "label": { "type": "string", "minLength": 1, "maxLength": 120 },
92
+ "description": { "type": "string", "maxLength": 500 },
93
+ "review_required": { "type": "boolean", "default": false },
94
+ "steps": {
95
+ "type": "array",
96
+ "minItems": 1,
97
+ "maxItems": 64,
98
+ "items": { "$ref": "#/$defs/sequenceStep" }
99
+ }
100
+ }
101
+ }
102
+ }
103
+ }
@@ -47,9 +47,15 @@ def main() -> int:
47
47
  print("capture-runtime-telemetry: --max-age-days must be non-negative", file=sys.stderr)
48
48
  return 2
49
49
 
50
- root = Path(__file__).resolve().parents[1]
50
+ package_root = Path(__file__).resolve().parents[1]
51
+ root = Path(os.environ.get("MOTIONLOOM_PROJECT_ROOT") or Path.cwd()).expanduser().resolve()
51
52
  task_dir = Path(args.task_dir).expanduser()
52
53
  task_dir = task_dir.resolve() if task_dir.is_absolute() else (root / task_dir).resolve()
54
+ try:
55
+ task_dir.relative_to(root)
56
+ except ValueError:
57
+ print("capture-runtime-telemetry: task directory must remain inside the project root", file=sys.stderr)
58
+ return 2
53
59
  scene_dir = (root / "src" / "output" / args.scene).resolve()
54
60
  manifest_path = scene_dir / "manifest.json"
55
61
  task_path = task_dir / "task.json"
@@ -74,10 +80,6 @@ def main() -> int:
74
80
  return 2
75
81
 
76
82
  output_dir = task_dir / "runtime-adapters"
77
- if output_dir.exists():
78
- shutil.rmtree(output_dir)
79
- output_dir.mkdir(parents=True, exist_ok=True)
80
-
81
83
  env = os.environ.copy()
82
84
  env.update(
83
85
  {
@@ -87,17 +89,23 @@ def main() -> int:
87
89
  "RUNTIME_SOURCE_PATH": str(source_path),
88
90
  "RUNTIME_MANIFEST_PATH": str(manifest_path),
89
91
  "RUNTIME_MOTION_IR_PATH": str(task_dir / "motion-ir.json"),
92
+ "MOTIONLOOM_RUNTIME_OUTPUT_ROOT": str(root),
90
93
  }
91
94
  )
92
- npm = "npm.cmd" if os.name == "nt" else "npm"
93
- runtime = subprocess.run([npm, "run", "runtime:test"], cwd=root, env=env, check=False)
95
+ node = shutil.which("node") or ("node.exe" if os.name == "nt" else "node")
96
+ runtime = subprocess.run(
97
+ [node, str(package_root / "scripts" / "runtime-adapters.mjs")],
98
+ cwd=root,
99
+ env=env,
100
+ check=False,
101
+ )
94
102
  if runtime.returncode != 0:
95
103
  return runtime.returncode
96
104
 
97
105
  verifier = subprocess.run(
98
106
  [
99
107
  sys.executable,
100
- str(root / "scripts" / "evidence-verifier.py"),
108
+ str(package_root / "scripts" / "evidence-verifier.py"),
101
109
  "--scene-dir",
102
110
  str(scene_dir),
103
111
  "--task-dir",
package/scripts/devlab.py CHANGED
@@ -87,17 +87,9 @@ def prepare_scene(root: Path, lab: Path, scene: str, task_dir: Path | None) -> t
87
87
  return destination, task_destination, task_id
88
88
 
89
89
 
90
- def pnpm_executable() -> str:
91
- candidate = "pnpm.cmd" if os.name == "nt" else "pnpm"
92
- return shutil.which(candidate) or candidate
93
-
94
-
95
90
  def serve(lab: Path, port: int) -> int:
96
91
  if not (lab / "public").is_dir():
97
92
  fail(f"Dev Lab public directory not found: {lab / 'public'}")
98
- if not (lab / "node_modules").is_dir():
99
- print("== installing Dev Lab dependencies (first run) ==")
100
- subprocess.run([pnpm_executable(), "install", "--silent"], cwd=lab, check=True)
101
93
  print(f"== Dev Lab ready: http://localhost:{port}/ ==")
102
94
  return subprocess.run([sys.executable, "-m", "http.server", str(port), "--directory", str(lab / "public")], check=False).returncode
103
95
 
@@ -110,8 +102,9 @@ def main() -> int:
110
102
  parser.add_argument("--port", type=int, default=int(os.environ.get("PORT", "3300")))
111
103
  parser.add_argument("--prepare-only", action="store_true")
112
104
  args = parser.parse_args()
113
- root = Path(__file__).resolve().parents[1]
114
- lab = Path(args.lab_dir or os.environ.get("MOTIONLOOM_DEV_LAB") or root / "dev-lab").expanduser().resolve()
105
+ package_root = Path(__file__).resolve().parents[1]
106
+ root = Path(os.environ.get("MOTIONLOOM_PROJECT_ROOT") or Path.cwd()).expanduser().resolve()
107
+ lab = Path(args.lab_dir or os.environ.get("MOTIONLOOM_DEV_LAB") or package_root / "dev-lab").expanduser().resolve()
115
108
  task_dir = Path(args.task_dir).expanduser().resolve() if args.task_dir else None
116
109
  destination, task_destination, task_id = prepare_scene(root, lab, args.scene, task_dir)
117
110
  print(f"== Dev Lab scene prepared: {destination} ==")
@@ -93,9 +93,21 @@ if "--require-asset-provenance" not in (workflow_dir / "quality.yml").read_text(
93
93
  errors.append(f"{workflow.relative_to(ROOT)}: secrets referenced in pull_request workflow")
94
94
 
95
95
  release = (workflow_dir / "release.yml").read_text(encoding="utf-8")
96
- for required in ["workflow_dispatch:", "environment: npm-release", "id-token: write", "release_version:", "scripts/release-verify.py"]:
96
+ for required in [
97
+ "workflow_dispatch:",
98
+ "environment: npm-release",
99
+ "id-token: write",
100
+ "release_version:",
101
+ "scripts/release-verify.py",
102
+ 'node-version: "24"',
103
+ "npm@11.19.0",
104
+ 'npm publish --access public --tag "$NPM_TAG"',
105
+ ]:
97
106
  if required not in release:
98
107
  errors.append(f"release.yml: missing release safety control {required}")
108
+ for forbidden in ["NODE_AUTH_TOKEN", "secrets.NPM_TOKEN", "--provenance"]:
109
+ if forbidden in release:
110
+ errors.append(f"release.yml: token-based or redundant trusted-publishing control remains: {forbidden}")
99
111
 
100
112
  devlab = (workflow_dir / "devlab.yml").read_text(encoding="utf-8")
101
113
  for required in [