motionloom 2.0.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/LICENSE +21 -0
- package/README.md +179 -0
- package/SKILL.md +122 -0
- package/agent-card.json +161 -0
- package/assets/library/ATTRIBUTION.md +6 -0
- package/assets/library/README.md +20 -0
- package/assets/library/avatar-base.svg +19 -0
- package/assets/library/error-alert.json +1 -0
- package/assets/library/rive/ATTRIBUTION.md +12 -0
- package/assets/library/rive/state-machine-test.riv +0 -0
- package/assets/library/success-check.json +1 -0
- package/bin/motionloom.mjs +81 -0
- package/docs/BROWSER-REVIEW-E2E.md +78 -0
- package/docs/CATEGORIES.md +16 -0
- package/docs/CHECKLIST.md +31 -0
- package/docs/DEEP-AUDIT-WORKING-NOTES.md +22 -0
- package/docs/FRAMEWORK-SELECTION.md +26 -0
- package/docs/PROJECT-MANIFEST.md +37 -0
- package/docs/ROADMAP-INTELLIGENCE.md +224 -0
- package/docs/audits/1.10.0-attestation-research-notes.md +19 -0
- package/docs/audits/1.8.0-trust-boundary-hardening.md +56 -0
- package/docs/audits/1.9.0-evidence-interoperability-threat-model.md +37 -0
- package/docs/audits/2.0.0-attestation-acceptance.md +30 -0
- package/docs/releases/1.5.0.md +25 -0
- package/docs/releases/1.6.0.md +27 -0
- package/docs/releases/1.7.0.md +23 -0
- package/docs/releases/1.8.0.md +23 -0
- package/docs/releases/1.9.0.md +21 -0
- package/docs/releases/2.0.0.md +21 -0
- package/docs/releases/npm-publish-from-workstation.md +88 -0
- package/docs/research/AGENT-PROTOCOL-FINDINGS.md +43 -0
- package/examples/report-demo/REPORT.md +50 -0
- package/examples/report-demo/artifact-manifest.json +25 -0
- package/examples/report-demo/decision-log.jsonl +0 -0
- package/examples/report-demo/execution-report.json +70 -0
- package/examples/report-demo/handoff.json +22 -0
- package/examples/report-demo/issue-register.json +5 -0
- package/examples/report-demo/task.json +13 -0
- package/package.json +95 -0
- package/project-context.example.json +26 -0
- package/references/browser-review-contract.md +32 -0
- package/references/dotlottie-source-notes.md +21 -0
- package/references/intelligence-core.md +98 -0
- package/references/reporting-contract.md +38 -0
- package/references/runtime-capability.md +12 -0
- package/references/signed-attestation.md +31 -0
- package/schemas/artifact-manifest.schema.json +22 -0
- package/schemas/browser-review-candidate.schema.json +24 -0
- package/schemas/capability-registry.schema.json +61 -0
- package/schemas/continuity-report.schema.json +52 -0
- package/schemas/evidence-verifier-report.schema.json +35 -0
- package/schemas/execution-report.schema.json +35 -0
- package/schemas/fix-plan.schema.json +44 -0
- package/schemas/handoff.schema.json +19 -0
- package/schemas/motion-ir.schema.json +74 -0
- package/schemas/project-graph.schema.json +70 -0
- package/schemas/provenance.schema.json +76 -0
- package/schemas/runtime-evidence.schema.json +48 -0
- package/schemas/runtime-telemetry.schema.json +50 -0
- package/schemas/scene-manifest.schema.json +45 -0
- package/schemas/semantic-benchmark.schema.json +26 -0
- package/schemas/semantic-lint-report.schema.json +48 -0
- package/schemas/signed-attestation.schema.json +95 -0
- package/schemas/task.schema.json +39 -0
- package/schemas/trust-policy.schema.json +53 -0
- package/scripts/analyze.sh +13 -0
- package/scripts/attestation-keygen.py +63 -0
- package/scripts/attestation-verifier.py +178 -0
- package/scripts/attestation.py +288 -0
- package/scripts/capture-runtime-telemetry.sh +37 -0
- package/scripts/devlab.sh +77 -0
- package/scripts/eval-intelligence.py +377 -0
- package/scripts/evidence-verifier.py +222 -0
- package/scripts/fetch-library.sh +57 -0
- package/scripts/intelligence.py +1543 -0
- package/scripts/manifest.py +61 -0
- package/scripts/pr.sh +103 -0
- package/scripts/quality-gate.py +378 -0
- package/scripts/render-node.mjs +53 -0
- package/scripts/render.sh +37 -0
- package/scripts/report-contract.py +181 -0
- package/scripts/report.py +588 -0
- package/scripts/review-hook.py +199 -0
- package/scripts/runtime-adapters.mjs +187 -0
- package/scripts/skill-doctor.py +150 -0
- package/scripts/to-dotlottie.mjs +99 -0
- package/scripts/to-dotlottie.sh +25 -0
- package/scripts/validate-lottie.py +102 -0
- package/src/core/analyzer.py +226 -0
- package/src/core/snapshot.py +124 -0
- package/src/core/spec.py +240 -0
- package/src/output/browser-review-smoke/animation.json +57 -0
- package/src/output/browser-review-smoke/browser-review.json +21 -0
- package/src/output/browser-review-smoke/manifest.json +22 -0
- package/src/output/browser-review-smoke/motion-spec.json +28 -0
- package/src/output/browser-review-smoke/snapshot/.render-meta.json +10 -0
- package/src/output/browser-review-smoke/snapshot/frame-00.png +0 -0
- package/src/output/browser-review-smoke/snapshot/frame-100.png +0 -0
- package/src/output/browser-review-smoke/snapshot/frame-50.png +0 -0
- package/src/rig/README.md +35 -0
- package/src/rig/cutout_rig.py +211 -0
- package/templates/framer-motion/ui-micro.tsx +50 -0
- package/templates/gsap/scroll-scene.js +54 -0
- package/templates/lottie/README.md +21 -0
- package/templates/lottie/react-component.tsx +82 -0
- package/templates/lottie/scaffold/animation.json +57 -0
- package/templates/lottie/scaffold/character-rig.svg +19 -0
- package/templates/lottie/vanilla.js +68 -0
- package/templates/rive/README.md +36 -0
- package/tests/evals/intelligence-cases.json +131 -0
- package/tests/scripts/run_tests.py +843 -0
- package/tests/scripts/test_attestation.py +172 -0
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
set -euo pipefail
|
|
3
|
+
|
|
4
|
+
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
|
5
|
+
SCENE="${1:?scene is required}"
|
|
6
|
+
TASK_DIR="${2:?task directory is required}"
|
|
7
|
+
|
|
8
|
+
if [[ ! "$SCENE" =~ ^[A-Za-z0-9._-]+$ ]]; then
|
|
9
|
+
echo "capture-runtime-telemetry: unsafe scene identifier: $SCENE" >&2
|
|
10
|
+
exit 2
|
|
11
|
+
fi
|
|
12
|
+
if [[ ! -f "$ROOT/src/output/$SCENE/manifest.json" || ! -f "$ROOT/$TASK_DIR/task.json" ]]; then
|
|
13
|
+
echo "capture-runtime-telemetry: missing scene manifest or task.json" >&2
|
|
14
|
+
exit 2
|
|
15
|
+
fi
|
|
16
|
+
|
|
17
|
+
MANIFEST="$ROOT/src/output/$SCENE/manifest.json"
|
|
18
|
+
TASK_JSON="$ROOT/$TASK_DIR/task.json"
|
|
19
|
+
SOURCE_FILE="$(python3 -c 'import json,sys; print(json.load(open(sys.argv[1], encoding="utf-8"))["file"])' "$MANIFEST")"
|
|
20
|
+
TASK_ID="$(python3 -c 'import json,sys; print(json.load(open(sys.argv[1], encoding="utf-8"))["task_id"])' "$TASK_JSON")"
|
|
21
|
+
OUTPUT_DIR="$ROOT/$TASK_DIR/runtime-adapters"
|
|
22
|
+
|
|
23
|
+
rm -rf "$OUTPUT_DIR"
|
|
24
|
+
RUNTIME_EVIDENCE_DIR="$OUTPUT_DIR" \
|
|
25
|
+
RUNTIME_SCENE="$SCENE" \
|
|
26
|
+
RUNTIME_TASK_ID="$TASK_ID" \
|
|
27
|
+
RUNTIME_SOURCE_PATH="$ROOT/src/output/$SCENE/$SOURCE_FILE" \
|
|
28
|
+
RUNTIME_MANIFEST_PATH="$MANIFEST" \
|
|
29
|
+
RUNTIME_MOTION_IR_PATH="$ROOT/$TASK_DIR/motion-ir.json" \
|
|
30
|
+
npm run runtime:test
|
|
31
|
+
|
|
32
|
+
python3 "$ROOT/scripts/evidence-verifier.py" \
|
|
33
|
+
--scene-dir "$ROOT/src/output/$SCENE" \
|
|
34
|
+
--task-dir "$ROOT/$TASK_DIR" \
|
|
35
|
+
--runtime-evidence runtime-adapters/runtime-evidence.json \
|
|
36
|
+
--max-age-days 1 \
|
|
37
|
+
--output "$ROOT/$TASK_DIR/evidence-verifier-report.json"
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# devlab.sh — Step 5 of the pipeline: boot the Dev Lab for a scene.
|
|
3
|
+
# Copies the scene into the Dev Lab's public scenes folder, installs its
|
|
4
|
+
# dependencies, and serves the workbench so the user can scrub, inspect,
|
|
5
|
+
# run the checklist and iterate fixes before confirming.
|
|
6
|
+
#
|
|
7
|
+
# Usage: bash scripts/devlab.sh <scene> [mode] [task-dir]
|
|
8
|
+
set -euo pipefail
|
|
9
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
10
|
+
REPO="$(dirname "$SCRIPT_DIR")"
|
|
11
|
+
SCENE="${1:?usage: bash scripts/devlab.sh <scene>}"
|
|
12
|
+
MODE="${2:-serve}"
|
|
13
|
+
TASK_DIR="${3:-}"
|
|
14
|
+
SCENE_DIR="$REPO/src/output/$SCENE"
|
|
15
|
+
LAB="$REPO/dev-lab"
|
|
16
|
+
|
|
17
|
+
if [[ ! "$SCENE" =~ ^[A-Za-z0-9._-]+$ || "$SCENE" == "." || "$SCENE" == ".." ]]; then
|
|
18
|
+
echo "error: scene id contains unsafe path characters: $SCENE" >&2
|
|
19
|
+
exit 1
|
|
20
|
+
fi
|
|
21
|
+
|
|
22
|
+
if [ ! -d "$SCENE_DIR" ]; then
|
|
23
|
+
echo "error: scene directory not found: $SCENE_DIR"
|
|
24
|
+
echo "run the generator first and render the scene into src/output/<scene>/"
|
|
25
|
+
exit 1
|
|
26
|
+
fi
|
|
27
|
+
|
|
28
|
+
if [ ! -f "$SCENE_DIR/browser-review.json" ]; then
|
|
29
|
+
echo "error: browser-review.json is required; run scripts/review-hook.py prepare after runtime render" >&2
|
|
30
|
+
exit 1
|
|
31
|
+
fi
|
|
32
|
+
|
|
33
|
+
rm -rf "$LAB/public/scenes/$SCENE"
|
|
34
|
+
mkdir -p "$LAB/public/scenes/$SCENE"
|
|
35
|
+
cp -R "$SCENE_DIR"/. "$LAB/public/scenes/$SCENE/"
|
|
36
|
+
|
|
37
|
+
if [ -n "$TASK_DIR" ]; then
|
|
38
|
+
TASK_DIR="$(cd "$TASK_DIR" && pwd -P)"
|
|
39
|
+
case "$TASK_DIR" in
|
|
40
|
+
"$REPO"/*) ;;
|
|
41
|
+
*) echo "error: task bundle must be inside the repository" >&2; exit 1 ;;
|
|
42
|
+
esac
|
|
43
|
+
TASK_ID="$(python3 - "$TASK_DIR/task.json" <<'PY'
|
|
44
|
+
import json, sys
|
|
45
|
+
from pathlib import Path
|
|
46
|
+
data = json.loads(Path(sys.argv[1]).read_text(encoding="utf-8"))
|
|
47
|
+
print(data.get("task_id", ""))
|
|
48
|
+
PY
|
|
49
|
+
)"
|
|
50
|
+
if [[ -z "$TASK_ID" || ! "$TASK_ID" =~ ^[A-Za-z0-9._-]+$ || "$TASK_ID" == "." || "$TASK_ID" == ".." ]]; then
|
|
51
|
+
echo "error: task bundle has unsafe or missing task_id" >&2
|
|
52
|
+
exit 1
|
|
53
|
+
fi
|
|
54
|
+
rm -rf "$LAB/public/tasks/$TASK_ID"
|
|
55
|
+
mkdir -p "$LAB/public/tasks/$TASK_ID"
|
|
56
|
+
for artifact in task.json browser-review.json review.json execution-report.json handoff.json quality-report.json artifact-manifest.json issue-register.json decision-log.jsonl project-graph.json provenance.json capability-registry.json motion-ir.json replay-bundle.json semantic-lint-report.json continuity-report.json fix-plan.json browser-observation.md; do
|
|
57
|
+
if [ -f "$TASK_DIR/$artifact" ]; then
|
|
58
|
+
cp "$TASK_DIR/$artifact" "$LAB/public/tasks/$TASK_ID/$artifact"
|
|
59
|
+
fi
|
|
60
|
+
done
|
|
61
|
+
fi
|
|
62
|
+
|
|
63
|
+
if [ "$MODE" = "--prepare-only" ]; then
|
|
64
|
+
echo "== Dev Lab scene prepared: $LAB/public/scenes/$SCENE =="
|
|
65
|
+
if [ -n "$TASK_DIR" ]; then echo "== Dev Lab task bundle prepared: $LAB/public/tasks/$TASK_ID =="; fi
|
|
66
|
+
exit 0
|
|
67
|
+
fi
|
|
68
|
+
|
|
69
|
+
if [ ! -d "$LAB/node_modules" ]; then
|
|
70
|
+
echo "== installing Dev Lab dependencies (first run) =="
|
|
71
|
+
(cd "$LAB" && pnpm install --silent)
|
|
72
|
+
fi
|
|
73
|
+
|
|
74
|
+
PORT="${PORT:-3300}"
|
|
75
|
+
echo "== Dev Lab ready for scene: $SCENE =="
|
|
76
|
+
echo " http://localhost:${PORT}/?scene=$SCENE (use the candidate URL emitted by review-hook.py for task-bound review)"
|
|
77
|
+
exec python3 -m http.server "$PORT" --directory "$LAB/public"
|
|
@@ -0,0 +1,377 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Run the deterministic Intelligence Core eval corpus.
|
|
3
|
+
|
|
4
|
+
The runner intentionally uses clean temporary roots and subprocesses the same
|
|
5
|
+
CLI entrypoints used by Agents and CI. It reports each case as pass/fail and
|
|
6
|
+
never turns a negative case into a successful acceptance.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
import base64
|
|
12
|
+
import hashlib
|
|
13
|
+
import json
|
|
14
|
+
import shutil
|
|
15
|
+
import subprocess
|
|
16
|
+
import sys
|
|
17
|
+
import tempfile
|
|
18
|
+
from datetime import datetime, timedelta, timezone
|
|
19
|
+
from pathlib import Path
|
|
20
|
+
|
|
21
|
+
from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PrivateKey
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
ROOT = Path(__file__).resolve().parents[1]
|
|
25
|
+
INTELLIGENCE = ROOT / "scripts/intelligence.py"
|
|
26
|
+
REPORT = ROOT / "scripts/report.py"
|
|
27
|
+
VERIFIER = ROOT / "scripts/evidence-verifier.py"
|
|
28
|
+
ATTESTATION = ROOT / "scripts/attestation.py"
|
|
29
|
+
ATTESTATION_VERIFIER = ROOT / "scripts/attestation-verifier.py"
|
|
30
|
+
CASES = ROOT / "tests/evals/intelligence-cases.json"
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def invoke(args: list[str], cwd: Path | None = None) -> subprocess.CompletedProcess[str]:
|
|
34
|
+
return subprocess.run([sys.executable, *args], cwd=cwd or ROOT, capture_output=True, text=True)
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def record(results: list[dict[str, object]], case_id: str, passed: bool, detail: str = "") -> None:
|
|
38
|
+
results.append({"id": case_id, "status": "pass" if passed else "fail", "detail": detail.strip()[-500:]})
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def iso(value: datetime) -> str:
|
|
42
|
+
return value.astimezone(timezone.utc).isoformat().replace("+00:00", "Z")
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def attestation_policy(key_id: str, public_key: bytes, status: str = "active") -> dict[str, object]:
|
|
46
|
+
now = datetime.now(timezone.utc)
|
|
47
|
+
key: dict[str, object] = {
|
|
48
|
+
"key_id": key_id,
|
|
49
|
+
"algorithm": "ed25519",
|
|
50
|
+
"public_key_base64": base64.b64encode(public_key).decode("ascii"),
|
|
51
|
+
"status": status,
|
|
52
|
+
"valid_from": iso(now - timedelta(days=1)),
|
|
53
|
+
}
|
|
54
|
+
if status == "revoked":
|
|
55
|
+
key["revoked_at"] = iso(now - timedelta(hours=1))
|
|
56
|
+
key["revocation_reason"] = "eval fixture revocation"
|
|
57
|
+
return {
|
|
58
|
+
"schema_version": "1.0",
|
|
59
|
+
"policy_id": "motionloom-eval-policy",
|
|
60
|
+
"trust_domain": "https://motionloom.dev/trust/eval",
|
|
61
|
+
"keys": [key],
|
|
62
|
+
"rotation": {"max_key_age_days": 90, "overlap_days": 7, "require_active_signer": True},
|
|
63
|
+
"revocation": {"mode": "local-policy", "fail_closed": True, "sources": ["eval-fixture"]},
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def run_attestation_cases(root: Path, results: list[dict[str, object]]) -> None:
|
|
68
|
+
"""Exercise the signed-attestation boundary with stable verifier outcomes."""
|
|
69
|
+
case_root = root / "attestation-eval"
|
|
70
|
+
case_root.mkdir(parents=True, exist_ok=True)
|
|
71
|
+
private_key = Ed25519PrivateKey.generate()
|
|
72
|
+
key_id = "eval-signer-v1"
|
|
73
|
+
private_key_path = case_root / "private.key"
|
|
74
|
+
private_key_path.write_text(base64.b64encode(private_key.private_bytes_raw()).decode("ascii") + "\n", encoding="utf-8")
|
|
75
|
+
policy_path = case_root / "trust-policy.json"
|
|
76
|
+
policy_path.write_text(json.dumps(attestation_policy(key_id, private_key.public_key().public_bytes_raw()), indent=2) + "\n", encoding="utf-8")
|
|
77
|
+
statement = {
|
|
78
|
+
"type": "https://motionloom.dev/attestation/v1",
|
|
79
|
+
"predicate_type": "https://motionloom.dev/predicate/animation-evidence/v1",
|
|
80
|
+
"subject": [{"name": "eval-scene", "digest": {"sha256": "a" * 64}}],
|
|
81
|
+
"predicate": {
|
|
82
|
+
"task_id": "attestation-eval-task",
|
|
83
|
+
"scene": "eval-scene",
|
|
84
|
+
"context_hash": "b" * 64,
|
|
85
|
+
"source_sha256": "c" * 64,
|
|
86
|
+
"manifest_sha256": "d" * 64,
|
|
87
|
+
"motion_ir_sha256": "e" * 64,
|
|
88
|
+
"evidence": {
|
|
89
|
+
"runtime_evidence_sha256": "f" * 64,
|
|
90
|
+
"runtime_telemetry_sha256": "0" * 64,
|
|
91
|
+
"verifier_report_sha256": "1" * 64,
|
|
92
|
+
},
|
|
93
|
+
"provenance_chain_hash": "2" * 64,
|
|
94
|
+
"policy_version": "1.0",
|
|
95
|
+
"generated_at": iso(datetime.now(timezone.utc)),
|
|
96
|
+
"builder": {"name": "motionloom-eval", "version": "1.0.0"},
|
|
97
|
+
},
|
|
98
|
+
}
|
|
99
|
+
statement_path = case_root / "statement.json"
|
|
100
|
+
statement_path.write_text(json.dumps(statement, indent=2) + "\n", encoding="utf-8")
|
|
101
|
+
bundle_path = case_root / "attestation.json"
|
|
102
|
+
built = invoke([
|
|
103
|
+
str(ATTESTATION), "build", "--statement", str(statement_path), "--private-key", str(private_key_path),
|
|
104
|
+
"--key-id", key_id, "--output", str(bundle_path),
|
|
105
|
+
])
|
|
106
|
+
clean = invoke([
|
|
107
|
+
str(ATTESTATION_VERIFIER), "--attestation", str(bundle_path), "--trust-policy", str(policy_path),
|
|
108
|
+
"--expected-task-id", "attestation-eval-task", "--expected-scene", "eval-scene",
|
|
109
|
+
])
|
|
110
|
+
clean_doc = json.loads(clean.stdout) if clean.stdout.strip().startswith("{") else {}
|
|
111
|
+
record(results, "p2-attestation-clean", built.returncode == 0 and clean.returncode == 0 and clean_doc.get("verified") is True and clean_doc.get("approval") is False, clean.stdout + clean.stderr)
|
|
112
|
+
|
|
113
|
+
tampered = json.loads(bundle_path.read_text(encoding="utf-8"))
|
|
114
|
+
tampered["envelope"]["payload_base64"] = base64.b64encode(b"tampered").decode("ascii")
|
|
115
|
+
tampered_path = case_root / "tampered.json"
|
|
116
|
+
tampered_path.write_text(json.dumps(tampered, indent=2) + "\n", encoding="utf-8")
|
|
117
|
+
tamper_result = invoke([str(ATTESTATION_VERIFIER), "--attestation", str(tampered_path), "--trust-policy", str(policy_path)])
|
|
118
|
+
record(results, "p2-attestation-payload-tamper", tamper_result.returncode == 11, tamper_result.stdout + tamper_result.stderr)
|
|
119
|
+
|
|
120
|
+
binding_result = invoke([
|
|
121
|
+
str(ATTESTATION_VERIFIER), "--attestation", str(bundle_path), "--trust-policy", str(policy_path),
|
|
122
|
+
"--expected-task-id", "foreign-task",
|
|
123
|
+
])
|
|
124
|
+
record(results, "p2-attestation-binding-mismatch", binding_result.returncode == 14, binding_result.stdout + binding_result.stderr)
|
|
125
|
+
|
|
126
|
+
revoked_policy = case_root / "revoked-policy.json"
|
|
127
|
+
revoked_policy.write_text(json.dumps(attestation_policy(key_id, private_key.public_key().public_bytes_raw(), "revoked"), indent=2) + "\n", encoding="utf-8")
|
|
128
|
+
revoked_result = invoke([str(ATTESTATION_VERIFIER), "--attestation", str(bundle_path), "--trust-policy", str(revoked_policy)])
|
|
129
|
+
record(results, "p2-attestation-revoked-signer", revoked_result.returncode == 13, revoked_result.stdout + revoked_result.stderr)
|
|
130
|
+
|
|
131
|
+
unknown_policy = case_root / "unknown-policy.json"
|
|
132
|
+
unknown_policy.write_text(json.dumps(attestation_policy("other-signer-v1", private_key.public_key().public_bytes_raw()), indent=2) + "\n", encoding="utf-8")
|
|
133
|
+
unknown_result = invoke([str(ATTESTATION_VERIFIER), "--attestation", str(bundle_path), "--trust-policy", str(unknown_policy)])
|
|
134
|
+
record(results, "p2-attestation-unknown-signer", unknown_result.returncode == 13, unknown_result.stdout + unknown_result.stderr)
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
def run_p1_cases(root: Path, task_dir: Path, results: list[dict[str, object]]) -> None:
|
|
138
|
+
"""Exercise P1 semantic, continuity and feedback contracts in isolated copies."""
|
|
139
|
+
lint_task = root / "p1-human-review"
|
|
140
|
+
shutil.copytree(task_dir, lint_task)
|
|
141
|
+
lint_result = invoke([str(INTELLIGENCE), "semantic-lint", "build", "--task-dir", str(lint_task)])
|
|
142
|
+
lint_data: dict[str, object] = {}
|
|
143
|
+
if (lint_task / "semantic-lint-report.json").is_file():
|
|
144
|
+
lint_data = json.loads((lint_task / "semantic-lint-report.json").read_text(encoding="utf-8"))
|
|
145
|
+
human_warning = any(
|
|
146
|
+
isinstance(item, dict) and item.get("basis") == "human" and not item.get("approval_blocking")
|
|
147
|
+
for item in lint_data.get("findings", []) if isinstance(lint_data.get("findings"), list)
|
|
148
|
+
)
|
|
149
|
+
record(results, "p1-human-review-warning-preserved", lint_result.returncode == 0 and human_warning, lint_result.stdout + lint_result.stderr)
|
|
150
|
+
|
|
151
|
+
generic_task = root / "p1-generic-intent"
|
|
152
|
+
shutil.copytree(task_dir, generic_task)
|
|
153
|
+
generic_ir_path = generic_task / "motion-ir.json"
|
|
154
|
+
generic_ir = json.loads(generic_ir_path.read_text(encoding="utf-8"))
|
|
155
|
+
generic_ir["intent"] = "motion"
|
|
156
|
+
generic_ir_path.write_text(json.dumps(generic_ir, indent=2) + "\n", encoding="utf-8")
|
|
157
|
+
generic_result = invoke([str(INTELLIGENCE), "semantic-lint", "build", "--task-dir", str(generic_task)])
|
|
158
|
+
generic_report = json.loads((generic_task / "semantic-lint-report.json").read_text(encoding="utf-8"))
|
|
159
|
+
generic_warning = any(item.get("id") == "intent-low-specificity" for item in generic_report.get("findings", []))
|
|
160
|
+
record(results, "p1-generic-intent-warning", generic_result.returncode == 0 and generic_warning and generic_report.get("status") == "warn", generic_result.stdout + generic_result.stderr)
|
|
161
|
+
|
|
162
|
+
continuity_a = root / "p1-continuity-a"
|
|
163
|
+
continuity_b = root / "p1-continuity-b"
|
|
164
|
+
shutil.copytree(task_dir, continuity_a)
|
|
165
|
+
shutil.copytree(task_dir, continuity_b)
|
|
166
|
+
task_b_path = continuity_b / "task.json"
|
|
167
|
+
task_b = json.loads(task_b_path.read_text(encoding="utf-8"))
|
|
168
|
+
task_b["task_id"] = "professional-review-followup"
|
|
169
|
+
task_b["scene"] = "browser-review-followup"
|
|
170
|
+
task_b["scene_order"] = 1
|
|
171
|
+
task_b_path.write_text(json.dumps(task_b, indent=2) + "\n", encoding="utf-8")
|
|
172
|
+
ir_b_path = continuity_b / "motion-ir.json"
|
|
173
|
+
ir_b = json.loads(ir_b_path.read_text(encoding="utf-8"))
|
|
174
|
+
ir_b["task_id"] = task_b["task_id"]
|
|
175
|
+
ir_b["scene"] = task_b["scene"]
|
|
176
|
+
ir_b["context_hash"] = "f" * 64
|
|
177
|
+
ir_b_path.write_text(json.dumps(ir_b, indent=2) + "\n", encoding="utf-8")
|
|
178
|
+
continuity_output = root / "p1-continuity-drift.json"
|
|
179
|
+
continuity_result = invoke([
|
|
180
|
+
str(INTELLIGENCE), "continuity", "build", "--task-dirs", str(continuity_a), str(continuity_b), "--output", str(continuity_output)
|
|
181
|
+
])
|
|
182
|
+
continuity_report = json.loads(continuity_output.read_text(encoding="utf-8")) if continuity_output.is_file() else {}
|
|
183
|
+
transitions = continuity_report.get("transitions", []) if isinstance(continuity_report, dict) else []
|
|
184
|
+
drift_found = bool(transitions) and "context hash changes between adjacent scenes" in transitions[0].get("findings", [])
|
|
185
|
+
record(results, "p1-continuity-context-drift", continuity_result.returncode == 0 and continuity_report.get("status") == "warn" and drift_found, continuity_result.stdout + continuity_result.stderr)
|
|
186
|
+
|
|
187
|
+
fix_plan_path = task_dir / "fix-plan.json"
|
|
188
|
+
fix_plan_result = invoke([str(INTELLIGENCE), "fix-plan", "validate", "--path", str(fix_plan_path)])
|
|
189
|
+
fix_plan = json.loads(fix_plan_path.read_text(encoding="utf-8")) if fix_plan_path.is_file() else {}
|
|
190
|
+
selective = any(
|
|
191
|
+
isinstance(issue, dict) and "lint" in issue.get("rerun_scope", []) and issue.get("finding_ref")
|
|
192
|
+
for issue in fix_plan.get("issues", []) if isinstance(fix_plan.get("issues"), list)
|
|
193
|
+
)
|
|
194
|
+
handoff = json.loads((task_dir / "handoff.json").read_text(encoding="utf-8"))
|
|
195
|
+
synced = handoff.get("fix_plan", {}).get("path") == "fix-plan.json" and "semantic-lint-report.json" in handoff.get("required_artifacts", [])
|
|
196
|
+
record(results, "p1-fix-plan-selective-rerun", fix_plan_result.returncode == 0 and selective and synced, fix_plan_result.stdout + fix_plan_result.stderr)
|
|
197
|
+
|
|
198
|
+
|
|
199
|
+
def run_performance_perceptual_cases(root: Path, task_dir: Path, results: list[dict[str, object]]) -> None:
|
|
200
|
+
"""Exercise non-blocking performance/perceptual findings and the benchmark contract."""
|
|
201
|
+
duration_task = root / "p1-perf-duration-budget"
|
|
202
|
+
shutil.copytree(task_dir, duration_task)
|
|
203
|
+
duration_ir_path = duration_task / "motion-ir.json"
|
|
204
|
+
duration_ir = json.loads(duration_ir_path.read_text(encoding="utf-8"))
|
|
205
|
+
duration_ir["duration_ms"] = 600
|
|
206
|
+
duration_ir_path.write_text(json.dumps(duration_ir, indent=2) + "\n", encoding="utf-8")
|
|
207
|
+
duration_result = invoke([str(INTELLIGENCE), "semantic-lint", "build", "--task-dir", str(duration_task)])
|
|
208
|
+
duration_report = json.loads((duration_task / "semantic-lint-report.json").read_text(encoding="utf-8")) if (duration_task / "semantic-lint-report.json").is_file() else {}
|
|
209
|
+
duration_warning = any(item.get("id") == "perf-animation-budget" and item.get("severity") == "warning" and not item.get("approval_blocking") for item in duration_report.get("findings", []))
|
|
210
|
+
record(results, "p1-perf-duration-budget-warning", duration_result.returncode == 0 and duration_warning, duration_result.stdout + duration_result.stderr)
|
|
211
|
+
|
|
212
|
+
fps_task = root / "p1-perf-fps"
|
|
213
|
+
shutil.copytree(task_dir, fps_task)
|
|
214
|
+
fps_ir_path = fps_task / "motion-ir.json"
|
|
215
|
+
fps_ir = json.loads(fps_ir_path.read_text(encoding="utf-8"))
|
|
216
|
+
fps_ir["fps"] = 24
|
|
217
|
+
fps_ir_path.write_text(json.dumps(fps_ir, indent=2) + "\n", encoding="utf-8")
|
|
218
|
+
fps_result = invoke([str(INTELLIGENCE), "semantic-lint", "build", "--task-dir", str(fps_task)])
|
|
219
|
+
fps_report = json.loads((fps_task / "semantic-lint-report.json").read_text(encoding="utf-8")) if (fps_task / "semantic-lint-report.json").is_file() else {}
|
|
220
|
+
fps_warning = any(item.get("id") == "perf-frame-rate" and item.get("severity") == "warning" for item in fps_report.get("findings", []))
|
|
221
|
+
record(results, "p1-perf-fps-warning", fps_result.returncode == 0 and fps_warning, fps_result.stdout + fps_result.stderr)
|
|
222
|
+
|
|
223
|
+
easing_task = root / "p1-perceptual-easing"
|
|
224
|
+
shutil.copytree(task_dir, easing_task)
|
|
225
|
+
easing_ir_path = easing_task / "motion-ir.json"
|
|
226
|
+
easing_ir = json.loads(easing_ir_path.read_text(encoding="utf-8"))
|
|
227
|
+
for keyframe in easing_ir.get("tracks", [])[0].get("keyframes", []):
|
|
228
|
+
keyframe["easing"] = "linear"
|
|
229
|
+
easing_ir_path.write_text(json.dumps(easing_ir, indent=2) + "\n", encoding="utf-8")
|
|
230
|
+
easing_result = invoke([str(INTELLIGENCE), "semantic-lint", "build", "--task-dir", str(easing_task)])
|
|
231
|
+
easing_report = json.loads((easing_task / "semantic-lint-report.json").read_text(encoding="utf-8")) if (easing_task / "semantic-lint-report.json").is_file() else {}
|
|
232
|
+
easing_warning = any(item.get("id") == "perceptual-easing-linear" and item.get("severity") == "warning" for item in easing_report.get("findings", []))
|
|
233
|
+
record(results, "p1-perceptual-easing-linear-warning", easing_result.returncode == 0 and easing_warning, easing_result.stdout + easing_result.stderr)
|
|
234
|
+
|
|
235
|
+
reduced_task = root / "p1-perceptual-reduced-motion"
|
|
236
|
+
shutil.copytree(task_dir, reduced_task)
|
|
237
|
+
reduced_ir_path = reduced_task / "motion-ir.json"
|
|
238
|
+
reduced_ir = json.loads(reduced_ir_path.read_text(encoding="utf-8"))
|
|
239
|
+
reduced_ir.setdefault("accessibility", {})["reduced_motion"] = "none"
|
|
240
|
+
reduced_ir_path.write_text(json.dumps(reduced_ir, indent=2) + "\n", encoding="utf-8")
|
|
241
|
+
reduced_result = invoke([str(INTELLIGENCE), "semantic-lint", "build", "--task-dir", str(reduced_task)])
|
|
242
|
+
reduced_report = json.loads((reduced_task / "semantic-lint-report.json").read_text(encoding="utf-8")) if (reduced_task / "semantic-lint-report.json").is_file() else {}
|
|
243
|
+
reduced_warning = any(item.get("id") == "perceptual-reduced-motion-missing" and item.get("severity") == "warning" and not item.get("approval_blocking") for item in reduced_report.get("findings", []))
|
|
244
|
+
record(results, "p1-perceptual-reduced-motion-warning", reduced_result.returncode == 0 and reduced_warning, reduced_result.stdout + reduced_result.stderr)
|
|
245
|
+
|
|
246
|
+
benchmark_task = root / "p1-benchmark"
|
|
247
|
+
shutil.copytree(task_dir, benchmark_task)
|
|
248
|
+
benchmark_path = benchmark_task / "semantic-lint-benchmark.json"
|
|
249
|
+
benchmark_result = invoke([
|
|
250
|
+
str(INTELLIGENCE), "semantic-lint", "benchmark", "--task-dir", str(benchmark_task),
|
|
251
|
+
"--iterations", "10", "--threshold-ms", "500", "--output", str(benchmark_path),
|
|
252
|
+
])
|
|
253
|
+
benchmark = json.loads(benchmark_path.read_text(encoding="utf-8")) if benchmark_path.is_file() else {}
|
|
254
|
+
benchmark_ok = benchmark.get("status") == "pass" and benchmark.get("p95_ms", 999999) < benchmark.get("threshold_ms", 0) and benchmark.get("rule_count", 0) >= 10
|
|
255
|
+
record(results, "p1-benchmark-execution-time", benchmark_result.returncode == 0 and benchmark_ok, benchmark_result.stdout + benchmark_result.stderr)
|
|
256
|
+
|
|
257
|
+
|
|
258
|
+
def run_runtime_verifier_cases(root: Path, results: list[dict[str, object]]) -> None:
|
|
259
|
+
"""Exercise external verification without granting approval or trusting paths."""
|
|
260
|
+
scene = root / "telemetry-scene/browser-review-smoke"
|
|
261
|
+
task = root / "telemetry-task"
|
|
262
|
+
shutil.copytree(ROOT / "src/output/browser-review-smoke", scene)
|
|
263
|
+
shutil.copytree(ROOT / "artifacts/browser-review-smoke-task", task)
|
|
264
|
+
base = [str(VERIFIER), "--scene-dir", str(scene), "--task-dir", str(task)]
|
|
265
|
+
|
|
266
|
+
clean = invoke(base)
|
|
267
|
+
clean_doc = json.loads(clean.stdout) if clean.stdout.strip().startswith("{") else {}
|
|
268
|
+
record(results, "p2-runtime-verifier-clean", clean.returncode == 0 and clean_doc.get("verified") is True and clean_doc.get("approval") is False, clean.stdout + clean.stderr)
|
|
269
|
+
|
|
270
|
+
tampered_task = root / "telemetry-tampered"
|
|
271
|
+
shutil.copytree(task, tampered_task)
|
|
272
|
+
tampered_path = tampered_task / "runtime-adapters/rive/runtime-telemetry.json"
|
|
273
|
+
tampered = json.loads(tampered_path.read_text(encoding="utf-8"))
|
|
274
|
+
tampered["samples"][0]["state"]["eval_tamper"] = True
|
|
275
|
+
tampered_path.write_text(json.dumps(tampered, indent=2) + "\n", encoding="utf-8")
|
|
276
|
+
tampered_result = invoke([str(VERIFIER), "--scene-dir", str(scene), "--task-dir", str(tampered_task)])
|
|
277
|
+
record(results, "p2-runtime-verifier-tamper", tampered_result.returncode != 0 and "sha256 mismatch" in tampered_result.stdout, tampered_result.stdout + tampered_result.stderr)
|
|
278
|
+
|
|
279
|
+
foreign_task = root / "telemetry-foreign-task"
|
|
280
|
+
shutil.copytree(task, foreign_task)
|
|
281
|
+
foreign_doc = json.loads((foreign_task / "task.json").read_text(encoding="utf-8"))
|
|
282
|
+
foreign_doc["task_id"] = "telemetry-foreign-task"
|
|
283
|
+
(foreign_task / "task.json").write_text(json.dumps(foreign_doc, indent=2) + "\n", encoding="utf-8")
|
|
284
|
+
foreign_result = invoke([str(VERIFIER), "--scene-dir", str(scene), "--task-dir", str(foreign_task)])
|
|
285
|
+
record(results, "p2-runtime-verifier-cross-task", foreign_result.returncode != 0 and "task_id" in foreign_result.stdout, foreign_result.stdout + foreign_result.stderr)
|
|
286
|
+
|
|
287
|
+
symlink_task = root / "telemetry-symlink-task"
|
|
288
|
+
shutil.copytree(task, symlink_task)
|
|
289
|
+
outside = root / "telemetry-outside"
|
|
290
|
+
outside.mkdir()
|
|
291
|
+
(outside / "runtime-telemetry.json").write_text((task / "runtime-adapters/rive/runtime-telemetry.json").read_text(encoding="utf-8"), encoding="utf-8")
|
|
292
|
+
(symlink_task / "runtime-adapters/linked").symlink_to(outside, target_is_directory=True)
|
|
293
|
+
symlink_evidence_path = symlink_task / "runtime-adapters/runtime-evidence.json"
|
|
294
|
+
symlink_evidence = json.loads(symlink_evidence_path.read_text(encoding="utf-8"))
|
|
295
|
+
symlink_evidence["frameworks"][0]["telemetry"]["file"] = "linked/runtime-telemetry.json"
|
|
296
|
+
symlink_evidence_path.write_text(json.dumps(symlink_evidence, indent=2) + "\n", encoding="utf-8")
|
|
297
|
+
symlink_result = invoke([str(VERIFIER), "--scene-dir", str(scene), "--task-dir", str(symlink_task)])
|
|
298
|
+
record(results, "p2-runtime-verifier-symlink", symlink_result.returncode != 0 and "symlink" in symlink_result.stdout, symlink_result.stdout + symlink_result.stderr)
|
|
299
|
+
|
|
300
|
+
|
|
301
|
+
def main() -> int:
|
|
302
|
+
corpus = json.loads(CASES.read_text(encoding="utf-8"))
|
|
303
|
+
expected = {case["id"] for case in corpus.get("cases", [])}
|
|
304
|
+
results: list[dict[str, object]] = []
|
|
305
|
+
with tempfile.TemporaryDirectory(prefix="motionloom-eval-") as td:
|
|
306
|
+
root = Path(td)
|
|
307
|
+
task_dir = root / "artifacts/professional-review-e2e"
|
|
308
|
+
shutil.copytree(ROOT / "artifacts/professional-review-e2e", task_dir)
|
|
309
|
+
|
|
310
|
+
registry = root / "capability-registry.json"
|
|
311
|
+
build = invoke([str(INTELLIGENCE), "capabilities", "build", "--output", str(registry)])
|
|
312
|
+
if build.returncode != 0:
|
|
313
|
+
record(results, "verified-runtime-selection", False, build.stdout + build.stderr)
|
|
314
|
+
else:
|
|
315
|
+
selected = invoke([str(INTELLIGENCE), "capabilities", "select", "--registry", str(registry), "--capability", "runtime.rive"])
|
|
316
|
+
record(results, "verified-runtime-selection", selected.returncode == 0, selected.stdout + selected.stderr)
|
|
317
|
+
|
|
318
|
+
scaffold = invoke([str(INTELLIGENCE), "capabilities", "select", "--registry", str(registry), "--capability", "runtime.spine"])
|
|
319
|
+
record(results, "scaffold-runtime-blocked", scaffold.returncode != 0, scaffold.stdout + scaffold.stderr)
|
|
320
|
+
|
|
321
|
+
stale = json.loads(registry.read_text(encoding="utf-8"))
|
|
322
|
+
for entry in stale["capabilities"]:
|
|
323
|
+
if entry.get("id") == "runtime.rive":
|
|
324
|
+
entry["last_verified_at"] = "2000-01-01T00:00:00Z"
|
|
325
|
+
stale_path = root / "stale-capability-registry.json"
|
|
326
|
+
stale_path.write_text(json.dumps(stale, indent=2) + "\n", encoding="utf-8")
|
|
327
|
+
stale_result = invoke([str(INTELLIGENCE), "capabilities", "select", "--registry", str(stale_path), "--capability", "runtime.rive"])
|
|
328
|
+
record(results, "stale-capability-evidence", stale_result.returncode != 0, stale_result.stdout + stale_result.stderr)
|
|
329
|
+
|
|
330
|
+
tampered = json.loads(registry.read_text(encoding="utf-8"))
|
|
331
|
+
for entry in tampered["capabilities"]:
|
|
332
|
+
if entry.get("id") == "runtime.rive":
|
|
333
|
+
entry["evidence"][0]["sha256"] = "0" * 64
|
|
334
|
+
tampered_path = root / "tampered-capability-registry.json"
|
|
335
|
+
tampered_path.write_text(json.dumps(tampered, indent=2) + "\n", encoding="utf-8")
|
|
336
|
+
tampered_result = invoke([str(INTELLIGENCE), "capabilities", "select", "--registry", str(tampered_path), "--capability", "runtime.rive"])
|
|
337
|
+
record(results, "tampered-capability-evidence", tampered_result.returncode != 0, tampered_result.stdout + tampered_result.stderr)
|
|
338
|
+
|
|
339
|
+
graph_build = invoke([str(INTELLIGENCE), "graph", "build", "--task-dir", str(task_dir)])
|
|
340
|
+
graph = json.loads((task_dir / "project-graph.json").read_text(encoding="utf-8"))
|
|
341
|
+
graph["edges"].append({"from": graph["roots"][0], "to": "artifact:missing", "relation": "uses"})
|
|
342
|
+
graph_path = root / "corrupt-project-graph.json"
|
|
343
|
+
graph_path.write_text(json.dumps(graph, indent=2) + "\n", encoding="utf-8")
|
|
344
|
+
graph_result = invoke([str(INTELLIGENCE), "graph", "validate", "--path", str(graph_path)])
|
|
345
|
+
record(results, "graph-edge-corruption", graph_build.returncode == 0 and graph_result.returncode != 0, graph_result.stdout + graph_result.stderr)
|
|
346
|
+
|
|
347
|
+
replay_build = invoke([str(INTELLIGENCE), "replay", "capture", "--root", str(root), "--task-dir", str(task_dir)])
|
|
348
|
+
replay_verify = invoke([str(INTELLIGENCE), "replay", "verify", "--root", str(root), "--bundle", str(task_dir / "replay-bundle.json")])
|
|
349
|
+
review_path = task_dir / "review.json"
|
|
350
|
+
review_path.write_text(review_path.read_text(encoding="utf-8") + "\n", encoding="utf-8")
|
|
351
|
+
replay_tamper = invoke([str(INTELLIGENCE), "replay", "verify", "--root", str(root), "--bundle", str(task_dir / "replay-bundle.json")])
|
|
352
|
+
record(results, "replay-artifact-tamper", replay_build.returncode == 0 and replay_verify.returncode == 0 and replay_tamper.returncode != 0, replay_tamper.stdout + replay_tamper.stderr)
|
|
353
|
+
|
|
354
|
+
foreign_task = root / "foreign-task"
|
|
355
|
+
shutil.copytree(ROOT / "artifacts/professional-review-e2e", foreign_task)
|
|
356
|
+
candidate = json.loads((foreign_task / "browser-review.json").read_text(encoding="utf-8"))
|
|
357
|
+
candidate["status"] = "prepared"
|
|
358
|
+
candidate["task_id"] = "foreign-task"
|
|
359
|
+
(foreign_task / "browser-review.json").write_text(json.dumps(candidate, indent=2) + "\n", encoding="utf-8")
|
|
360
|
+
foreign = invoke([str(REPORT), "review", "--task-dir", str(foreign_task), "--candidate-id", str(candidate.get("candidate_id")), "--decision", "approved", "--reviewer", "eval"])
|
|
361
|
+
record(results, "foreign-task-candidate", foreign.returncode != 0, foreign.stdout + foreign.stderr)
|
|
362
|
+
|
|
363
|
+
run_p1_cases(root, task_dir, results)
|
|
364
|
+
run_performance_perceptual_cases(root, task_dir, results)
|
|
365
|
+
run_runtime_verifier_cases(root, results)
|
|
366
|
+
run_attestation_cases(root, results)
|
|
367
|
+
|
|
368
|
+
missing = expected - {str(item["id"]) for item in results}
|
|
369
|
+
for case_id in sorted(missing):
|
|
370
|
+
record(results, case_id, False, "case was declared but not executed")
|
|
371
|
+
failed = [item for item in results if item["status"] != "pass"]
|
|
372
|
+
print(json.dumps({"status": "fail" if failed else "pass", "suite": corpus.get("suite"), "case_count": len(results), "results": results}, indent=2))
|
|
373
|
+
return 1 if failed else 0
|
|
374
|
+
|
|
375
|
+
|
|
376
|
+
if __name__ == "__main__":
|
|
377
|
+
raise SystemExit(main())
|