motionloom 2.4.0 → 2.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +21 -0
- package/README.md +20 -1
- package/SKILL.md +1 -1
- package/agent-card.json +4 -1
- package/agent-surfaces.json +1 -1
- package/bin/motionloom.mjs +15 -2
- package/capability-registry.json +308 -0
- package/docs/apple-distribution.md +24 -0
- package/docs/architecture/apple/cloudkit.md +18 -0
- package/docs/architecture/apple/contracts.md +9 -0
- package/docs/architecture/apple/implementation-status-2026-08-16.md +55 -0
- package/docs/architecture/apple/studio.md +35 -0
- package/docs/releases/2.5.0.md +31 -0
- package/docs/releases/npm-publish-from-workstation.md +6 -6
- package/docs/research/ai-repository-benchmark-2026-08-16.md +75 -0
- package/package.json +3 -1
- package/scripts/attestation.py +8 -0
- package/scripts/evidence-verifier.py +18 -9
- package/scripts/intelligence.py +85 -18
- package/tests/scripts/run_tests.py +44 -0
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,26 @@ All notable MotionLoom changes are documented here. The project follows semantic
|
|
|
4
4
|
|
|
5
5
|
## [Unreleased]
|
|
6
6
|
|
|
7
|
+
## [2.5.0] - 2026-08-17
|
|
8
|
+
|
|
9
|
+
### Added
|
|
10
|
+
|
|
11
|
+
- Read-only `motionloom capability card --format json` discovery surface. It exports the evidence-validated runtime registry for an Agent, including runtime status, adapter/version, compatibility, verification timestamp, inputs/outputs, evidence references, limitations, fallback, risk and side-effect metadata.
|
|
12
|
+
- `pnpm intelligence:card` shortcut and `agent-card.json` command/capability declarations for machine-readable discovery without a parallel capability policy.
|
|
13
|
+
- `capability-registry.json` is included in the npm package allowlist, so the default card command has its verified registry after a normal package installation.
|
|
14
|
+
- Benchmark research ledger comparing public agent workflow, model-serving and AI/video repositories; the release applies the low-risk capability-card lesson and records deferred generation-profile and hash-bound workflow-template work with explicit gates.
|
|
15
|
+
- Native `apps/apple/` alpha in the GitHub source release: shared Swift packages for contracts, review state, macOS bridge, review UI and local-first CloudKit metadata sync, plus unsigned macOS Studio and iOS/iPadOS Review build targets and Apple CI.
|
|
16
|
+
|
|
17
|
+
### Verified
|
|
18
|
+
|
|
19
|
+
- Capability-card export validates registry evidence before output and fails closed for missing, altered or out-of-repository evidence. It does not select a runtime or infer review/production approval.
|
|
20
|
+
- The repository release target passes the full regression suite, Skill/doctor and documentation gates, package artifact inspection, and GitHub Quality, Security, Documentation and Apple CI checks.
|
|
21
|
+
|
|
22
|
+
### Boundary
|
|
23
|
+
|
|
24
|
+
- The Apple deliverable is an alpha source release. It has no Apple Developer signing, CloudKit production container, TestFlight distribution or iOS Simulator UI-smoke claim.
|
|
25
|
+
- Capability discovery is not execution authority. Runtime selection still validates current evidence, and attestation/production approval remain non-derived human decisions.
|
|
26
|
+
|
|
7
27
|
## [2.4.0] - 2026-08-15
|
|
8
28
|
|
|
9
29
|
### Added
|
|
@@ -109,6 +129,7 @@ See the [2.0.0 release note](docs/releases/2.0.0.md).
|
|
|
109
129
|
The 1.5.0–1.9.0 milestones established runtime evidence, browser review, Intelligence Core, semantic lint, continuity, telemetry and trust-boundary hardening. Their detailed notes are available in [`docs/releases/`](docs/releases/).
|
|
110
130
|
|
|
111
131
|
[2.2.0]: docs/releases/2.2.0.md
|
|
132
|
+
[2.5.0]: docs/releases/2.5.0.md
|
|
112
133
|
[2.4.0]: docs/releases/2.4.0.md
|
|
113
134
|
[2.3.0]: docs/releases/2.3.0.md
|
|
114
135
|
[2.1.0]: docs/releases/2.1.0.md
|
package/README.md
CHANGED
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
[](LICENSE)
|
|
7
7
|
[](https://nodejs.org/)
|
|
8
8
|
[](https://www.python.org/)
|
|
9
|
+
[](https://github.com/lenhonbp/MotionLoom/actions/workflows/apple.yml)
|
|
9
10
|
[](https://agentskills.io/specification)
|
|
10
11
|
|
|
11
12
|
**Project-aware animation production and runtime verification for coding agents.**
|
|
@@ -14,7 +15,7 @@ MotionLoom is an independent open-source Agent Skill for building UI motion, Lot
|
|
|
14
15
|
|
|
15
16
|
> **MotionLoom is not an auto-approval layer.** A valid signature, a passing heuristic, or a successful render proves only the contract it checks. Visual quality, intent, accessibility and PR authorization remain reviewable human decisions.
|
|
16
17
|
|
|
17
|
-
> **Release posture:**
|
|
18
|
+
> **Release posture:** MotionLoom 2.5.0 adds an evidence-bound Agent capability card, benchmark-informed runtime discovery, and the native macOS/iOS review alpha in the GitHub source release under `apps/apple/`. The npm package remains the cross-platform Node/Python Skill and documentation surface; it does not distribute or sign the Xcode app. Verify npm/GitHub publication metadata separately; passing evidence never implies user approval.
|
|
18
19
|
|
|
19
20
|
## Why MotionLoom
|
|
20
21
|
|
|
@@ -154,6 +155,14 @@ Read the [Project Memory schema](schemas/project-memory.schema.json), [2.1.0 rel
|
|
|
154
155
|
|
|
155
156
|
Capability selection uses `agent-card.json` and the capability registry. A runtime is not promoted from `scaffold_only` to `verified` because a template exists; its adapter evidence and CI contract must pass.
|
|
156
157
|
|
|
158
|
+
An Agent can inspect the read-only capability card before choosing a renderer:
|
|
159
|
+
|
|
160
|
+
```bash
|
|
161
|
+
motionloom capability card --format json
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
The card exposes declared compatibility, evidence references, limitations and fallback paths. It does **not** select a runtime, refresh evidence, infer production approval or replace the verification step; use `motionloom intelligence capabilities select --registry capability-registry.json --capability runtime.<id>` immediately before execution.
|
|
165
|
+
|
|
157
166
|
## Evidence, trust and review
|
|
158
167
|
|
|
159
168
|
MotionLoom keeps distinct layers distinct:
|
|
@@ -257,6 +266,14 @@ The public integration surfaces are intentionally small and inspectable:
|
|
|
257
266
|
|
|
258
267
|
The Skill can trigger or suggest an internal browser-capable Agent to open the Dev Lab after rendering. Dev Lab is post-render review infrastructure, not a competing Skill. The user can request changes, receive a structured fix plan and rerender selectively, or explicitly confirm the PR path.
|
|
259
268
|
|
|
269
|
+
## Native companion apps (alpha)
|
|
270
|
+
|
|
271
|
+
`apps/apple/` contains the native-first Apple companion surface. **MotionLoom Studio for macOS** opens a scoped project, exposes the evidence and timeline inspection surface, and can request only allow-listed local checks. **MotionLoom Review for iPhone and iPad** reads a hash-bound review launch descriptor, scrubs evidence, records annotations, and exports a human review decision.
|
|
272
|
+
|
|
273
|
+
These apps do not replace the Agent, the npm CLI or Dev Lab. They make a project’s artifact state and human review decision visible between Agent sessions. They cannot grant `production_approved`, set `OPEN_PR=1`, push Git changes, publish npm packages, or turn an AI-generated asset into artist-authored material.
|
|
274
|
+
|
|
275
|
+
Read [the Apple workspace guide](apps/apple/README.md), [the contract boundary](docs/architecture/apple/contracts.md), and [the TestFlight preparation guide](docs/apple-distribution.md) before building locally.
|
|
276
|
+
|
|
260
277
|
## Repository map
|
|
261
278
|
|
|
262
279
|
| Path | Purpose |
|
|
@@ -273,6 +290,7 @@ The Skill can trigger or suggest an internal browser-capable Agent to open the D
|
|
|
273
290
|
| `references/` | Progressive-disclosure implementation references |
|
|
274
291
|
| `docs/` | Framework selection, checklists, audits and release notes |
|
|
275
292
|
| `dev-lab/` | Self-contained browser review workbench and harness |
|
|
293
|
+
| `apps/apple/` | macOS Studio and iOS/iPadOS Review companion sources, Swift packages, Xcode project and local build guide |
|
|
276
294
|
| `artifacts/<task-id>/` | Per-task evidence, report and handoff bundle |
|
|
277
295
|
| `schemas/visual-truth.schema.json`, `scripts/visual-truth.py` | Provenance-bound visual comparison and review explanation contract |
|
|
278
296
|
| `schemas/remediation-history.schema.json`, `scripts/remediation-learning.py` | Append-only remediation/benchmark ledger and aggregate learning metrics |
|
|
@@ -297,6 +315,7 @@ The Skill can trigger or suggest an internal browser-capable Agent to open the D
|
|
|
297
315
|
| Check control-to-runtime and rig compatibility | `motionloom runtime-candidate`, `motionloom rig-compatibility`, and [production checklist](docs/CHECKLIST.md) |
|
|
298
316
|
| Validate visual truth before review/PR | `motionloom visual-truth build|validate` and [production checklist](docs/CHECKLIST.md) |
|
|
299
317
|
| Check current evidence posture | [Current status](docs/STATUS.md), [external corpus evidence](docs/audits/external-project-corpus-2026-08-13.md) and [historical audit snapshot](AUDIT-REPORT.md) |
|
|
318
|
+
| Build the native review companions | [Apple workspace guide](apps/apple/README.md), [Studio architecture](docs/architecture/apple/studio.md), and [distribution preparation](docs/apple-distribution.md) |
|
|
300
319
|
| Contribute code or docs | [CONTRIBUTING.md](CONTRIBUTING.md) |
|
|
301
320
|
| Report a vulnerability or request help | [SECURITY.md](SECURITY.md) and [SUPPORT.md](SUPPORT.md) |
|
|
302
321
|
| See version history | [CHANGELOG.md](CHANGELOG.md) and [release notes](docs/releases/) |
|
package/SKILL.md
CHANGED
|
@@ -7,7 +7,7 @@ description: >-
|
|
|
7
7
|
validate, review, or deliver animation inside an existing project.
|
|
8
8
|
license: MIT
|
|
9
9
|
metadata:
|
|
10
|
-
version: "2.
|
|
10
|
+
version: "2.5.0"
|
|
11
11
|
target_frameworks: "lottie,dotlottie,rive,gsap,framer-motion,spine,threejs"
|
|
12
12
|
verified_runtimes: "lottie-json,dotlottie-package,svg-cutout-rig,rive,gsap,framer-motion"
|
|
13
13
|
---
|
package/agent-card.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"card_version": "1.0",
|
|
3
3
|
"name": "motionloom",
|
|
4
|
-
"version": "2.
|
|
4
|
+
"version": "2.5.0",
|
|
5
5
|
"description": "Project-aware animation production with durable Project Memory, provider-neutral Artifact Intake, hash-bound control-to-runtime candidates, tiered asset provenance, framework-neutral motion IR, rig compatibility, runtime verification and telemetry, Visual Truth, Remediation Learning, DSSE-compatible signed attestation, internal browser review, trust-boundary hardening and deterministic Agent handoff.",
|
|
6
6
|
"capabilities": [
|
|
7
7
|
"project.setup",
|
|
@@ -41,6 +41,7 @@
|
|
|
41
41
|
"provenance.emit",
|
|
42
42
|
"provenance.verify",
|
|
43
43
|
"capability.discover",
|
|
44
|
+
"capability.card",
|
|
44
45
|
"capability.select",
|
|
45
46
|
"replay.capture",
|
|
46
47
|
"replay.verify",
|
|
@@ -121,6 +122,7 @@
|
|
|
121
122
|
"project-graph",
|
|
122
123
|
"provenance-attestation",
|
|
123
124
|
"capability-registry",
|
|
125
|
+
"capability-card",
|
|
124
126
|
"motion-ir",
|
|
125
127
|
"replay-bundle",
|
|
126
128
|
"semantic-lint-report",
|
|
@@ -203,6 +205,7 @@
|
|
|
203
205
|
"intelligence_graph": "python3 scripts/intelligence.py graph build --task-dir artifacts/<task-id>",
|
|
204
206
|
"intelligence_provenance": "python3 scripts/intelligence.py provenance build --task-dir artifacts/<task-id>",
|
|
205
207
|
"intelligence_capabilities": "python3 scripts/intelligence.py capabilities build --output capability-registry.json",
|
|
208
|
+
"intelligence_capability_card": "motionloom capability card --format json",
|
|
206
209
|
"intelligence_motion_ir": "python3 scripts/intelligence.py motion-ir build --task-dir artifacts/<task-id>",
|
|
207
210
|
"intelligence_replay": "python3 scripts/intelligence.py replay capture --task-dir artifacts/<task-id>",
|
|
208
211
|
"semantic_lint": "python3 scripts/intelligence.py semantic-lint build --task-dir artifacts/<task-id>",
|
package/agent-surfaces.json
CHANGED
package/bin/motionloom.mjs
CHANGED
|
@@ -54,6 +54,10 @@ const NODE_COMMANDS = {
|
|
|
54
54
|
repair: "scripts/setup.mjs",
|
|
55
55
|
};
|
|
56
56
|
|
|
57
|
+
const COMMAND_ALIASES = {
|
|
58
|
+
capability: { script: "scripts/intelligence.py", args: ["capabilities"] },
|
|
59
|
+
};
|
|
60
|
+
|
|
57
61
|
function printHelp() {
|
|
58
62
|
console.log(`MotionLoom 2.4.0 — project-aware animation production and evidence contracts
|
|
59
63
|
|
|
@@ -69,6 +73,7 @@ Use when an animation task needs it:
|
|
|
69
73
|
analyze Run project analysis and refresh Project Memory
|
|
70
74
|
memory Initialize, inspect, refresh, recover or validate memory
|
|
71
75
|
intelligence Build or validate Intelligence Core artifacts
|
|
76
|
+
capability Export or select evidence-bound runtime capabilities
|
|
72
77
|
attestation Build/validate canonical signed-attestation artifacts
|
|
73
78
|
verify-attestation Verify an attestation against a trust policy
|
|
74
79
|
evidence-verify Verify runtime evidence externally
|
|
@@ -103,6 +108,7 @@ Cross-platform examples:
|
|
|
103
108
|
--status accepted --summary "Use ease-out for UI entry" --user-confirmed
|
|
104
109
|
motionloom discovery check --root . --json
|
|
105
110
|
motionloom discovery install-matrix --root . --json
|
|
111
|
+
motionloom capability card --format json
|
|
106
112
|
motionloom visual-truth validate --root . --input src/output/<scene>/visual-truth.json
|
|
107
113
|
motionloom remediation-learning summary --history artifacts/remediation-history.jsonl --json
|
|
108
114
|
motionloom asset-provenance check --input <asset-provenance.json> --root <scene-dir> --mode runtime --json
|
|
@@ -124,7 +130,8 @@ if (!command || command === "help" || command === "--help" || command === "-h")
|
|
|
124
130
|
process.exit(0);
|
|
125
131
|
}
|
|
126
132
|
|
|
127
|
-
const
|
|
133
|
+
const alias = COMMAND_ALIASES[command];
|
|
134
|
+
const script = alias?.script || NODE_COMMANDS[command] || PYTHON_COMMANDS[command];
|
|
128
135
|
if (!script) {
|
|
129
136
|
console.error(`Unknown MotionLoom command: ${command}`);
|
|
130
137
|
printHelp();
|
|
@@ -132,7 +139,13 @@ if (!script) {
|
|
|
132
139
|
}
|
|
133
140
|
|
|
134
141
|
const executable = script.endsWith(".mjs") ? process.execPath : PYTHON;
|
|
135
|
-
|
|
142
|
+
const delegatedArgs = alias
|
|
143
|
+
? [...alias.args, ...args]
|
|
144
|
+
: NODE_COMMANDS[command] && !["setup", "init"].includes(command)
|
|
145
|
+
? [command, ...args]
|
|
146
|
+
: command === "init"
|
|
147
|
+
? ["init", ...args]
|
|
148
|
+
: args;
|
|
136
149
|
const result = spawnSync(executable, [resolve(ROOT, script), ...delegatedArgs], {
|
|
137
150
|
cwd: ROOT,
|
|
138
151
|
stdio: "inherit",
|
|
@@ -0,0 +1,308 @@
|
|
|
1
|
+
{
|
|
2
|
+
"schema_version": "0.1",
|
|
3
|
+
"registry_id": "registry-motionloom-2.5.0",
|
|
4
|
+
"generated_at": "2026-08-17T05:39:24Z",
|
|
5
|
+
"selection_policy": {
|
|
6
|
+
"require_verified": true,
|
|
7
|
+
"allow_scaffold_only": false,
|
|
8
|
+
"max_evidence_age_seconds": 604800
|
|
9
|
+
},
|
|
10
|
+
"capabilities": [
|
|
11
|
+
{
|
|
12
|
+
"id": "runtime.lottie-json",
|
|
13
|
+
"kind": "renderer",
|
|
14
|
+
"status": "verified",
|
|
15
|
+
"adapter_version": "2.5.0",
|
|
16
|
+
"inputs": [
|
|
17
|
+
"project-context",
|
|
18
|
+
"motion-spec",
|
|
19
|
+
"source-binding"
|
|
20
|
+
],
|
|
21
|
+
"outputs": [
|
|
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-17T05:39:24Z",
|
|
34
|
+
"evidence": [
|
|
35
|
+
{
|
|
36
|
+
"path": "scripts/runtime-adapters.mjs",
|
|
37
|
+
"sha256": "711a64c07b4d82f646d066c7b0d91deaefc5c9c0fb701358706bf31d558b4d89",
|
|
38
|
+
"kind": "ci"
|
|
39
|
+
}
|
|
40
|
+
],
|
|
41
|
+
"limitations": [
|
|
42
|
+
"Capability evidence is refreshed by runtime:test."
|
|
43
|
+
],
|
|
44
|
+
"fallback": "",
|
|
45
|
+
"risk_level": "low",
|
|
46
|
+
"side_effect_level": "local_write"
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
"id": "runtime.dotlottie-package",
|
|
50
|
+
"kind": "packager",
|
|
51
|
+
"status": "verified",
|
|
52
|
+
"adapter_version": "2.5.0",
|
|
53
|
+
"inputs": [
|
|
54
|
+
"project-context",
|
|
55
|
+
"motion-spec",
|
|
56
|
+
"source-binding"
|
|
57
|
+
],
|
|
58
|
+
"outputs": [
|
|
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-17T05:39:24Z",
|
|
71
|
+
"evidence": [
|
|
72
|
+
{
|
|
73
|
+
"path": "scripts/runtime-adapters.mjs",
|
|
74
|
+
"sha256": "711a64c07b4d82f646d066c7b0d91deaefc5c9c0fb701358706bf31d558b4d89",
|
|
75
|
+
"kind": "ci"
|
|
76
|
+
}
|
|
77
|
+
],
|
|
78
|
+
"limitations": [
|
|
79
|
+
"Capability evidence is refreshed by runtime:test."
|
|
80
|
+
],
|
|
81
|
+
"fallback": "",
|
|
82
|
+
"risk_level": "low",
|
|
83
|
+
"side_effect_level": "local_write"
|
|
84
|
+
},
|
|
85
|
+
{
|
|
86
|
+
"id": "runtime.svg-cutout-rig",
|
|
87
|
+
"kind": "renderer",
|
|
88
|
+
"status": "verified",
|
|
89
|
+
"adapter_version": "2.5.0",
|
|
90
|
+
"inputs": [
|
|
91
|
+
"project-context",
|
|
92
|
+
"motion-spec",
|
|
93
|
+
"source-binding"
|
|
94
|
+
],
|
|
95
|
+
"outputs": [
|
|
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-17T05:39:24Z",
|
|
108
|
+
"evidence": [
|
|
109
|
+
{
|
|
110
|
+
"path": "scripts/runtime-adapters.mjs",
|
|
111
|
+
"sha256": "711a64c07b4d82f646d066c7b0d91deaefc5c9c0fb701358706bf31d558b4d89",
|
|
112
|
+
"kind": "ci"
|
|
113
|
+
}
|
|
114
|
+
],
|
|
115
|
+
"limitations": [
|
|
116
|
+
"Capability evidence is refreshed by runtime:test."
|
|
117
|
+
],
|
|
118
|
+
"fallback": "",
|
|
119
|
+
"risk_level": "low",
|
|
120
|
+
"side_effect_level": "local_write"
|
|
121
|
+
},
|
|
122
|
+
{
|
|
123
|
+
"id": "runtime.rive",
|
|
124
|
+
"kind": "runtime-adapter",
|
|
125
|
+
"status": "verified",
|
|
126
|
+
"adapter_version": "2.5.0",
|
|
127
|
+
"inputs": [
|
|
128
|
+
"project-context",
|
|
129
|
+
"motion-spec",
|
|
130
|
+
"source-binding"
|
|
131
|
+
],
|
|
132
|
+
"outputs": [
|
|
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-17T05:39:24Z",
|
|
145
|
+
"evidence": [
|
|
146
|
+
{
|
|
147
|
+
"path": "scripts/runtime-adapters.mjs",
|
|
148
|
+
"sha256": "711a64c07b4d82f646d066c7b0d91deaefc5c9c0fb701358706bf31d558b4d89",
|
|
149
|
+
"kind": "ci"
|
|
150
|
+
}
|
|
151
|
+
],
|
|
152
|
+
"limitations": [
|
|
153
|
+
"Capability evidence is refreshed by runtime:test."
|
|
154
|
+
],
|
|
155
|
+
"fallback": "",
|
|
156
|
+
"risk_level": "low",
|
|
157
|
+
"side_effect_level": "local_write"
|
|
158
|
+
},
|
|
159
|
+
{
|
|
160
|
+
"id": "runtime.gsap",
|
|
161
|
+
"kind": "runtime-adapter",
|
|
162
|
+
"status": "verified",
|
|
163
|
+
"adapter_version": "2.5.0",
|
|
164
|
+
"inputs": [
|
|
165
|
+
"project-context",
|
|
166
|
+
"motion-spec",
|
|
167
|
+
"source-binding"
|
|
168
|
+
],
|
|
169
|
+
"outputs": [
|
|
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-17T05:39:24Z",
|
|
182
|
+
"evidence": [
|
|
183
|
+
{
|
|
184
|
+
"path": "scripts/runtime-adapters.mjs",
|
|
185
|
+
"sha256": "711a64c07b4d82f646d066c7b0d91deaefc5c9c0fb701358706bf31d558b4d89",
|
|
186
|
+
"kind": "ci"
|
|
187
|
+
}
|
|
188
|
+
],
|
|
189
|
+
"limitations": [
|
|
190
|
+
"Capability evidence is refreshed by runtime:test."
|
|
191
|
+
],
|
|
192
|
+
"fallback": "",
|
|
193
|
+
"risk_level": "low",
|
|
194
|
+
"side_effect_level": "local_write"
|
|
195
|
+
},
|
|
196
|
+
{
|
|
197
|
+
"id": "runtime.framer-motion",
|
|
198
|
+
"kind": "runtime-adapter",
|
|
199
|
+
"status": "verified",
|
|
200
|
+
"adapter_version": "2.5.0",
|
|
201
|
+
"inputs": [
|
|
202
|
+
"project-context",
|
|
203
|
+
"motion-spec",
|
|
204
|
+
"source-binding"
|
|
205
|
+
],
|
|
206
|
+
"outputs": [
|
|
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-17T05:39:24Z",
|
|
219
|
+
"evidence": [
|
|
220
|
+
{
|
|
221
|
+
"path": "scripts/runtime-adapters.mjs",
|
|
222
|
+
"sha256": "711a64c07b4d82f646d066c7b0d91deaefc5c9c0fb701358706bf31d558b4d89",
|
|
223
|
+
"kind": "ci"
|
|
224
|
+
}
|
|
225
|
+
],
|
|
226
|
+
"limitations": [
|
|
227
|
+
"Capability evidence is refreshed by runtime:test."
|
|
228
|
+
],
|
|
229
|
+
"fallback": "",
|
|
230
|
+
"risk_level": "low",
|
|
231
|
+
"side_effect_level": "local_write"
|
|
232
|
+
},
|
|
233
|
+
{
|
|
234
|
+
"id": "runtime.spine",
|
|
235
|
+
"kind": "renderer",
|
|
236
|
+
"status": "scaffold_only",
|
|
237
|
+
"adapter_version": "2.5.0",
|
|
238
|
+
"inputs": [
|
|
239
|
+
"project-context",
|
|
240
|
+
"motion-spec",
|
|
241
|
+
"source-binding"
|
|
242
|
+
],
|
|
243
|
+
"outputs": [
|
|
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-17T05:39:24Z",
|
|
256
|
+
"evidence": [
|
|
257
|
+
{
|
|
258
|
+
"path": "scripts/runtime-adapters.mjs",
|
|
259
|
+
"sha256": "711a64c07b4d82f646d066c7b0d91deaefc5c9c0fb701358706bf31d558b4d89",
|
|
260
|
+
"kind": "ci"
|
|
261
|
+
}
|
|
262
|
+
],
|
|
263
|
+
"limitations": [
|
|
264
|
+
"Scaffold only; do not use for production acceptance."
|
|
265
|
+
],
|
|
266
|
+
"fallback": "runtime.lottie-json",
|
|
267
|
+
"risk_level": "high",
|
|
268
|
+
"side_effect_level": "local_write"
|
|
269
|
+
},
|
|
270
|
+
{
|
|
271
|
+
"id": "runtime.threejs",
|
|
272
|
+
"kind": "renderer",
|
|
273
|
+
"status": "scaffold_only",
|
|
274
|
+
"adapter_version": "2.5.0",
|
|
275
|
+
"inputs": [
|
|
276
|
+
"project-context",
|
|
277
|
+
"motion-spec",
|
|
278
|
+
"source-binding"
|
|
279
|
+
],
|
|
280
|
+
"outputs": [
|
|
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-17T05:39:24Z",
|
|
293
|
+
"evidence": [
|
|
294
|
+
{
|
|
295
|
+
"path": "scripts/runtime-adapters.mjs",
|
|
296
|
+
"sha256": "711a64c07b4d82f646d066c7b0d91deaefc5c9c0fb701358706bf31d558b4d89",
|
|
297
|
+
"kind": "ci"
|
|
298
|
+
}
|
|
299
|
+
],
|
|
300
|
+
"limitations": [
|
|
301
|
+
"Scaffold only; do not use for production acceptance."
|
|
302
|
+
],
|
|
303
|
+
"fallback": "runtime.lottie-json",
|
|
304
|
+
"risk_level": "high",
|
|
305
|
+
"side_effect_level": "local_write"
|
|
306
|
+
}
|
|
307
|
+
]
|
|
308
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# Apple distribution boundary
|
|
2
|
+
|
|
3
|
+
The current `MotionLoomStudio` build is an unsigned local alpha. GitHub CI builds packages and checks iOS Simulator compatibility; it does not sign, upload, submit or distribute an application.
|
|
4
|
+
|
|
5
|
+
| Channel | Required external inputs | Current state |
|
|
6
|
+
| --- | --- | --- |
|
|
7
|
+
| Local macOS alpha | Xcode and a user-selected project folder | Implemented and manually launchable. |
|
|
8
|
+
| iOS Simulator review UI | Xcode simulator runtime and an Xcode app target that embeds `MotionLoomReviewUI` | Library cross-compilation is verified; the signed target remains intentionally uncreated. |
|
|
9
|
+
| TestFlight | Apple Developer Program membership, bundle identifier, signing, App Store Connect app record and a user approval to upload | Not initiated. |
|
|
10
|
+
| Mac App Store / iOS App Store | The TestFlight requirements plus app metadata, privacy details and an explicit human submission decision | Not initiated. |
|
|
11
|
+
|
|
12
|
+
TestFlight distribution is handled through App Store Connect and requires the app owner to control signing and submission; MotionLoom’s CI must stay read-only until an owner explicitly decides to introduce that workflow.[1]
|
|
13
|
+
|
|
14
|
+
## Before a TestFlight decision
|
|
15
|
+
|
|
16
|
+
1. Create a final bundle identifier and an Apple-owned iCloud container; do not reuse development identifiers accidentally.
|
|
17
|
+
2. Create the iOS/macOS Xcode app targets and add `MotionLoomContracts`, `MotionLoomReview`, `MotionLoomReviewUI` and, on macOS, `MotionLoomMacBridge` as local packages.
|
|
18
|
+
3. Complete privacy labels and explain that CloudKit is limited to review metadata if sync is enabled.
|
|
19
|
+
4. Test review JSON handoff with a real candidate on a physical iPhone/iPad and macOS.
|
|
20
|
+
5. Make a separate explicit decision before uploading any build, inviting testers or submitting for review.
|
|
21
|
+
|
|
22
|
+
## References
|
|
23
|
+
|
|
24
|
+
[1]: https://developer.apple.com/testflight/ "Apple Developer — TestFlight"
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# CloudKit review metadata boundary
|
|
2
|
+
|
|
3
|
+
`MotionLoomReviewSync` is intentionally **local-first**. A review is written to a local outbox before it can be uploaded. The CloudKit adapter uploads only a metadata projection after `MotionLoomContractDecoder.verify` confirms that task ID, candidate ID and both evidence digests match the review launch descriptor.
|
|
4
|
+
|
|
5
|
+
| Data class | Local outbox | Private CloudKit database | Explicitly excluded |
|
|
6
|
+
| --- | --- | --- | --- |
|
|
7
|
+
| Human review decision and annotations | Yes | Metadata projection only | No production approval field exists. |
|
|
8
|
+
| Task/candidate IDs and evidence hashes | Yes | Yes | No raw runtime artifact bytes. |
|
|
9
|
+
| Source asset, texture, `.riv`, `.lottie`, project repository or secrets | No | No | Never synchronized by this adapter. |
|
|
10
|
+
| GitHub/PR/npm/App Store operations | No | No | Must remain an explicit human-controlled workflow outside CloudKit. |
|
|
11
|
+
|
|
12
|
+
The default database is the user’s private CloudKit database. Before enabling a production container, the app project needs a user-owned container identifier, the iCloud capability entitlement, an App ID and an explicit privacy review. The source package does **not** supply a container identifier, entitlement, Apple team ID, signing profile or server token.
|
|
13
|
+
|
|
14
|
+
Apple describes CloudKit as a framework for storing and syncing app data through the user’s iCloud account; this design deliberately narrows that scope to review metadata and does not treat CloudKit as the source of truth for source assets or repository state.[1]
|
|
15
|
+
|
|
16
|
+
## References
|
|
17
|
+
|
|
18
|
+
[1]: https://developer.apple.com/icloud/cloudkit/ "Apple Developer — CloudKit"
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
# Apple contract boundary
|
|
2
|
+
|
|
3
|
+
The Apple application is an inspection and human-review surface. It reads canonical MotionLoom artifacts and writes only a human review decision bound to the selected task, candidate and evidence digests. It does not duplicate Python validator policy, create provenance, grant `production_approved`, set `OPEN_PR=1`, open a pull request or publish a package.
|
|
4
|
+
|
|
5
|
+
`contracts/apple/review-launch-descriptor.schema.json` defines the minimum identity-bound information a macOS or iOS review surface may accept. A descriptor is rejected when its schema version is unsupported, it has unknown fields, identifiers are invalid, an artifact root is missing, or runtime/candidate evidence digests are absent.
|
|
6
|
+
|
|
7
|
+
`contracts/apple/review-decision.schema.json` defines a record made only after explicit human action. The only allowed decisions are `request_changes`, `reviewed_no_decision` and `approve_for_next_human_step`. None is production approval. A later policy-controlled workflow may consume a record only after independently verifying the same task, candidate and evidence identity.
|
|
8
|
+
|
|
9
|
+
The initial fixtures bind to `runtime-pilot-001`. They are decoder and identity fixtures, not an approval fixture and not a claim that the candidate is production eligible.
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
# MotionLoom Apple alpha — implementation status
|
|
2
|
+
|
|
3
|
+
**Ngày kiểm tra:** 16-08-2026
|
|
4
|
+
**Phạm vi:** macOS Studio alpha, iOS/iPadOS Review companion, review contracts, local-first sync foundation và native CI.
|
|
5
|
+
**Trạng thái source:** hoàn thành và đã kiểm thử local; chưa commit, push, ký hoặc phát hành.
|
|
6
|
+
|
|
7
|
+
## Kết luận
|
|
8
|
+
|
|
9
|
+
MotionLoom hiện có một lớp app Apple native-first bám đúng mô hình **artifact-first, runtime-evidence và human-governed** của repository. Lớp mới không thay thế CLI Node/Python, runtime adapters hoặc Dev Lab; nó biến evidence đã có thành Project Inspector, Timeline Desk và review surface native cho macOS/iOS.
|
|
10
|
+
|
|
11
|
+
> App không có khả năng cấp `production_approved`, tự chạy shell tùy ý, tự tạo PR, push Git, publish npm hay submit App Store. Các hành động này tiếp tục nằm ngoài native surface và phải qua cơ chế review/quyền hiện hữu.
|
|
12
|
+
|
|
13
|
+
| Thành phần | Đầu ra đã tạo | Trạng thái |
|
|
14
|
+
| --- | --- | --- |
|
|
15
|
+
| Canonical contracts | `contracts/apple/review-launch-descriptor.schema.json`, `review-decision.schema.json` và fixture hash-bound runtime-pilot | Đã hoàn thành; reject unknown fields và không biểu diễn production approval. |
|
|
16
|
+
| Contract runtime | `apps/apple/Packages/MotionLoomContracts` | Đã hoàn thành; Swift decoder fail-closed, migration version và identity binding. |
|
|
17
|
+
| Review core | `apps/apple/Packages/MotionLoomReview` | Đã hoàn thành; timeline, annotation, ba human review decisions và export review JSON deterministic. |
|
|
18
|
+
| Project bridge | `apps/apple/Packages/MotionLoomMacBridge` | Đã hoàn thành; security-scoped folder access, allow-list inspection commands, chặn path/URL không tin cậy. |
|
|
19
|
+
| macOS Studio | `apps/apple/MotionLoomStudio` và target `MotionLoomStudio` | Đã build và launch unsigned; có Project Inspector, Timeline Desk và native web surface mở Dev Lab review URL. |
|
|
20
|
+
| iOS/iPadOS companion | `apps/apple/MotionLoomReviewApp`, `MotionLoomReviewUI` và target `MotionLoomReview` | Đã build unsigned cho iPhone Simulator SDK; chỉ đọc evidence, scrub timeline, annotate và tạo review decision. |
|
|
21
|
+
| Đồng bộ review | `apps/apple/Packages/MotionLoomReviewSync` | Đã hoàn thành local-first outbox; chỉ đưa metadata review identity-bound vào sync adapter. |
|
|
22
|
+
| CI và hướng dẫn | `.github/workflows/apple.yml`, `apps/apple/scripts/emit-ios-simulator-destination.sh`, các tài liệu Apple | Đã hoàn thành; CI không ký, không publish và dùng dynamic simulator SDK destination. |
|
|
23
|
+
|
|
24
|
+
## Trust boundary giữ nguyên
|
|
25
|
+
|
|
26
|
+
Các package Swift dùng canonical contract và fixture từ repository thay vì sao chép policy validator sang Swift. `MotionLoomContracts` giới hạn review decision ở các trạng thái review của con người; `MotionLoomReview` chỉ export review record; `MotionLoomMacBridge` chỉ có lệnh inspection trong allow-list. CloudKit adapter cũng chỉ nhận review metadata đã identity-bound, không nhận source asset, repository, secret, production authority hoặc capability nâng quyền.
|
|
27
|
+
|
|
28
|
+
Mac app có thể mở project folder và xuất handoff review; iOS app có thể đọc candidate/evidence và ghi note theo timeline. Cả hai app không làm thay nhiệm vụ của Agent: Agent tiếp tục tạo/sửa artifact, chạy validator và render runtime thật; người dùng review trên native app rồi tạo data để Agent tiếp tục xử lý.
|
|
29
|
+
|
|
30
|
+
## Kiểm thử đã chạy trên Mac
|
|
31
|
+
|
|
32
|
+
| Kiểm tra | Kết quả |
|
|
33
|
+
| --- | --- |
|
|
34
|
+
| `pnpm test` từ repository root | **PASS** — toàn bộ regression suite, gồm Apple contract và signed attestation. |
|
|
35
|
+
| `pnpm run docs:check` | **PASS**. |
|
|
36
|
+
| `swift test` cho Contracts, Review, MacBridge, ReviewUI và ReviewSync | **PASS**. |
|
|
37
|
+
| `xcodebuild` unsigned target `MotionLoomStudio` với SDK macOS | **PASS**; Studio đã launch local. |
|
|
38
|
+
| `xcodebuild` unsigned target `MotionLoomReview` với SDK iPhone Simulator | **PASS**. |
|
|
39
|
+
| `git diff --check` | **PASS**. |
|
|
40
|
+
|
|
41
|
+
Trong quá trình acceptance trên macOS, regression cũ phát hiện giả định path không phù hợp với alias hệ thống `/var -> /private/var`. Các chỉnh sửa giới hạn trong `scripts/evidence-verifier.py`, `scripts/intelligence.py` và `scripts/attestation.py`: chỉ parent alias hệ thống này được xử lý đúng trên macOS; symlink tại hoặc bên trong scene, task, evidence, repository và replay roots vẫn bị reject. Toàn bộ regression suite đã pass sau thay đổi này.
|
|
42
|
+
|
|
43
|
+
## Blocker còn lại ngoài source code
|
|
44
|
+
|
|
45
|
+
| Hạng mục | Lý do chưa thực hiện | Điều kiện để hoàn thành |
|
|
46
|
+
| --- | --- | --- |
|
|
47
|
+
| iOS UI smoke trên simulator | Xcode đang có iPhone Simulator SDK để compile nhưng chưa có device runtime khả dụng để boot. | Cài một iOS Simulator runtime trong Xcode Settings, sau đó boot device và chạy UI smoke. |
|
|
48
|
+
| CloudKit sync live | Cần container thuộc Apple Developer account và entitlement có quyền của chủ sở hữu. | Chủ sở hữu tạo/chọn CloudKit container, quyết định retention/privacy và bật entitlement trong Xcode. |
|
|
49
|
+
| Ký/TestFlight/App Store | Cần Apple Developer Team, App IDs, bundle identifiers, signing và quyết định submit của chủ sở hữu. | Cấu hình signing, tạo archive, rồi chủ sở hữu duyệt upload TestFlight. |
|
|
50
|
+
|
|
51
|
+
Các blocker trên không làm source alpha bị lỗi; chúng là quyền tài khoản và hạ tầng bên ngoài mà MotionLoom không tự tạo hoặc tự chấp nhận thay người dùng.
|
|
52
|
+
|
|
53
|
+
## Bước tiếp theo sau khi commit/push được duyệt
|
|
54
|
+
|
|
55
|
+
Sau khi source được commit và push riêng, công việc hợp lý tiếp theo là cài iOS Simulator runtime, chạy UI smoke của Review companion, thiết kế CloudKit container review-only và chỉ sau đó mới cân nhắc beta TestFlight. Không nên thêm production approval, Git push hay asset upload capability vào app trước khi vòng review iOS đã được kiểm chứng trên device/simulator.
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# MotionLoom Studio alpha
|
|
2
|
+
|
|
3
|
+
MotionLoom Studio is a **native macOS inspection and review surface**, not a replacement for the cross-platform MotionLoom CLI, its Python validators, runtime adapters or Dev Lab. The Studio presents verified task/candidate/evidence identity and helps a human create a review handoff.
|
|
4
|
+
|
|
5
|
+
| Surface | Responsibility | Prohibited actions |
|
|
6
|
+
| --- | --- | --- |
|
|
7
|
+
| Project Inspector | User selects a scoped project folder, then launches a small allow-list of local inspections. | Arbitrary shell, `git push`, `OPEN_PR=1`, npm publish and PR creation. |
|
|
8
|
+
| Timeline Desk | Scrub review timecodes, open the existing Dev Lab candidate and collect annotations. | Treating a preview or heuristic as approval. |
|
|
9
|
+
| Evidence | Shows task/candidate identity and SHA-256 digests before review. | Loading a candidate whose identity binding is not valid. |
|
|
10
|
+
| Export Human Review | Writes a deterministic review JSON for an Agent or maintainer to act on later. | Writing `production_approved` or modifying a repository. |
|
|
11
|
+
|
|
12
|
+
The app uses `ProjectAccessScope` to confine read/write operations to an explicitly selected project root. It invokes the CLI through an argument vector and a closed command enum, never a shell string. macOS security-scoped bookmarks are supported for a later sandboxed/notarized build, but the alpha remains unsigned and local.
|
|
13
|
+
|
|
14
|
+
## Local verification
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
cd apps/apple/Packages/MotionLoomContracts && swift test
|
|
18
|
+
cd ../MotionLoomReview && swift test
|
|
19
|
+
cd ../MotionLoomMacBridge && swift test
|
|
20
|
+
cd ../MotionLoomReviewUI && swift test
|
|
21
|
+
cd ../MotionLoomReviewSync && swift test
|
|
22
|
+
cd ../../MotionLoomStudio && swift build
|
|
23
|
+
swift run MotionLoomStudio
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
To verify the companion library against the installed iOS Simulator SDK without signing an app bundle:
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
destination="$(mktemp)"
|
|
30
|
+
apps/apple/scripts/emit-ios-simulator-destination.sh "$destination"
|
|
31
|
+
(cd apps/apple/Packages/MotionLoomReviewUI && swift build --destination "$destination")
|
|
32
|
+
rm -f "$destination"
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
> A successful native build, attestation or quality gate is **not** human approval. The only review records emitted by the app are `request_changes`, `reviewed_no_decision`, and `approve_for_next_human_step`.
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# MotionLoom 2.5.0
|
|
2
|
+
|
|
3
|
+
## Summary
|
|
4
|
+
|
|
5
|
+
MotionLoom 2.5.0 makes the runtime capability boundary directly discoverable by an Agent while keeping execution, evidence verification and human review separate. It also publishes the repository's native Apple review alpha in the GitHub source release, with explicit limits around signing and distribution.
|
|
6
|
+
|
|
7
|
+
## Added
|
|
8
|
+
|
|
9
|
+
### Evidence-bound capability card
|
|
10
|
+
|
|
11
|
+
`motionloom capability card --format json` exports a read-only card from the already validated capability registry. The card reports runtime status, compatibility, adapter version, verification time, inputs/outputs, evidence references, limitations, fallback and risk metadata. It fails closed if the registry evidence is missing, altered or points outside the repository.
|
|
12
|
+
|
|
13
|
+
The card is discovery metadata only. It does not select or execute a runtime, refresh evidence, grant an attestation approval, set `production_approved`, open a pull request or publish an artifact. An Agent must still call capability selection immediately before execution.
|
|
14
|
+
|
|
15
|
+
### Benchmark-informed discovery
|
|
16
|
+
|
|
17
|
+
The research ledger records transferable patterns from public agent workflow, model-serving and AI/video repositories. This release applies only the low-risk capability-card pattern. Generation-profile metadata and hash-bound workflow/template recipes remain deferred until their own schemas, validators, provenance bindings and regression suites exist.
|
|
18
|
+
|
|
19
|
+
### Native Apple review alpha
|
|
20
|
+
|
|
21
|
+
The GitHub source release contains `apps/apple/`: shared Swift packages for fail-closed review contracts, timeline/review state, macOS sandbox bridge, shared macOS/iOS review UI and local-first CloudKit review metadata sync. It also includes unsigned macOS Studio and iOS/iPadOS Review targets plus Apple CI.
|
|
22
|
+
|
|
23
|
+
The npm tarball intentionally remains the portable Node/Python Skill and documentation package. It does not package the Xcode source tree, sign an application, create a CloudKit production container, install an iOS Simulator runtime or submit a TestFlight build.
|
|
24
|
+
|
|
25
|
+
## Verified behavior
|
|
26
|
+
|
|
27
|
+
Before publication, the release target is checked with the full regression harness, documentation audit, Skill Doctor, discovery check, package dry-run/artifact inspection and GitHub Quality, Security Analysis, Documentation & Package Hygiene and MotionLoom Apple workflows.
|
|
28
|
+
|
|
29
|
+
## Trust boundary
|
|
30
|
+
|
|
31
|
+
Runtime capability information is not a release or approval mechanism. Capability selection still requires fresh evidence. A passing test, an intact registry, an attestation or an Apple build does not replace user review, grant `production_approved`, create a pull request or authorize a production deployment.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Publish MotionLoom to npm from your workstation
|
|
2
2
|
|
|
3
|
-
This guide publishes the prepared `motionloom@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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
64
64
|
|
|
65
65
|
## 6. Verify the registry publication
|
|
66
66
|
|
|
67
|
-
Run the following commands and confirm that they return `2.
|
|
67
|
+
Run the following commands and confirm that they return `2.5.0` 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.
|
|
71
|
+
npm view motionloom@2.5.0 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.
|
|
81
|
+
npm install motionloom@2.5.0
|
|
82
82
|
motionloom --help
|
|
83
83
|
motionloom doctor
|
|
84
84
|
```
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
# AI Repository Benchmark — research ledger
|
|
2
|
+
|
|
3
|
+
**Ngày bắt đầu:** 2026-08-16
|
|
4
|
+
**Phạm vi:** Các repository/tool AI công khai có bài học workflow, packaging hoặc runtime verification có thể đánh giá cho MotionLoom. Đây không phải là danh sách khuyến nghị tích hợp dependency.
|
|
5
|
+
|
|
6
|
+
## Phương pháp
|
|
7
|
+
|
|
8
|
+
Mỗi candidate được kiểm tra qua nguồn chính thức, README/license, yêu cầu triển khai, bề mặt security và mức độ phù hợp với invariant MotionLoom. Star count chỉ là tín hiệu phụ; không dùng làm bằng chứng chất lượng hay an toàn. Không thực thi code bên thứ ba trong đợt nghiên cứu này.
|
|
9
|
+
|
|
10
|
+
## Phát hiện ban đầu
|
|
11
|
+
|
|
12
|
+
| Candidate | Nhóm | Điều nguồn chính thức xác nhận | Bài học tiềm năng cho MotionLoom | Rủi ro/không sao chép |
|
|
13
|
+
|---|---|---|---|---|
|
|
14
|
+
| [Coze Studio](https://github.com/coze-dev/coze-studio) | Agent workflow | Công cụ phát triển agent all-in-one với prompt, RAG, plugin, workflow; có templates, low/no-code canvas, API/SDK và self-host qua Docker Compose. README cảnh báo rõ public deployment có bề mặt registration, Python workflow execution, SSRF và API privilege risks. License Apache-2.0. | Progressive disclosure: quick setup, sau đó mới model/plugin/workflow configuration. Hiển thị explicit security posture bên cạnh workflow launch. | Không áp dụng visual canvas như authority; MotionLoom phải giữ artifact identity, runtime evidence và user review là nguồn truth. Không gọi code execution node tùy ý từ app/Dev Lab. |
|
|
15
|
+
| [Dify](https://github.com/langgenius/dify) | Agent workflow | Nền tảng app LLM có workflow, RAG, agents, model management, observability và APIs; quick start rõ ràng qua Docker Compose; mô tả environment/config separation. License là Dify Open Source License dựa trên Apache-2.0 với điều kiện bổ sung. | Onboarding theo tầng: cài nhanh, dashboard init, cấu hình môi trường rõ; observability là bề mặt sản phẩm chứ không phải log ẩn. | Không sao chép license/model hosting approach; không dùng dashboard telemetry làm bằng chứng production approval. Không mở mơ hồ các provider credential vào repository. |
|
|
16
|
+
| [ComfyUI](https://github.com/Comfy-Org/ComfyUI) và [workflow templates](https://github.com/Comfy-Org/workflow_templates) | Visual workflow | Node graph cho image/video/audio/3D/text; JSON save/load workflows, reusable subgraphs, templates, local API, App Mode và khả năng recover workflow/seed từ media được hỗ trợ. Repo template dùng manifest regenerated với SHA-256 mismatch gate; production chỉ deploy workflow có `status: approved`, preview có thể hiển thị trạng thái khác. | Đưa `approved/rejected/deprecated` vào index workflow/template, bắt buộc manifest hash và expose một review surface giản lược cho end user thay vì tất cả graph controls. | Không xem JSON graph/seed là provenance hoặc runtime evidence. Không nhận custom-node metadata như source tin cậy; mọi external template phải qua intake và identity binding của MotionLoom. |
|
|
17
|
+
| [Wan2.1](https://github.com/Wan-Video/Wan2.1) | Video generation | Model suite hỗ trợ T2V, I2V, FLF2V, editing/VACE; README công bố rõ task/resolution/model và links checkpoint. Có integration Diffusers/ComfyUI và nêu ràng buộc VRAM, resolution stability, model-specific prompt guidance. | `generation-profile` phải tách task/model/resolution/frames/provider; lấy capability, hardware expectation và model/checkpoint identity thành evidence thay vì claim chung chung “AI video generated”. | Không tích hợp model runtime vào MotionLoom core và không suy diễn quality từ model name. Kết quả video/pose từ bất kỳ provider nào vẫn là `ai_generated`, cần geometry/consistency/runtime review. |
|
|
18
|
+
| [HunyuanVideo](https://github.com/Tencent-Hunyuan/HunyuanVideo) | Video/human animation | Cung cấp inference/weights, I2V, Avatar, custom generation, benchmark và các integration. README ghi rõ dependency CUDA/Linux, memory requirement cao và phạm vi environment đã test. | Cần consumer-facing capability card: platform, runtime, GPU/memory, tested environment, source model/version và failure mode trước khi Agent đề xuất một generation route. | Không hứa “one-click” nếu phần cứng/OS không đáp ứng. Không dùng self-reported benchmark để thay runtime verification hay human approval. |
|
|
19
|
+
| [Xinference](https://github.com/xorbitsai/inference) | Local model serving | Model server self-hosted cho language, speech và multimodal; có OpenAI-compatible REST, RPC, CLI, WebUI, Docker/Kubernetes examples và thể hiện rõ CPU/Metal/GPU/distributed capabilities. README có migration notes cho breaking change. | Đăng ký **capability card** và compatibility/migration note theo adapter version; Agent chỉ đề xuất provider/runtime đã thỏa platform, hardware, credential và health-check profile. | MotionLoom không trở thành model server, không mở endpoint model nội bộ ra artifact/Dev Lab, không tự chạy Docker/model download từ brief người dùng. |
|
|
20
|
+
| [ModelScope](https://github.com/modelscope/modelscope) | Model registry / MaaS | Unified abstraction cho inference, training, evaluation, export, deployment; model/dataset hub, version/cache management, modular task pipeline và Docker images theo CPU/GPU. README cũng nêu dependency/OS incompatibilities của một số model. | Tách **identity** (provider/model/checkpoint/version/license) khỏi **capability** (task, input constraints, platform, hardware, known limitations), và fail closed khi metadata tối thiểu thiếu. | Không coi registry listing là license/provenance đã xác minh, không nhận “few lines to run” là bằng chứng reproducibility hoặc cross-platform support. |
|
|
21
|
+
| [CogVideo](https://github.com/zai-org/CogVideo) | Video generation | README công bố task T2V/I2V/continuation, model task matrix, resolution/frame divisibility, precision, VRAM/timing benchmark, requirement Python và model-specific license notes. Nó cũng nêu prompt optimization và external model dependence. | Mở rộng `generation-profile` thành matrix bắt buộc: task, model/checkpoint, resolution, frame formula, precision, expected VRAM/time, prompt-transform provider và source URLs. | Không chuyển benchmark tự công bố thành SLA; không biến prompt enhancement bởi external LLM thành `artist_authored` hoặc ẩn provider ở provenance. |
|
|
22
|
+
|
|
23
|
+
## Metadata GitHub tại thời điểm khảo sát
|
|
24
|
+
|
|
25
|
+
Snapshot dưới đây được lấy bằng GitHub CLI lúc **2026-08-16**. Nó chỉ là tín hiệu maintenance/community, không phải chứng nhận security, chất lượng hay tính phù hợp để tích hợp.
|
|
26
|
+
|
|
27
|
+
| Repository | Stars / forks | License GitHub nhận diện | Cập nhật gần nhất lúc khảo sát | Kết luận sàng lọc |
|
|
28
|
+
|---|---:|---|---|---|
|
|
29
|
+
| `coze-dev/coze-studio` | 21,453 / 3,117 | Apache-2.0 | 2026-08-16 | Mature reference cho progressive onboarding và self-host security posture. Không là dependency candidate. |
|
|
30
|
+
| `langgenius/dify` | 152,585 / 24,091 | Other (Dify Open Source License) | 2026-08-16 | Reference tốt cho product observability; cần tránh mọi code/license reuse. |
|
|
31
|
+
| `Comfy-Org/ComfyUI` | 127,883 / 15,057 | GPL-3.0 | 2026-08-16 | Reference tốt cho workflow packaging/manifest; không phù hợp để import code vào package MotionLoom. |
|
|
32
|
+
| `Wan-Video/Wan2.1` | 16,833 / 3,364 | Apache-2.0 | 2026-08-16 | Reference cho generation task/capability matrix và điều kiện runtime. |
|
|
33
|
+
| `Tencent-Hunyuan/HunyuanVideo` | 12,429 / 1,312 | Other | 2026-08-16 | Reference cho disclosure GPU/environment; license và model terms cần review theo từng checkpoint. |
|
|
34
|
+
| `zai-org/CogVideo` | 12,956 / 1,326 | Apache-2.0 | 2026-08-16 | Reference cho matrix resolution/frames/precision/VRAM; không coi benchmark là SLA. |
|
|
35
|
+
| `xorbitsai/inference` | 9,500 / 858 | Apache-2.0 | 2026-08-16 | Reference cho adapter capability/health-check/migration policy; không biến MotionLoom thành model server. |
|
|
36
|
+
| `modelscope/modelscope` | 9,091 / 961 | Apache-2.0 | 2026-08-16 | Reference cho tách model identity khỏi capability; registry listing không thay provenance. |
|
|
37
|
+
|
|
38
|
+
## Decision log sơ bộ
|
|
39
|
+
|
|
40
|
+
| Pattern | Quyết định | Lý do và guardrail MotionLoom |
|
|
41
|
+
|---|---|---|
|
|
42
|
+
| Quick start theo tầng, chỉ mở gate chuyên sâu khi cần | **Áp dụng ngay** | Phù hợp onboarding một-lệnh hiện có; deep gates vẫn fail-closed và không bị ẩn khỏi artifact. |
|
|
43
|
+
| Runtime capability card có adapter/platform/constraint/evidence | **Đã triển khai** | `motionloom capability card --format json` xuất projection chỉ đọc từ registry đã validate; không làm thay provenance, runtime evidence hoặc selection. |
|
|
44
|
+
| Generation profile có model/checkpoint/platform/hardware/constraint | **Hoãn có gate** | Bổ sung thông tin quyết định cho Agent trước khi gọi provider chỉ sau khi có schema, validator, provenance binding và regression; không mở provider credential trong core. |
|
|
45
|
+
| Hash-bound workflow/template manifest và trạng thái `approved/rejected/deprecated` | **Thí nghiệm có gate** | Chỉ áp dụng cho profile/template nội bộ sau khi có schema, validator và regression; không đồng nhất template approval với `production_approved`. |
|
|
46
|
+
| Visual node canvas hoặc dashboard như execution authority | **Từ chối** | Làm mờ artifact identity và mở khả năng thực thi node tùy ý, trái mô hình deterministic review-first. |
|
|
47
|
+
| Nhúng model server, tự tải model, hoặc provider credential vào MotionLoom core | **Từ chối** | Tăng bề mặt attack, chi phí vận hành và rủi ro secret; chỉ dùng adapter capability/card và explicit user setup. |
|
|
48
|
+
| Tuyên bố generated asset “artist-authored” vì prompt/model tốt | **Từ chối** | Vi phạm provider-truthful provenance và human-governed production boundary. |
|
|
49
|
+
|
|
50
|
+
## Cải tiến đã áp dụng
|
|
51
|
+
|
|
52
|
+
Capability card được triển khai như một **projection chỉ đọc** trên `capability-registry.json`, không phải registry hay policy thứ hai. Lệnh `motionloom capability card --format json` xác thực hash evidence trước khi xuất `id`, declared status, adapter version, compatibility, thời điểm kiểm chứng, inputs/outputs, evidence reference, limitations, fallback, risk và side-effect level. Card luôn nhắc Agent gọi selection trước execution; selection mới áp dụng evidence freshness/integrity.
|
|
53
|
+
|
|
54
|
+
| Hạng mục | Trạng thái | Cách kiểm chứng | Guardrail giữ nguyên |
|
|
55
|
+
|---|---|---|---|
|
|
56
|
+
| `motionloom capability card --format json` | Hoàn thành | Thử bằng registry repository thật và npm shortcut `pnpm intelligence:card` | Không chọn runtime, không refresh evidence, không tạo commit/PR. |
|
|
57
|
+
| Validation registry trước export | Hoàn thành | Regression cố ý sửa SHA-256 evidence; card trả lỗi non-zero | Fail-closed khi evidence thiếu, sai hash hoặc thoát repository. |
|
|
58
|
+
| Agent-facing CLI/help, npm script, agent-card, README | Hoàn thành | `pnpm docs:check`, CLI help và regression alias | Chỉ công bố discovery; không nâng `scaffold_only` thành `verified`. |
|
|
59
|
+
| Generation profile model/checkpoint/hardware | Chưa triển khai | Chưa có schema hay validator | Không suy diễn provider/model identity; tiếp tục dùng provenance/intake hiện hữu. |
|
|
60
|
+
| Hash-bound workflow/template recipe | Chưa triển khai | Cần schema riêng, approval-state vocabulary và test | Không đồng nhất template approval với `production_approved`. |
|
|
61
|
+
|
|
62
|
+
Kết quả regression: `pnpm test` pass toàn bộ suite, bao gồm export card, alias CLI và rejection với evidence bị sửa. `pnpm docs:check` và `git diff --check` cũng pass. Không có dependency hay code bên thứ ba nào được thêm từ các repository khảo sát.
|
|
63
|
+
|
|
64
|
+
## Nguồn
|
|
65
|
+
|
|
66
|
+
1. [Coze Studio README và self-host/security guidance](https://github.com/coze-dev/coze-studio)
|
|
67
|
+
2. [Dify README, self-hosting, observability và license notice](https://github.com/langgenius/dify)
|
|
68
|
+
3. [Dify Cloud product overview](https://dify.ai/)
|
|
69
|
+
4. [ComfyUI README: workflow JSON, API, App Mode và offline posture](https://github.com/Comfy-Org/ComfyUI)
|
|
70
|
+
5. [ComfyUI workflow templates: manifest, SHA-256 validation và release filtering](https://github.com/Comfy-Org/workflow_templates)
|
|
71
|
+
6. [Wan2.1 README: task matrix, integration và inference requirements](https://github.com/Wan-Video/Wan2.1)
|
|
72
|
+
7. [HunyuanVideo README: model scope, hardware requirements và test environment](https://github.com/Tencent-Hunyuan/HunyuanVideo)
|
|
73
|
+
8. [Xinference README: local serving, APIs, deployment và migration](https://github.com/xorbitsai/inference)
|
|
74
|
+
9. [ModelScope README: MaaS abstraction, registry, pipeline và platform constraints](https://github.com/modelscope/modelscope)
|
|
75
|
+
10. [CogVideo README: video task/memory matrix, precision, model scope và prompt guidance](https://github.com/zai-org/CogVideo)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "motionloom",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.5.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.",
|
|
@@ -19,6 +19,7 @@
|
|
|
19
19
|
"SKILL.md",
|
|
20
20
|
"LICENSE",
|
|
21
21
|
"agent-card.json",
|
|
22
|
+
"capability-registry.json",
|
|
22
23
|
"bin",
|
|
23
24
|
"scripts",
|
|
24
25
|
"src",
|
|
@@ -142,6 +143,7 @@
|
|
|
142
143
|
"intelligence": "node bin/motionloom.mjs intelligence",
|
|
143
144
|
"intelligence:registry": "node bin/motionloom.mjs intelligence capabilities build --output capability-registry.json",
|
|
144
145
|
"intelligence:registry:check": "node bin/motionloom.mjs intelligence capabilities validate --path capability-registry.json",
|
|
146
|
+
"intelligence:card": "node bin/motionloom.mjs capability card --format json",
|
|
145
147
|
"intelligence:p1": "node bin/motionloom.mjs intelligence",
|
|
146
148
|
"quality:p1": "node bin/motionloom.mjs quality-gate --require-intelligence --require-p1 --require-benchmark",
|
|
147
149
|
"eval:intelligence": "node bin/motionloom.mjs eval-intelligence",
|
package/scripts/attestation.py
CHANGED
|
@@ -41,6 +41,11 @@ def dsse_pae(payload_type: str, body: bytes) -> bytes:
|
|
|
41
41
|
return b"DSSEv1 " + str(len(payload_type_bytes)).encode("ascii") + b" " + payload_type_bytes + b" " + str(len(body)).encode("ascii") + b" " + body
|
|
42
42
|
|
|
43
43
|
|
|
44
|
+
def is_macos_var_alias(path: Path) -> bool:
|
|
45
|
+
"""Allow only macOS's documented lexical alias `/var` -> `/private/var`."""
|
|
46
|
+
return path == Path("/var") and path.is_symlink() and path.resolve() == Path("/private/var")
|
|
47
|
+
|
|
48
|
+
|
|
44
49
|
def reject_symlink_components(path: Path) -> None:
|
|
45
50
|
current = Path(path.anchor) if path.anchor else Path(".")
|
|
46
51
|
for part in path.parts:
|
|
@@ -48,6 +53,9 @@ def reject_symlink_components(path: Path) -> None:
|
|
|
48
53
|
continue
|
|
49
54
|
current = current / part
|
|
50
55
|
if current.is_symlink():
|
|
56
|
+
if is_macos_var_alias(current):
|
|
57
|
+
current = current.resolve()
|
|
58
|
+
continue
|
|
51
59
|
raise ValueError(f"symlink path component is not allowed: {path}")
|
|
52
60
|
|
|
53
61
|
|
|
@@ -34,14 +34,21 @@ def parse_json(path: Path) -> object:
|
|
|
34
34
|
return json.loads(path.read_text(encoding="utf-8"))
|
|
35
35
|
|
|
36
36
|
|
|
37
|
-
def
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
37
|
+
def reject_symlink_within_root(root: Path, relative: Path, label: str) -> None:
|
|
38
|
+
"""Reject links inside an untrusted root, not symlinked system parents.
|
|
39
|
+
|
|
40
|
+
macOS exposes /var as a symlink to /private/var. A fixture rooted in mktemp
|
|
41
|
+
must be valid when the root itself is a real directory, while an evidence
|
|
42
|
+
path must never traverse a symlink at or below that root.
|
|
43
|
+
"""
|
|
44
|
+
root = root.absolute()
|
|
45
|
+
if root.is_symlink():
|
|
46
|
+
raise ValueError(f"symlink root is not allowed: {label}")
|
|
47
|
+
current = root
|
|
48
|
+
for part in relative.parts:
|
|
42
49
|
current = current / part
|
|
43
50
|
if current.is_symlink():
|
|
44
|
-
raise ValueError(f"symlink path component is not allowed: {
|
|
51
|
+
raise ValueError(f"symlink path component is not allowed: {label}")
|
|
45
52
|
|
|
46
53
|
|
|
47
54
|
def safe_relative_file(root: Path, relative: object, label: str) -> Path:
|
|
@@ -53,7 +60,7 @@ def safe_relative_file(root: Path, relative: object, label: str) -> Path:
|
|
|
53
60
|
if any(part in ("", ".", "..") for part in candidate.parts):
|
|
54
61
|
raise ValueError(f"{label} contains unsafe path components")
|
|
55
62
|
joined = root / candidate
|
|
56
|
-
|
|
63
|
+
reject_symlink_within_root(root, candidate, label)
|
|
57
64
|
resolved_root = root.resolve()
|
|
58
65
|
resolved = joined.resolve(strict=False)
|
|
59
66
|
if resolved != resolved_root and resolved_root not in resolved.parents:
|
|
@@ -81,8 +88,10 @@ def verify(scene_dir: Path, task_dir: Path, runtime_evidence_name: str, max_age_
|
|
|
81
88
|
raise ValueError("scene directory name is unsafe")
|
|
82
89
|
if not SAFE_NAME.fullmatch(task_dir.name):
|
|
83
90
|
raise ValueError("task directory name is unsafe")
|
|
84
|
-
|
|
85
|
-
|
|
91
|
+
if scene_dir.is_symlink():
|
|
92
|
+
raise ValueError("scene directory symlink is not allowed")
|
|
93
|
+
if task_dir.is_symlink():
|
|
94
|
+
raise ValueError("task directory symlink is not allowed")
|
|
86
95
|
manifest_path = safe_relative_file(scene_dir, "manifest.json", "manifest.json")
|
|
87
96
|
source_manifest = parse_json(manifest_path)
|
|
88
97
|
if not isinstance(source_manifest, dict):
|
package/scripts/intelligence.py
CHANGED
|
@@ -82,14 +82,22 @@ def safe_relative(value: str) -> Path:
|
|
|
82
82
|
return path
|
|
83
83
|
|
|
84
84
|
|
|
85
|
-
def has_symlink_component(path: Path) -> bool:
|
|
86
|
-
|
|
87
|
-
|
|
85
|
+
def has_symlink_component(path: Path, root: Path) -> bool:
|
|
86
|
+
"""Reject symlinks at or below a trusted root, not system parent aliases."""
|
|
87
|
+
root = root.absolute()
|
|
88
|
+
path = path.absolute()
|
|
89
|
+
try:
|
|
90
|
+
relative = path.relative_to(root)
|
|
91
|
+
except ValueError:
|
|
92
|
+
return True
|
|
93
|
+
if root.is_symlink():
|
|
94
|
+
return True
|
|
95
|
+
current = root
|
|
96
|
+
for component in relative.parts:
|
|
97
|
+
current = current / component
|
|
88
98
|
if current.is_symlink():
|
|
89
99
|
return True
|
|
90
|
-
|
|
91
|
-
return False
|
|
92
|
-
current = current.parent
|
|
100
|
+
return False
|
|
93
101
|
|
|
94
102
|
|
|
95
103
|
def within(root: Path, candidate: Path) -> bool:
|
|
@@ -104,7 +112,7 @@ def task_artifact(task_dir: Path, relative: str) -> dict[str, Any]:
|
|
|
104
112
|
safe = safe_relative(relative)
|
|
105
113
|
raw_path = task_dir / safe
|
|
106
114
|
path = raw_path.resolve()
|
|
107
|
-
if has_symlink_component(raw_path) or not within(task_dir, path) or not path.is_file():
|
|
115
|
+
if has_symlink_component(raw_path, task_dir) or not within(task_dir, path) or not path.is_file():
|
|
108
116
|
raise ValueError(f"artifact is missing or outside task bundle: {relative}")
|
|
109
117
|
return {
|
|
110
118
|
"id": relative.replace("/", ":"),
|
|
@@ -179,7 +187,7 @@ def graph_build(args: argparse.Namespace) -> int:
|
|
|
179
187
|
|
|
180
188
|
task_files = []
|
|
181
189
|
for path in sorted(task_dir.rglob("*")):
|
|
182
|
-
if has_symlink_component(path):
|
|
190
|
+
if has_symlink_component(path, task_dir):
|
|
183
191
|
raise ValueError(f"task bundle cannot contain symlinked artifact: {path.relative_to(task_dir)}")
|
|
184
192
|
if not path.is_file():
|
|
185
193
|
continue
|
|
@@ -363,7 +371,7 @@ def provenance_validate(args: argparse.Namespace) -> int:
|
|
|
363
371
|
if not isinstance(attestation, dict) or not isinstance(attestation.get("steps"), list):
|
|
364
372
|
raise ValueError("provenance attestation must contain steps")
|
|
365
373
|
task_dir = task_dir_from(args.task_dir)
|
|
366
|
-
if has_symlink_component(
|
|
374
|
+
if has_symlink_component(path, task_dir) or not within(task_dir, path):
|
|
367
375
|
raise ValueError("provenance attestation must be a non-symlink file inside task bundle")
|
|
368
376
|
if attestation.get("task_id") != read_json(task_dir / "task.json").get("task_id"):
|
|
369
377
|
raise ValueError("provenance task_id does not match task.json")
|
|
@@ -400,13 +408,13 @@ def capability_build(args: argparse.Namespace) -> int:
|
|
|
400
408
|
if not isinstance(card, dict):
|
|
401
409
|
raise ValueError("agent-card must be an object")
|
|
402
410
|
raw_evidence_path = Path(args.evidence).expanduser() if args.evidence else ROOT / "scripts" / "runtime-adapters.mjs"
|
|
403
|
-
if has_symlink_component(raw_evidence_path):
|
|
404
|
-
raise ValueError("capability evidence cannot traverse symlinks")
|
|
405
411
|
evidence_path = raw_evidence_path.resolve()
|
|
406
412
|
if not evidence_path.is_file():
|
|
407
413
|
raise ValueError(f"capability evidence path does not exist: {evidence_path}")
|
|
408
414
|
if not within(ROOT, evidence_path):
|
|
409
415
|
raise ValueError("capability evidence must be inside the repository")
|
|
416
|
+
if has_symlink_component(raw_evidence_path, ROOT):
|
|
417
|
+
raise ValueError("capability evidence cannot traverse symlinks")
|
|
410
418
|
evidence_ref = evidence_path.relative_to(ROOT).as_posix()
|
|
411
419
|
evidence_kind = args.evidence_kind
|
|
412
420
|
entries = []
|
|
@@ -445,6 +453,13 @@ def capability_build(args: argparse.Namespace) -> int:
|
|
|
445
453
|
def capability_validate(args: argparse.Namespace) -> int:
|
|
446
454
|
registry_path = Path(args.path).expanduser().resolve()
|
|
447
455
|
registry = read_json(registry_path)
|
|
456
|
+
capability_registry_validate(registry)
|
|
457
|
+
ids = [entry.get("id") for entry in registry["capabilities"]]
|
|
458
|
+
print(json.dumps({"status": "valid", "kind": "capability-registry", "capability_count": len(ids)}, ensure_ascii=False))
|
|
459
|
+
return 0
|
|
460
|
+
|
|
461
|
+
|
|
462
|
+
def capability_registry_validate(registry: Any) -> None:
|
|
448
463
|
if not isinstance(registry, dict) or not isinstance(registry.get("capabilities"), list):
|
|
449
464
|
raise ValueError("capability registry must contain capabilities")
|
|
450
465
|
ids = [entry.get("id") for entry in registry["capabilities"]]
|
|
@@ -453,13 +468,61 @@ def capability_validate(args: argparse.Namespace) -> int:
|
|
|
453
468
|
for entry in registry["capabilities"]:
|
|
454
469
|
for evidence in entry.get("evidence", []):
|
|
455
470
|
evidence_path = ROOT / safe_relative(str(evidence.get("path", "")))
|
|
456
|
-
if has_symlink_component(evidence_path) or not within(ROOT, evidence_path):
|
|
471
|
+
if has_symlink_component(evidence_path, ROOT) or not within(ROOT, evidence_path):
|
|
457
472
|
raise ValueError(f"capability evidence escapes repository: {evidence.get('path')}")
|
|
458
473
|
if not evidence_path.is_file():
|
|
459
474
|
raise ValueError(f"capability evidence missing: {evidence.get('path')}")
|
|
460
475
|
if digest_file(evidence_path) != evidence.get("sha256"):
|
|
461
476
|
raise ValueError(f"capability evidence hash mismatch: {evidence.get('path')}")
|
|
462
|
-
|
|
477
|
+
|
|
478
|
+
|
|
479
|
+
def capability_card(args: argparse.Namespace) -> int:
|
|
480
|
+
"""Export a read-only, evidence-bound capability projection for Agents.
|
|
481
|
+
|
|
482
|
+
The card deliberately exposes declared registry state only. It does not
|
|
483
|
+
select a runtime, refresh evidence, grant approval, or change policy.
|
|
484
|
+
Callers must run ``capabilities select`` before execution.
|
|
485
|
+
"""
|
|
486
|
+
registry = read_json(Path(args.registry).expanduser().resolve())
|
|
487
|
+
capability_registry_validate(registry)
|
|
488
|
+
capabilities = []
|
|
489
|
+
for entry in registry["capabilities"]:
|
|
490
|
+
capabilities.append({
|
|
491
|
+
"id": entry.get("id"),
|
|
492
|
+
"kind": entry.get("kind"),
|
|
493
|
+
"declared_status": entry.get("status"),
|
|
494
|
+
"adapter_version": entry.get("adapter_version"),
|
|
495
|
+
"last_verified_at": entry.get("last_verified_at"),
|
|
496
|
+
"inputs": entry.get("inputs", []),
|
|
497
|
+
"outputs": entry.get("outputs", []),
|
|
498
|
+
"compatibility": entry.get("compatibility", {}),
|
|
499
|
+
"evidence": entry.get("evidence", []),
|
|
500
|
+
"limitations": entry.get("limitations", []),
|
|
501
|
+
"fallback": entry.get("fallback", ""),
|
|
502
|
+
"risk_level": entry.get("risk_level"),
|
|
503
|
+
"side_effect_level": entry.get("side_effect_level"),
|
|
504
|
+
})
|
|
505
|
+
card = {
|
|
506
|
+
"schema_version": "0.1",
|
|
507
|
+
"kind": "motionloom-capability-card",
|
|
508
|
+
"registry": {
|
|
509
|
+
"registry_id": registry.get("registry_id"),
|
|
510
|
+
"schema_version": registry.get("schema_version"),
|
|
511
|
+
"generated_at": registry.get("generated_at"),
|
|
512
|
+
"selection_policy": registry.get("selection_policy", {}),
|
|
513
|
+
},
|
|
514
|
+
"capabilities": capabilities,
|
|
515
|
+
"selection": {
|
|
516
|
+
"required": True,
|
|
517
|
+
"command": "motionloom capability select --registry <capability-registry.json> --capability <runtime.id>",
|
|
518
|
+
"note": "This card is discovery metadata. Select validates evidence freshness and integrity before a runtime may be used.",
|
|
519
|
+
},
|
|
520
|
+
"review": {
|
|
521
|
+
"production_approval": "not_derived",
|
|
522
|
+
"attestation_approval": "not_derived",
|
|
523
|
+
},
|
|
524
|
+
}
|
|
525
|
+
print(json.dumps(card, ensure_ascii=False))
|
|
463
526
|
return 0
|
|
464
527
|
|
|
465
528
|
|
|
@@ -488,7 +551,7 @@ def capability_select(args: argparse.Namespace) -> int:
|
|
|
488
551
|
for evidence in entry.get("evidence", []):
|
|
489
552
|
try:
|
|
490
553
|
evidence_path = ROOT / safe_relative(str(evidence.get("path", "")))
|
|
491
|
-
if has_symlink_component(evidence_path) or not within(ROOT, evidence_path) or not evidence_path.is_file() or digest_file(evidence_path) != evidence.get("sha256"):
|
|
554
|
+
if has_symlink_component(evidence_path, ROOT) or not within(ROOT, evidence_path) or not evidence_path.is_file() or digest_file(evidence_path) != evidence.get("sha256"):
|
|
492
555
|
evidence_ok = False
|
|
493
556
|
break
|
|
494
557
|
except (OSError, ValueError):
|
|
@@ -1383,7 +1446,7 @@ def replay_capture(args: argparse.Namespace) -> int:
|
|
|
1383
1446
|
task_rel = task_dir.relative_to(root).as_posix()
|
|
1384
1447
|
records = []
|
|
1385
1448
|
for path in sorted(task_dir.rglob("*")):
|
|
1386
|
-
if has_symlink_component(path):
|
|
1449
|
+
if has_symlink_component(path, task_dir):
|
|
1387
1450
|
raise ValueError(f"task bundle cannot contain symlinked artifact: {path.relative_to(task_dir)}")
|
|
1388
1451
|
if not path.is_file():
|
|
1389
1452
|
continue
|
|
@@ -1417,7 +1480,7 @@ def replay_verify(args: argparse.Namespace) -> int:
|
|
|
1417
1480
|
task_rel = safe_relative(str(bundle.get("task_dir", "")))
|
|
1418
1481
|
task_dir = (root / task_rel).resolve()
|
|
1419
1482
|
task = read_json(task_dir / "task.json")
|
|
1420
|
-
if has_symlink_component(root / task_rel) or not within(root, task_dir) or not task_dir.is_dir():
|
|
1483
|
+
if has_symlink_component(root / task_rel, root) or not within(root, task_dir) or not task_dir.is_dir():
|
|
1421
1484
|
mismatches.append({"path": str(task_rel), "reason": "task_dir_invalid"})
|
|
1422
1485
|
else:
|
|
1423
1486
|
if bundle.get("task_id") != task.get("task_id"):
|
|
@@ -1430,7 +1493,7 @@ def replay_verify(args: argparse.Namespace) -> int:
|
|
|
1430
1493
|
safe = safe_relative(str(record.get("path", "")))
|
|
1431
1494
|
raw_path = root / safe
|
|
1432
1495
|
path = raw_path.resolve()
|
|
1433
|
-
if has_symlink_component(raw_path) or not within(root, path) or "task_dir" in locals() and not within(task_dir, path) or not path.is_file():
|
|
1496
|
+
if has_symlink_component(raw_path, root) or not within(root, path) or "task_dir" in locals() and not within(task_dir, path) or not path.is_file():
|
|
1434
1497
|
mismatches.append({"path": str(safe), "reason": "missing"})
|
|
1435
1498
|
continue
|
|
1436
1499
|
actual = digest_file(path)
|
|
@@ -1449,7 +1512,7 @@ def replay_mismatches(bundle: dict[str, Any], root: Path) -> list[dict[str, Any]
|
|
|
1449
1512
|
safe = safe_relative(str(record.get("path", "")))
|
|
1450
1513
|
raw_path = root / safe
|
|
1451
1514
|
path = raw_path.resolve()
|
|
1452
|
-
if has_symlink_component(raw_path) or not within(root, path) or not path.is_file():
|
|
1515
|
+
if has_symlink_component(raw_path, root) or not within(root, path) or not path.is_file():
|
|
1453
1516
|
mismatches.append({"path": str(safe), "reason": "missing"})
|
|
1454
1517
|
continue
|
|
1455
1518
|
actual = digest_file(path)
|
|
@@ -1563,6 +1626,10 @@ def add_subcommands(parser: argparse.ArgumentParser) -> None:
|
|
|
1563
1626
|
capabilities_select_parser.add_argument("--status", default="verified")
|
|
1564
1627
|
capabilities_select_parser.add_argument("--allow-scaffold-only", action="store_true")
|
|
1565
1628
|
capabilities_select_parser.set_defaults(func=capability_select)
|
|
1629
|
+
capabilities_card_parser = capabilities_sub.add_parser("card", help="export a read-only capability card for Agent discovery")
|
|
1630
|
+
capabilities_card_parser.add_argument("--registry", default=str(ROOT / "capability-registry.json"))
|
|
1631
|
+
capabilities_card_parser.add_argument("--format", choices=["json"], default="json")
|
|
1632
|
+
capabilities_card_parser.set_defaults(func=capability_card)
|
|
1566
1633
|
|
|
1567
1634
|
replay = sub.add_parser("replay", help="capture or verify clean-root integrity replay bundle")
|
|
1568
1635
|
replay_sub = replay.add_subparsers(dest="action", required=True)
|
|
@@ -554,6 +554,35 @@ def test_intelligence_core_contracts():
|
|
|
554
554
|
"--registry", str(registry), "--capability", "runtime.rive",
|
|
555
555
|
], capture_output=True, text=True)
|
|
556
556
|
check("intelligence selects verified runtime", selected.returncode == 0 and '"status": "verified"' in selected.stdout)
|
|
557
|
+
|
|
558
|
+
capability_card = subprocess.run([
|
|
559
|
+
sys.executable, str(intelligence), "capabilities", "card",
|
|
560
|
+
"--registry", str(registry), "--format", "json",
|
|
561
|
+
], capture_output=True, text=True)
|
|
562
|
+
capability_card_data = json.loads(capability_card.stdout) if capability_card.returncode == 0 else {}
|
|
563
|
+
rive_card = next((entry for entry in capability_card_data.get("capabilities", []) if entry.get("id") == "runtime.rive"), {})
|
|
564
|
+
check(
|
|
565
|
+
"intelligence exports read-only capability card",
|
|
566
|
+
capability_card.returncode == 0
|
|
567
|
+
and capability_card_data.get("kind") == "motionloom-capability-card"
|
|
568
|
+
and capability_card_data.get("selection", {}).get("required") is True
|
|
569
|
+
and capability_card_data.get("review", {}).get("production_approval") == "not_derived"
|
|
570
|
+
and rive_card.get("declared_status") == "verified"
|
|
571
|
+
and rive_card.get("last_verified_at"),
|
|
572
|
+
capability_card.stderr.strip(),
|
|
573
|
+
)
|
|
574
|
+
|
|
575
|
+
capability_alias = subprocess.run([
|
|
576
|
+
"node", str(ROOT / "bin/motionloom.mjs"), "capability", "card",
|
|
577
|
+
"--registry", str(registry), "--format", "json",
|
|
578
|
+
], capture_output=True, text=True)
|
|
579
|
+
capability_alias_data = json.loads(capability_alias.stdout) if capability_alias.returncode == 0 else {}
|
|
580
|
+
check(
|
|
581
|
+
"motionloom capability alias exports card",
|
|
582
|
+
capability_alias.returncode == 0 and capability_alias_data.get("kind") == "motionloom-capability-card",
|
|
583
|
+
capability_alias.stderr.strip(),
|
|
584
|
+
)
|
|
585
|
+
|
|
557
586
|
scaffold = subprocess.run([
|
|
558
587
|
sys.executable, str(intelligence), "capabilities", "select",
|
|
559
588
|
"--registry", str(registry), "--capability", "runtime.spine",
|
|
@@ -582,6 +611,11 @@ def test_intelligence_core_contracts():
|
|
|
582
611
|
"--registry", str(tampered_path), "--capability", "runtime.rive",
|
|
583
612
|
], capture_output=True, text=True)
|
|
584
613
|
check("intelligence blocks tampered capability evidence", tampered_select.returncode != 0)
|
|
614
|
+
tampered_card = subprocess.run([
|
|
615
|
+
sys.executable, str(intelligence), "capabilities", "card",
|
|
616
|
+
"--registry", str(tampered_path), "--format", "json",
|
|
617
|
+
], capture_output=True, text=True)
|
|
618
|
+
check("intelligence blocks capability card with tampered evidence", tampered_card.returncode != 0)
|
|
585
619
|
|
|
586
620
|
replay = subprocess.run([
|
|
587
621
|
sys.executable, str(intelligence), "replay", "capture",
|
|
@@ -952,6 +986,16 @@ def test_quality_workflow_rebuilds_replay_after_generated_artifacts():
|
|
|
952
986
|
setup_tests.returncode == 0 and "setup onboarding tests: PASS" in setup_tests.stdout,
|
|
953
987
|
setup_tests.stdout.strip() or setup_tests.stderr.strip(),
|
|
954
988
|
)
|
|
989
|
+
apple_contract_tests = subprocess.run(
|
|
990
|
+
[sys.executable, str(ROOT / "tests/scripts/test_apple_contracts.py")],
|
|
991
|
+
capture_output=True,
|
|
992
|
+
text=True,
|
|
993
|
+
)
|
|
994
|
+
check(
|
|
995
|
+
"Apple review contracts preserve hash binding and human-governed boundaries",
|
|
996
|
+
apple_contract_tests.returncode == 0 and "apple contract tests: PASS" in apple_contract_tests.stdout,
|
|
997
|
+
apple_contract_tests.stdout.strip() or apple_contract_tests.stderr.strip(),
|
|
998
|
+
)
|
|
955
999
|
visual_tests = subprocess.run(
|
|
956
1000
|
[sys.executable, str(ROOT / "tests/scripts/test_visual_truth.py")],
|
|
957
1001
|
capture_output=True,
|