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.
- package/AGENTS.md +2 -0
- package/CHANGELOG.md +52 -1
- package/README.md +20 -8
- package/SECURITY.md +6 -7
- package/SKILL.md +5 -3
- package/agent-card.json +4 -4
- package/agent-surfaces.json +1 -1
- package/bin/motionloom.mjs +5 -4
- package/capability-registry.json +26 -26
- package/dev-lab/public/action-library.js +899 -0
- package/dev-lab/public/devlab.js +722 -0
- package/dev-lab/public/index.html +163 -0
- package/dev-lab/public/runtime-bridge.js +109 -0
- package/docs/CHECKLIST.md +15 -2
- package/docs/DEV-LAB-RUNTIME.md +160 -0
- package/docs/DEV-LAB-STATE-TRANSITIONS.md +121 -0
- package/docs/STATUS.md +3 -3
- package/docs/releases/2.5.1.md +21 -0
- package/docs/releases/2.6.0.md +38 -0
- package/docs/releases/npm-publish-from-workstation.md +6 -6
- package/package.json +12 -4
- package/requirements.txt +1 -0
- package/rig-adapter-registry.json +3 -3
- package/schemas/browser-review-candidate.schema.json +37 -0
- package/schemas/devlab-runtime.schema.json +140 -0
- package/schemas/devlab-state-machine.schema.json +103 -0
- package/scripts/capture-runtime-telemetry.py +16 -8
- package/scripts/devlab.py +3 -10
- package/scripts/docs-audit.py +13 -1
- package/scripts/package-consumer-smoke.mjs +76 -0
- package/scripts/review-hook.py +255 -19
- package/scripts/runtime-adapters.mjs +28 -3
- package/scripts/skill-doctor.py +27 -1
- package/tests/runtime-harness/index.html +24 -0
- package/tests/runtime-harness/main.jsx +111 -0
- package/tests/scripts/run_tests.py +41 -0
|
@@ -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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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": "
|
|
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": "
|
|
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
|
-
"
|
|
201
|
+
"optionalDependencies": {
|
|
195
202
|
"playwright": "^1.62.1",
|
|
196
203
|
"vite": "^8.2.1"
|
|
197
204
|
},
|
|
205
|
+
"devDependencies": {},
|
|
198
206
|
"license": "MIT"
|
|
199
207
|
}
|
package/requirements.txt
ADDED
|
@@ -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": "
|
|
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": "
|
|
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": "
|
|
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
|
-
|
|
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
|
-
|
|
93
|
-
runtime = subprocess.run(
|
|
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(
|
|
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
|
-
|
|
114
|
-
|
|
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} ==")
|
package/scripts/docs-audit.py
CHANGED
|
@@ -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 [
|
|
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 [
|