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 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](https://img.shields.io/github/license/lenhonbp/MotionLoom)](LICENSE)
7
7
  [![Node.js 18+](https://img.shields.io/badge/Node.js-18%2B-339933?logo=node.js&logoColor=white)](https://nodejs.org/)
8
8
  [![Python 3.11+](https://img.shields.io/badge/Python-3.11%2B-3776AB?logo=python&logoColor=white)](https://www.python.org/)
9
+ [![Apple](https://github.com/lenhonbp/MotionLoom/actions/workflows/apple.yml/badge.svg)](https://github.com/lenhonbp/MotionLoom/actions/workflows/apple.yml)
9
10
  [![Agent Skills](https://img.shields.io/badge/Agent%20Skill-compatible-5B5BD6)](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:** `motionloom@2.4.0` is the next release target. It adds a truthful `code_authored` runtime-first lane, a Framer Motion end-to-end reference candidate, identity-bound Dev Lab review evidence and task-bundle discovery that binds by declared scene identity rather than an inferred directory name. Verify npm/GitHub publication metadata separately; passing evidence never implies user approval.
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.4.0"
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.0",
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>",
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "schema_version": "1.0",
3
3
  "name": "motionloom",
4
- "version": "2.4.0",
4
+ "version": "2.5.0",
5
5
  "canonical": {
6
6
  "skill": "SKILL.md",
7
7
  "agent_card": "agent-card.json",
@@ -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 script = NODE_COMMANDS[command] || PYTHON_COMMANDS[command];
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
- const delegatedArgs = NODE_COMMANDS[command] && !["setup", "init"].includes(command) ? [command, ...args] : command === "init" ? ["init", ...args] : args;
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.4.0` package from your own computer. The npm password, authenticator code and access token must stay on your computer; never paste them into chat, GitHub issues or repository files.
3
+ This guide publishes the prepared `motionloom@2.5.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.4.0`, public access, and the current package file list. Do not hard-code a historical file count: inspect the JSON output and compare it with the checked-in package allowlist. The prepack hook removes generated Python bytecode before packaging. Do not publish if the dry-run shows private keys, `.env` files, `artifacts/`, `dev-lab/` or `__pycache__/` entries.
51
+ The dry-run should report `motionloom@2.5.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.4.0` becomes immutable on npm after a successful publish. If npm reports that the version already exists, stop and verify the registry instead of trying to overwrite it.
63
+ The version `2.5.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.4.0` and a tarball URL:
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.4.0 name version license dist.tarball dist.shasum --json \
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.4.0
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.4.0",
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",
@@ -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 reject_symlink_components(path: Path) -> None:
38
- current = Path(path.anchor) if path.anchor else Path(".")
39
- for part in path.parts:
40
- if part in (path.anchor, ""):
41
- continue
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: {path}")
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
- reject_symlink_components(joined)
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
- reject_symlink_components(scene_dir)
85
- reject_symlink_components(task_dir)
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):
@@ -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
- current = path
87
- while True:
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
- if current.parent == current:
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(raw_path) or not within(task_dir, path):
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
- print(json.dumps({"status": "valid", "kind": "capability-registry", "capability_count": len(ids)}, ensure_ascii=False))
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,