671 lines
24 KiB
Bash
Executable File
671 lines
24 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
set -euo pipefail
|
|
|
|
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
|
TMP_DIR="$(mktemp -d "${TMPDIR:-/tmp}/apc-portable-pet-maker.XXXXXX")"
|
|
PET_MAKER_RUNTIME_HOME="${HOME:-}"
|
|
. "$ROOT_DIR/script/validation_helpers.sh"
|
|
apc_use_isolated_home "$TMP_DIR"
|
|
|
|
PETCORE_PID=""
|
|
export PYTHONDONTWRITEBYTECODE=1
|
|
export PYTHONPYCACHEPREFIX="$TMP_DIR/python-cache"
|
|
|
|
resolve_pet_maker_python() {
|
|
local candidate
|
|
local -a candidates=()
|
|
if [[ -n "${APC_PET_MAKER_PYTHON:-}" ]]; then
|
|
candidates+=("$APC_PET_MAKER_PYTHON")
|
|
fi
|
|
shopt -s nullglob
|
|
candidates+=(
|
|
"$PET_MAKER_RUNTIME_HOME"/.cache/codex-runtimes/*/dependencies/python/bin/python3*
|
|
)
|
|
shopt -u nullglob
|
|
for executable_name in python3.13 python3.12 python3.11 python3; do
|
|
if candidate="$(command -v "$executable_name" 2>/dev/null)"; then
|
|
candidates+=("$candidate")
|
|
fi
|
|
done
|
|
for candidate in "${candidates[@]}"; do
|
|
[[ -x "$candidate" && "$candidate" != *-config ]] || continue
|
|
if "$candidate" -I -c 'from PIL import Image; assert Image.__version__' \
|
|
>/dev/null 2>&1; then
|
|
printf '%s\n' "$candidate"
|
|
return 0
|
|
fi
|
|
done
|
|
printf 'Portable pet maker validation requires a Python runtime with Pillow PNG/WebP support\n' >&2
|
|
return 1
|
|
}
|
|
|
|
cleanup() {
|
|
if [[ -n "$PETCORE_PID" ]]; then
|
|
kill "$PETCORE_PID" >/dev/null 2>&1 || true
|
|
wait "$PETCORE_PID" >/dev/null 2>&1 || true
|
|
fi
|
|
rm -rf "$TMP_DIR"
|
|
}
|
|
trap cleanup EXIT
|
|
|
|
assert_json() {
|
|
local json="$1"
|
|
local expression="$2"
|
|
JSON="$json" "$PET_MAKER_PYTHON" -B - "$expression" <<'PY'
|
|
import json
|
|
import os
|
|
import sys
|
|
|
|
data = json.loads(os.environ["JSON"])
|
|
expression = sys.argv[1]
|
|
allowed = {
|
|
"__builtins__": {},
|
|
"all": all,
|
|
"any": any,
|
|
"data": data,
|
|
"len": len,
|
|
"set": set,
|
|
}
|
|
if not eval(expression, allowed, {}):
|
|
raise SystemExit(
|
|
f"assertion failed: {expression}\n{json.dumps(data, ensure_ascii=False, indent=2)}"
|
|
)
|
|
PY
|
|
}
|
|
|
|
cd "$ROOT_DIR"
|
|
cargo build --locked -p petcore -p petcore-cli >/dev/null
|
|
PET_MAKER_PYTHON="$(resolve_pet_maker_python)"
|
|
|
|
HELPER="$ROOT_DIR/skills/agent-pet-maker/scripts/petpack_workspace.py"
|
|
CLI="$ROOT_DIR/target/debug/petcore-cli"
|
|
PETCORE="$ROOT_DIR/target/debug/petcore"
|
|
|
|
if [[ -z "${APC_INTERACTION_ATTESTATION_PATH:-}" ]]; then
|
|
export APC_INTERACTION_ATTESTATION_PATH="$TMP_DIR/interaction-attestation.json"
|
|
"$ROOT_DIR/script/prepare_interaction_attestation.sh" \
|
|
--output "$APC_INTERACTION_ATTESTATION_PATH"
|
|
elif [[ ! -f "$APC_INTERACTION_ATTESTATION_PATH" ]]; then
|
|
echo 'portable pet maker validation requires the declared interaction attestation' >&2
|
|
exit 1
|
|
fi
|
|
|
|
"$PET_MAKER_PYTHON" -B -m unittest discover \
|
|
-s "$ROOT_DIR/skills/agent-pet-maker/tests" \
|
|
-p 'test_*.py' >/dev/null
|
|
|
|
PREFLIGHT="$("$PET_MAKER_PYTHON" -B "$HELPER" preflight --cli "$CLI")"
|
|
assert_json "$PREFLIGHT" 'data["ok"] is True and data["image_codecs"]["png"] is True and data["image_codecs"]["animated_webp"] is True'
|
|
|
|
CREATE_WORKSPACE="$TMP_DIR/create-workspace"
|
|
CREATE_OUTPUT="$TMP_DIR/portable-fixture.petpack"
|
|
CREATE_RESULT="$TMP_DIR/portable-fixture.result.json"
|
|
CREATE_PREPARE="$("$PET_MAKER_PYTHON" -B "$HELPER" prepare \
|
|
--operation create \
|
|
--workspace "$CREATE_WORKSPACE" \
|
|
--cli "$CLI")"
|
|
assert_json "$CREATE_PREPARE" 'data["ok"] is True and data["status"] == "prepared" and data["operation"] == "create"'
|
|
|
|
"$PET_MAKER_PYTHON" -B - "$CREATE_WORKSPACE/petpack-source" <<'PY'
|
|
import json
|
|
import math
|
|
import sys
|
|
from pathlib import Path
|
|
|
|
from PIL import Image, ImageDraw
|
|
|
|
root = Path(sys.argv[1])
|
|
states = (
|
|
"idle",
|
|
"thinking",
|
|
"tool",
|
|
"waiting",
|
|
"done",
|
|
"failed",
|
|
"acknowledge",
|
|
"drag_left",
|
|
"drag_right",
|
|
)
|
|
state_colors = {
|
|
"idle": (111, 92, 219, 235),
|
|
"thinking": (72, 143, 235, 235),
|
|
"tool": (35, 174, 132, 235),
|
|
"waiting": (230, 168, 48, 235),
|
|
"done": (62, 184, 91, 235),
|
|
"failed": (210, 72, 87, 235),
|
|
"acknowledge": (226, 118, 69, 235),
|
|
"drag_left": (82, 112, 208, 235),
|
|
"drag_right": (58, 144, 206, 235),
|
|
}
|
|
state_timings = {
|
|
"idle": {
|
|
"frame_durations_ms": [260, 220, 240, 260, 380, 640],
|
|
"playback": {"mode": "periodic", "cooldown_ms": [2500, 5000]},
|
|
"reduced_motion_frame_index": 2,
|
|
},
|
|
"thinking": {
|
|
"frame_durations_ms": [120, 140, 160, 180],
|
|
"playback": {"mode": "burst_then_idle", "entry_repeat_count": 3},
|
|
"reduced_motion_frame_index": 2,
|
|
},
|
|
"tool": {
|
|
"frame_durations_ms": [150, 150, 170, 330],
|
|
"playback": {
|
|
"mode": "burst_then_idle",
|
|
"entry_repeat_count": 3,
|
|
},
|
|
"reduced_motion_frame_index": 2,
|
|
},
|
|
"waiting": {
|
|
"frame_durations_ms": [100, 100, 110, 110, 120, 130, 160, 230],
|
|
"playback": {
|
|
"mode": "burst_then_settle",
|
|
"entry_repeat_count": 3,
|
|
"settle_frame_index": 7,
|
|
},
|
|
"reduced_motion_frame_index": 4,
|
|
},
|
|
"done": {
|
|
"frame_durations_ms": [120, 140, 160, 230],
|
|
"playback": {"mode": "burst_then_idle", "entry_repeat_count": 3},
|
|
"reduced_motion_frame_index": 2,
|
|
},
|
|
"failed": {
|
|
"frame_durations_ms": [80, 80, 90, 100, 110, 120, 190, 290],
|
|
"playback": {
|
|
"mode": "burst_then_settle",
|
|
"entry_repeat_count": 3,
|
|
"settle_frame_index": 7,
|
|
},
|
|
"reduced_motion_frame_index": 2,
|
|
},
|
|
"acknowledge": {
|
|
"frame_durations_ms": [180, 140, 180, 300],
|
|
"playback": {"mode": "once_then_return"},
|
|
"reduced_motion_frame_index": 1,
|
|
},
|
|
"drag_left": {
|
|
"frame_durations_ms": [100, 90, 100, 110, 100, 200],
|
|
"playback": {"mode": "loop"},
|
|
"reduced_motion_frame_index": 2,
|
|
},
|
|
"drag_right": {
|
|
"frame_durations_ms": [100, 90, 100, 110, 100, 200],
|
|
"playback": {"mode": "loop"},
|
|
"reduced_motion_frame_index": 2,
|
|
},
|
|
}
|
|
state_frame_counts = {
|
|
state: len(state_timings[state]["frame_durations_ms"]) for state in states
|
|
}
|
|
|
|
|
|
def write_json(path: Path, value: object) -> None:
|
|
path.write_text(
|
|
json.dumps(value, ensure_ascii=False, indent=2, sort_keys=True) + "\n",
|
|
encoding="utf-8",
|
|
)
|
|
|
|
|
|
def render_frame(state: str, index: int) -> Image.Image:
|
|
image = Image.new("RGBA", (192, 208), (0, 0, 0, 0))
|
|
draw = ImageDraw.Draw(image)
|
|
color = state_colors[state]
|
|
progress = index / max(1, state_frame_counts[state] - 1)
|
|
if state == "done":
|
|
dx = round(progress * 6)
|
|
dy = round(progress * 4)
|
|
else:
|
|
dx = round(math.sin(progress * math.tau) * 5)
|
|
dy = round(math.cos(progress * math.tau) * 3)
|
|
draw.ellipse((43 + dx, 49 - dy, 149 + dx, 169 - dy), fill=color)
|
|
draw.polygon(
|
|
[(58 + dx, 68 - dy), (70 + dx, 28 - dy), (91 + dx, 65 - dy)],
|
|
fill=color,
|
|
)
|
|
draw.polygon(
|
|
[(105 + dx, 65 - dy), (126 + dx, 28 - dy), (138 + dx, 68 - dy)],
|
|
fill=color,
|
|
)
|
|
draw.ellipse((73 + dx, 91 - dy, 84 + dx, 102 - dy), fill=(255, 255, 255, 255))
|
|
draw.ellipse((113 + dx, 91 - dy, 124 + dx, 102 - dy), fill=(255, 255, 255, 255))
|
|
draw.ellipse((77 + dx, 95 - dy, 82 + dx, 100 - dy), fill=(25, 28, 45, 255))
|
|
draw.ellipse((117 + dx, 95 - dy, 122 + dx, 100 - dy), fill=(25, 28, 45, 255))
|
|
draw.arc((87 + dx, 105 - dy, 109 + dx, 126 - dy), 15, 165, fill=(255, 255, 255, 255), width=3)
|
|
draw.ellipse((137 - dx, 131 + dy, 177 - dx, 163 + dy), fill=color)
|
|
# A small bounded phase mark makes every adjacent fixture frame visibly
|
|
# distinct without changing the character identity.
|
|
draw.rectangle((19 + index, 190, 20 + index, 197), fill=(*color[:3], 170))
|
|
return image
|
|
|
|
|
|
frames: list[Image.Image] = []
|
|
for state in states:
|
|
state_dir = root / "assets" / "frames" / state
|
|
state_dir.mkdir(parents=True, exist_ok=True)
|
|
for index in range(state_frame_counts[state]):
|
|
frame = render_frame(state, index)
|
|
frame.save(state_dir / f"frame-{index:03d}.png", format="PNG", optimize=False)
|
|
if state == "idle":
|
|
frames.append(frame)
|
|
|
|
cover = frames[0].resize((384, 416), Image.Resampling.NEAREST)
|
|
cover.save(root / "assets" / "preview" / "cover.png", format="PNG", optimize=False)
|
|
frames[0].resize((384, 416), Image.Resampling.NEAREST).save(
|
|
root / "assets" / "preview" / "animated_preview.webp",
|
|
format="WEBP",
|
|
save_all=True,
|
|
append_images=[frames[1].resize((384, 416), Image.Resampling.NEAREST)],
|
|
duration=[84, 84],
|
|
loop=0,
|
|
lossless=True,
|
|
)
|
|
|
|
created_at = "2026-07-16T00:00:00Z"
|
|
manifest = {
|
|
"schema_version": "apc.petpack.v3",
|
|
"id": "pet_portablefixture",
|
|
"name": "Portable Validation Fixture",
|
|
"style": "deterministic repository validation fixture",
|
|
"quality": "low",
|
|
"render_size": {"width": 192, "height": 208},
|
|
"states": [
|
|
{
|
|
"name": state,
|
|
"frames_dir": f"assets/frames/{state}",
|
|
**state_timings[state],
|
|
}
|
|
for state in states
|
|
],
|
|
"created_at": created_at,
|
|
}
|
|
write_json(root / "manifest.json", manifest)
|
|
write_json(
|
|
root / "brief.json",
|
|
{
|
|
"schema_version": "apc.pet-brief.v1",
|
|
"name": manifest["name"],
|
|
"style": manifest["style"],
|
|
"quality": "low",
|
|
"description": "A deterministic isolated fixture used only to validate the portable workflow.",
|
|
"generation": {
|
|
"generator": "repository-fixture-renderer",
|
|
"provenance": "skill-full-source",
|
|
"skill_helper": "agent-pet-maker",
|
|
"preview_only": False,
|
|
},
|
|
"states": [
|
|
{
|
|
"name": state,
|
|
"motion": f"Deterministic {state} fixture motion.",
|
|
**state_timings[state],
|
|
}
|
|
for state in states
|
|
],
|
|
"runtime": {
|
|
"states": manifest["states"],
|
|
"state_frame_counts": state_frame_counts,
|
|
"render_size": {"width": 192, "height": 208},
|
|
},
|
|
"extensions": {"dev.agentpet.validation/fixture": True},
|
|
},
|
|
)
|
|
write_json(
|
|
root / "source" / "source.json",
|
|
{
|
|
"schema_version": "apc.pet-source.v1",
|
|
"generator": "repository-fixture-renderer",
|
|
"provenance": "skill-full-source",
|
|
"created_at": created_at,
|
|
"manifest_id": manifest["id"],
|
|
"pet_name": manifest["name"],
|
|
"style": manifest["style"],
|
|
"quality": "low",
|
|
"visual_source": "image-generation",
|
|
"states": manifest["states"],
|
|
"state_frame_counts": state_frame_counts,
|
|
"preview_only": False,
|
|
"reference_files": [],
|
|
"runner": "repository-validation",
|
|
"skill_helper": "agent-pet-maker",
|
|
"extensions": {"dev.agentpet.validation/fixture": True},
|
|
},
|
|
)
|
|
(root / "source" / "prompt.md").write_text(
|
|
"Create the deterministic repository validation fixture. This is test data, not a user pet.\n",
|
|
encoding="utf-8",
|
|
)
|
|
session = root / "source" / "skill_session.jsonl"
|
|
with session.open("a", encoding="utf-8") as handle:
|
|
handle.write(
|
|
json.dumps(
|
|
{
|
|
"schema_version": "apc.pet-source-event.v1",
|
|
"event": "states.rendered",
|
|
"created_at": created_at,
|
|
"skill": "agent-pet-maker",
|
|
"runner": "repository-validation",
|
|
"generator": "repository-fixture-renderer",
|
|
"manifest_id": manifest["id"],
|
|
"quality": "low",
|
|
"render_size": {"width": 192, "height": 208},
|
|
"states": list(states),
|
|
"state_timings": manifest["states"],
|
|
"state_frame_counts": state_frame_counts,
|
|
"extensions": {"dev.agentpet.validation/fixture": True},
|
|
},
|
|
ensure_ascii=False,
|
|
sort_keys=True,
|
|
)
|
|
+ "\n"
|
|
)
|
|
PY
|
|
|
|
"$PET_MAKER_PYTHON" -B - "$TMP_DIR/moving-mask.png" <<'PY'
|
|
import sys
|
|
from pathlib import Path
|
|
|
|
from PIL import Image, ImageDraw
|
|
|
|
path = Path(sys.argv[1])
|
|
mask = Image.new("L", (192, 208), 0)
|
|
ImageDraw.Draw(mask).ellipse((24, 18, 168, 142), fill=255)
|
|
mask.save(path, format="PNG")
|
|
PY
|
|
|
|
CREATE_MOTION_LOCK="$("$PET_MAKER_PYTHON" -B "$HELPER" motion-lock \
|
|
--source "$CREATE_WORKSPACE/petpack-source" \
|
|
--state idle \
|
|
--moving-mask "$TMP_DIR/moving-mask.png" \
|
|
--output-dir "$TMP_DIR/locked-idle" \
|
|
--feather-px 0)"
|
|
assert_json "$CREATE_MOTION_LOCK" 'data["status"] == "completed" and data["capability"] == "motion-lock" and data["state"] == "idle" and data["frame_count"] == 6'
|
|
|
|
CREATE_MOTION_QA="$("$PET_MAKER_PYTHON" -B "$HELPER" motion-qa \
|
|
--workspace "$CREATE_WORKSPACE")"
|
|
assert_json "$CREATE_MOTION_QA" 'data["status"] == "completed" and data["capability"] == "motion-qa" and data["audited_states"] == ["idle", "thinking", "tool", "waiting", "done", "failed", "acknowledge", "drag_left", "drag_right"]'
|
|
|
|
CREATE_MOTION_REVIEW="$("$PET_MAKER_PYTHON" -B "$HELPER" motion-review \
|
|
--workspace "$CREATE_WORKSPACE" \
|
|
--state-note "idle=Fixture body and baseline remain stable through the complete idle loop." \
|
|
--state-note "thinking=Fixture thinking pose advances smoothly and ends on a readable final pose." \
|
|
--state-note "tool=Fixture tool action stays attached and the body anchor remains stable." \
|
|
--state-note "waiting=Fixture waiting action reads clearly and returns through a clean loop seam." \
|
|
--state-note "done=Fixture completion action has a readable success pose and clean settle." \
|
|
--state-note "failed=Fixture failure action remains identifiable and returns without a seam pop." \
|
|
--state-note "acknowledge=Fixture acknowledgement is restrained, readable, and returns cleanly." \
|
|
--state-note "drag_left=Fixture left drag cadence is directional and loops without a seam pop." \
|
|
--state-note "drag_right=Fixture right drag cadence is directional and loops without a seam pop.")"
|
|
assert_json "$CREATE_MOTION_REVIEW" 'data["status"] == "completed" and data["capability"] == "motion-review" and len(data["audited_states"]) == 9'
|
|
|
|
CREATE_FINALIZE="$("$PET_MAKER_PYTHON" -B "$HELPER" finalize \
|
|
--operation create \
|
|
--workspace "$CREATE_WORKSPACE" \
|
|
--output "$CREATE_OUTPUT" \
|
|
--result "$CREATE_RESULT" \
|
|
--cli "$CLI")"
|
|
assert_json "$CREATE_FINALIZE" 'data["status"] == "completed" and data["operation"] == "create" and data["manifest"]["id"] == "pet_portablefixture" and len(data["manifest"]["states"]) == 9 and data["validation"]["ok"] is True and data["validation"]["frame_count"] == 50 and data["changed_states"] == [] and data["motion_quality"]["human_reviewed"] is True and len(data["motion_quality"]["audited_states"]) == 9'
|
|
|
|
CREATE_VALIDATION="$("$CLI" petpack validate "$CREATE_OUTPUT")"
|
|
assert_json "$CREATE_VALIDATION" 'data["ok"] is True and data["manifest"]["id"] == "pet_portablefixture" and len(data["manifest"]["states"]) == 9 and data["frame_count"] == 50 and data["warnings"] == []'
|
|
|
|
MODIFY_WORKSPACE="$TMP_DIR/modify-workspace"
|
|
MODIFY_OUTPUT="$TMP_DIR/portable-fixture-revised.petpack"
|
|
MODIFY_RESULT="$TMP_DIR/portable-fixture-revised.result.json"
|
|
MODIFY_PREPARE="$("$PET_MAKER_PYTHON" -B "$HELPER" prepare \
|
|
--operation modify \
|
|
--input "$CREATE_OUTPUT" \
|
|
--workspace "$MODIFY_WORKSPACE" \
|
|
--cli "$CLI")"
|
|
assert_json "$MODIFY_PREPARE" 'data["ok"] is True and data["status"] == "prepared" and data["operation"] == "modify" and data["base"]["pet_id"] == "pet_portablefixture"'
|
|
|
|
"$PET_MAKER_PYTHON" -B - "$MODIFY_WORKSPACE/petpack-source" <<'PY'
|
|
import json
|
|
import sys
|
|
from pathlib import Path
|
|
|
|
from PIL import Image, ImageDraw
|
|
|
|
root = Path(sys.argv[1])
|
|
states = (
|
|
"idle",
|
|
"thinking",
|
|
"tool",
|
|
"waiting",
|
|
"done",
|
|
"failed",
|
|
"acknowledge",
|
|
"drag_left",
|
|
"drag_right",
|
|
)
|
|
created_at = "2026-07-16T00:01:00Z"
|
|
|
|
|
|
def write_json(path: Path, value: object) -> None:
|
|
path.write_text(
|
|
json.dumps(value, ensure_ascii=False, indent=2, sort_keys=True) + "\n",
|
|
encoding="utf-8",
|
|
)
|
|
|
|
|
|
for index, frame_path in enumerate(sorted((root / "assets" / "frames" / "tool").glob("*.png"))):
|
|
with Image.open(frame_path) as source:
|
|
frame = source.convert("RGBA")
|
|
draw = ImageDraw.Draw(frame)
|
|
shift = (index % 5) - 2
|
|
draw.rounded_rectangle(
|
|
(71 + shift, 131 - shift, 125 + shift, 175 - shift),
|
|
radius=8,
|
|
fill=(25, 38, 59, 235),
|
|
outline=(116, 232, 208, 255),
|
|
width=4,
|
|
)
|
|
draw.line((79 + shift, 144, 117 + shift, 144), fill=(255, 255, 255, 255), width=3)
|
|
draw.line((79 + shift, 155, 108 + shift, 155), fill=(255, 255, 255, 255), width=3)
|
|
frame.save(frame_path, format="PNG", optimize=False)
|
|
|
|
manifest = json.loads((root / "manifest.json").read_text(encoding="utf-8"))
|
|
state_timings = {entry["name"]: entry for entry in manifest["states"]}
|
|
state_frame_counts = {
|
|
state: len(state_timings[state]["frame_durations_ms"]) for state in states
|
|
}
|
|
write_json(
|
|
root / "brief.json",
|
|
{
|
|
"schema_version": "apc.pet-brief.v1",
|
|
"name": manifest["name"],
|
|
"style": manifest["style"],
|
|
"quality": "low",
|
|
"description": "The isolated fixture now has a visibly revised tool state.",
|
|
"generation": {
|
|
"generator": "repository-fixture-editor",
|
|
"provenance": "skill-full-source",
|
|
"skill_helper": "agent-pet-maker",
|
|
"preview_only": False,
|
|
},
|
|
"states": [
|
|
{
|
|
"name": state,
|
|
"motion": (
|
|
"Uses a small dark work panel."
|
|
if state == "tool"
|
|
else f"Preserved deterministic {state} fixture motion."
|
|
),
|
|
"frame_durations_ms": state_timings[state]["frame_durations_ms"],
|
|
"playback": state_timings[state]["playback"],
|
|
"reduced_motion_frame_index": state_timings[state][
|
|
"reduced_motion_frame_index"
|
|
],
|
|
}
|
|
for state in states
|
|
],
|
|
"runtime": {
|
|
"states": manifest["states"],
|
|
"state_frame_counts": state_frame_counts,
|
|
"render_size": {"width": 192, "height": 208},
|
|
},
|
|
"extensions": {"dev.agentpet.validation/fixture": True},
|
|
},
|
|
)
|
|
write_json(
|
|
root / "source" / "source.json",
|
|
{
|
|
"schema_version": "apc.pet-source.v1",
|
|
"generator": "repository-fixture-editor",
|
|
"provenance": "skill-full-source",
|
|
"created_at": created_at,
|
|
"manifest_id": manifest["id"],
|
|
"pet_name": manifest["name"],
|
|
"style": manifest["style"],
|
|
"quality": "low",
|
|
"visual_source": "image-generation",
|
|
"states": manifest["states"],
|
|
"state_frame_counts": state_frame_counts,
|
|
"preview_only": False,
|
|
"reference_files": [],
|
|
"runner": "repository-validation",
|
|
"skill_helper": "agent-pet-maker",
|
|
"extensions": {"dev.agentpet.validation/fixture": True},
|
|
},
|
|
)
|
|
(root / "source" / "prompt.md").write_text(
|
|
"Modify only the tool state of the deterministic repository validation fixture.\n",
|
|
encoding="utf-8",
|
|
)
|
|
session = root / "source" / "skill_session.jsonl"
|
|
with session.open("a", encoding="utf-8") as handle:
|
|
handle.write(
|
|
json.dumps(
|
|
{
|
|
"schema_version": "apc.pet-source-event.v1",
|
|
"event": "states.rendered",
|
|
"created_at": created_at,
|
|
"skill": "agent-pet-maker",
|
|
"runner": "repository-validation",
|
|
"generator": "repository-fixture-editor",
|
|
"manifest_id": manifest["id"],
|
|
"quality": "low",
|
|
"render_size": {"width": 192, "height": 208},
|
|
"states": ["tool"],
|
|
"changed_states": ["tool"],
|
|
"state_timings": manifest["states"],
|
|
"state_frame_counts": state_frame_counts,
|
|
"extensions": {"dev.agentpet.validation/fixture": True},
|
|
},
|
|
ensure_ascii=False,
|
|
sort_keys=True,
|
|
)
|
|
+ "\n"
|
|
)
|
|
PY
|
|
|
|
MODIFY_MOTION_QA="$("$PET_MAKER_PYTHON" -B "$HELPER" motion-qa \
|
|
--workspace "$MODIFY_WORKSPACE")"
|
|
assert_json "$MODIFY_MOTION_QA" 'data["status"] == "completed" and data["audited_states"] == ["tool"]'
|
|
|
|
MODIFY_MOTION_REVIEW="$("$PET_MAKER_PYTHON" -B "$HELPER" motion-review \
|
|
--workspace "$MODIFY_WORKSPACE" \
|
|
--state-note "tool=Revised tool motion preserves the body anchor and keeps the prop continuous.")"
|
|
assert_json "$MODIFY_MOTION_REVIEW" 'data["status"] == "completed" and data["audited_states"] == ["tool"]'
|
|
|
|
MODIFY_FINALIZE="$("$PET_MAKER_PYTHON" -B "$HELPER" finalize \
|
|
--operation modify \
|
|
--workspace "$MODIFY_WORKSPACE" \
|
|
--changed-state tool \
|
|
--output "$MODIFY_OUTPUT" \
|
|
--result "$MODIFY_RESULT" \
|
|
--cli "$CLI")"
|
|
assert_json "$MODIFY_FINALIZE" 'data["status"] == "completed" and data["operation"] == "modify" and data["manifest"]["id"] == "pet_portablefixture" and data["base"]["pet_id"] == "pet_portablefixture" and data["changed_states"] == ["tool"] and data["validation"]["ok"] is True and data["motion_quality"]["human_reviewed"] is True and data["motion_quality"]["audited_states"] == ["tool"]'
|
|
|
|
MODIFY_VALIDATION="$("$CLI" petpack validate "$MODIFY_OUTPUT")"
|
|
assert_json "$MODIFY_VALIDATION" 'data["ok"] is True and data["manifest"]["id"] == "pet_portablefixture" and len(data["manifest"]["states"]) == 9 and data["frame_count"] == 50 and data["warnings"] == []'
|
|
|
|
"$PET_MAKER_PYTHON" -B - "$CREATE_OUTPUT" "$MODIFY_OUTPUT" <<'PY'
|
|
import hashlib
|
|
import sys
|
|
import zipfile
|
|
|
|
states = (
|
|
"idle",
|
|
"thinking",
|
|
"tool",
|
|
"waiting",
|
|
"done",
|
|
"failed",
|
|
"acknowledge",
|
|
"drag_left",
|
|
"drag_right",
|
|
)
|
|
|
|
|
|
def state_hashes(path: str) -> dict[str, dict[str, str]]:
|
|
result: dict[str, dict[str, str]] = {}
|
|
with zipfile.ZipFile(path) as archive:
|
|
for state in states:
|
|
prefix = f"assets/frames/{state}/"
|
|
result[state] = {
|
|
name: hashlib.sha256(archive.read(name)).hexdigest()
|
|
for name in sorted(archive.namelist())
|
|
if name.startswith(prefix) and name.endswith(".png")
|
|
}
|
|
return result
|
|
|
|
|
|
before = state_hashes(sys.argv[1])
|
|
after = state_hashes(sys.argv[2])
|
|
changed = [state for state in states if before[state] != after[state]]
|
|
if changed != ["tool"]:
|
|
raise SystemExit(f"portable modify changed unexpected states: {changed}")
|
|
PY
|
|
|
|
(
|
|
APC_DISABLE_CODEX_APP_SERVER_AUTO=1 \
|
|
"$PETCORE" serve --ready-file "$TMP_DIR/daemon-ready"
|
|
) >"$TMP_DIR/petcore.log" 2>&1 &
|
|
PETCORE_PID="$!"
|
|
for _ in {1..200}; do
|
|
[[ -f "$TMP_DIR/daemon-ready" ]] && break
|
|
if ! kill -0 "$PETCORE_PID" >/dev/null 2>&1; then
|
|
cat "$TMP_DIR/petcore.log" >&2
|
|
exit 1
|
|
fi
|
|
sleep 0.05
|
|
done
|
|
[[ -f "$TMP_DIR/daemon-ready" ]]
|
|
|
|
HEALTH="$("$CLI" health)"
|
|
assert_json "$HEALTH" 'data["ok"] is True'
|
|
"$CLI" behavior set-json --value-json '{"enabled":false}' >/dev/null
|
|
|
|
INSTALL_RESULT="$TMP_DIR/portable-fixture.install-result.json"
|
|
INSTALL="$("$PET_MAKER_PYTHON" -B "$HELPER" install \
|
|
--input "$MODIFY_OUTPUT" \
|
|
--activate \
|
|
--result "$INSTALL_RESULT" \
|
|
--cli "$CLI")"
|
|
assert_json "$INSTALL" 'data["status"] == "completed" and data["manifest"]["id"] == "pet_portablefixture" and data["install"]["online_only"] is True and data["install"]["import"]["returned_id"] == "pet_portablefixture" and data["install"]["import"]["succeeded"] is True and data["install"]["activation"]["succeeded"] is True and data["install"]["verification"]["active"] is True and data["install"]["verification"]["behavior_enabled"] is False and data["install"]["verification"]["overlay_visibility"]["pet_visible"] is False'
|
|
|
|
EXPORTED_OUTPUT="$TMP_DIR/portable-fixture-exported.petpack"
|
|
EXPORTED="$("$CLI" petpack export --id pet_portablefixture --output "$EXPORTED_OUTPUT")"
|
|
assert_json "$EXPORTED" 'data["ok"] is True and data["pet_id"] == "pet_portablefixture" and data["validation"]["ok"] is True'
|
|
cmp -s "$MODIFY_OUTPUT" "$EXPORTED_OUTPUT"
|
|
|
|
"$CLI" pet delete --id pet_portablefixture >/dev/null
|
|
POST_DELETE="$("$CLI" pet list)"
|
|
assert_json "$POST_DELETE" 'all(pet["id"] != "pet_portablefixture" for pet in data)'
|
|
|
|
REIMPORTED="$("$CLI" petpack import "$EXPORTED_OUTPUT")"
|
|
assert_json "$REIMPORTED" 'data["id"] == "pet_portablefixture"'
|
|
"$CLI" pet activate --id pet_portablefixture >/dev/null
|
|
FINAL_SNAPSHOT="$("$CLI" state snapshot)"
|
|
assert_json "$FINAL_SNAPSHOT" 'any(pet["id"] == "pet_portablefixture" and pet["active"] is True for pet in data["pets"])'
|
|
|
|
if find "$ROOT_DIR/skills/agent-pet-maker" \
|
|
\( -type d -name '__pycache__' -o -type f \( -name '*.pyc' -o -name '*.pyo' \) \) \
|
|
-print -quit | grep -q .; then
|
|
printf 'portable pet maker validation polluted the source skill with Python bytecode\n' >&2
|
|
exit 1
|
|
fi
|
|
|
|
echo "Portable agent-pet-maker validation ok (unit tests, create, modify, online install/activate, export/reimport)"
|