Repository navigation
skills: add splash-app-verify, scenario-first testing for script apps in card-host - #192
codezzzsleep wants to merge 3 commits into
Conversation
The PR's CI runs the skill's tests on Windows and macOS as well as Linux. Checked for that: - result.json, summary.json, snap.json and calls.json were written in the platform's encoding. On Windows (cp1252) every scenario with a Chinese name ended as an environment error; reproduced under a cp1252 locale with a real OnCue scenario. Every text file is now UTF-8, and a test fails if a read or write ever omits the encoding again. - Progress lines a console cannot show are printed escaped instead of stopping the run. - Hashing card-host and the bundle ran before the guarded part of a run, so an unreadable file left summary.json at "running"; it now ends the summary as aborted. - card-host.exe is looked for on Windows. - Tests no longer use /bin/true, name UTF-8 for their own files, and skip the symbolic-link cases where the system cannot make links. 43 tests pass on Python 3.11, 3.12 and 3.13, under a cp1252 locale and with every default-encoding use turned into an error. Not run on Windows or macOS themselves. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015iSTkc7bsGF64TtxPC1sdY
BH3GEI
left a comment
There was a problem hiding this comment.
Thanks for putting this together — the runner's validation discipline is unusually thorough, and the tests are genuinely portable: I ran the full suite here, 43/43 on Python 3.14.5 and again under a non-UTF-8 locale, plus a 150-case mutation fuzz of the loader (only clean exit-2s, no tracebacks). One caveat on scope: I did not get to run the scenarios against a real card-host in this pass, so the host-driving path is reviewed at the unit/code level; everything below was reproduced or traced directly.
Two issues I'd want addressed before this lands:
-
Environment failures still write a FEEDBACK.md addressed to the app.
scripts/uitest.py:1084writesFEEDBACK.mdonenvironment_errorruns too. Reproduced here by running a valid scenario against a host binary that exits immediately: the resulting FEEDBACK.md is titled "Repair request", names the bundle'smain.splashartifact, and ends with "Fix: change only what makes this step pass. Keep widget ids…", while the actual cause iscard-host exited (0)— an exit-2, "not the app's fault" failure by the skill's own contract. SKILL.md step 3 tells the repair model to read FEEDBACK.md and fix only what it names; a dead Xvfb or a hung card-host would send that model editing app source for a machine problem. Could environment errors skip the app-facing template, or clearly retitle the artifact as not a repair request? -
The close path is POSIX-only, so a hung card-host on Windows raises and leaks.
scripts/uitest.py:566reachesos.killpg(..., signal.SIGKILL)when the process ignores/quit; the surrounding handlers catch onlyProcessLookupError/TimeoutExpired.os.killpgdoes not exist on Windows, andstart_new_session=Trueis a no-op there, so a card-host that hangs past the 5s wait raisesAttributeErrorinto the close wrapper and the process is left running — one leaked process per scenario. If the runner is meant to be Linux/macOS-only, a platform note (or aproc.kill()fallback) would close it either way.
Smaller points, each verified against the tree:
scripts/uitest.py:762vs the Checks table inreferences/SCENARIOS.md: acallsitem with onlyexcludespasses when the service was never called (the"excludes" not in itemcarve-out skips the "≥1" requirement), while the doc says "withoutcount, at least one". Doc and code should agree, whichever way you pick.references/SCENARIOS.md:42documentsstoragevalues as "text, or JSON for an object", butexamples/notes/tests/04-restart.jsonand05-remove.jsonseed JSON arrays, and the runner (scripts/uitest.py:1005) accepts any JSON value. Someone following the docs would wrap or stringify where they needn't.examples/titled-notes/bundle/main.splash:25-26,91usesis_array()/is_string(), which appear nowhere else in the repo — not indocs/SCRIPT-API.md,docs/RUNTIME-TYPES.md, or any other.splash— whilereferences/SHAPE.mditself says "Use only documented APIs". The recorded runs show the predicates exist in the current runtime; could their source be cited (or the docs extended) so the flagship example meets the skill's own rule?- The example set doesn't reach SKILL.md's own "at least" list:
main.splash:33implements the "not granted" branch ("This app may not use the model."), but no scenario setshost_profile: "rinx"or exercises it; and only01-first-screen.jsonruns at two sizes — the main path (02-keep.json) runs at the default size only. Since agents will pattern-match on these examples, closing the gap (or noting it) would help. scripts/uitest.py:983-987:mkdtemp()andrun_dir.mkdir()sit before thetrywhosefinallyremoves the work dir, so an OSError there (e.g. a scenario stem long enough thatstem@sizeexceeds 255 bytes) leaks oneuitest-*temp dir and surfaces as a generic environment error rather than a scenario-name complaint.scripts/uitest.py:380-388: if all fifty candidate displays are taken, the loop falls through and Xvfb is spawned on:139regardless; if the socket never appears the runner still proceeds, costing a fullstartup_timeoutper scenario with the error pointing at card-host rather than the display.scripts/uitest.py:1353:use/runpart names accept absolute paths and..(base / name), escaping the scenario directory — inconsistent with the strict jail applied tostorageandfilesnames.scripts/test_uitest.py:273:test_examples_are_validasserts a step-action set that lacksscroll_to(andrun), both documented in SCENARIOS.md — the first example adopting them would fail this test spuriously.SKILL.md:52: "Rinx refuses a mini-app what it was not granted" → "the services it was not granted".- In the same spirit as the runner's own "nothing passes silently" note:
"sizes": []validates and silently runs the default size, and{"text": [""]}passes vacuously.
For the record, these all held up under checking: the exact CI step command on the 3-OS onboarding job; tools/check-links.py 0 broken; every scenario field documented in SCENARIOS.md cross-checked against the runner's validation constants; all 14 example scenarios field-validated and traced against both apps' code (including the template texts examples/notes targets); both host profiles' refusal/reporting behavior; the storage-jail and symlink refusal; and the version/claim consistency (runner: 3). Looking forward to the next revision.
…; portable host stop; review fixes Addresses the review on OctoSense-org#192. Blocking: - An environment error (no display, a card-host that exited or would not stop, call evidence the runner cannot read) now writes ENVIRONMENT.md, "Environment error, not a repair request", and never FEEDBACK.md. The console says "environment, not the app", and SKILL.md step 3 says not to change the app for it. Reproduced with a card-host that exits at once. - A card-host that ignores /quit is stopped portably: the process group on POSIX; terminate, then kill, where os.killpg does not exist (Windows). One that survives both is reported as an environment error, not left running. Smaller points: - calls: without count, at least one call, with or without excludes; "count": 0 requires none. Code and SCENARIOS.md agree. - SCENARIOS.md: storage values are any JSON value; a string is the text. - is_array()/is_string(): documented in SCRIPT-API (Data and strings) with the runtime source, and cited in the example. - examples/titled-notes: 10-model-not-granted (host_profile rinx, the not-granted branch) and the main path at two sizes; 12 runs. - The work directory is created right before the try that removes it; a scenario file name too long for its evidence directory is a scenario error when the scenarios load. - Xvfb: no free display number, or a server that exits or never makes its socket, is a display error before any scenario runs. - use/run part names follow the storage name rules and must resolve inside the directory of the file that names them. - test_examples_are_valid takes the action set from the runner. - SKILL.md: "the services it was not granted". - "sizes": [] and empty texts in checks and selectors are refused on load. 58 unit tests pass on Python 3.11, 3.12 and 3.13, under a cp1252 locale and with every default-encoding use turned into an error; 12 of the 15 new ones fail on the previous runner. On Linux card-host (App Hub 8d3853c, Octoscript-Makepad 6cf2d90a): titled-notes 12/12, notes 6/6, OnCue 0.5.4 85/85. App Flow tool tests OK; check-links 0 broken. Not run on macOS or Windows hosts. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015iSTkc7bsGF64TtxPC1sdY
|
Thanks for the review. The revision is pushed (3de7f92). Both blocking points are fixed:
The smaller points, in your order:
Evidence for this revision: 58 skill unit tests pass on Python 3.11, 3.12 and 3.13, also under a cp1252 locale and with every default-encoding use turned into an error. 15 of them are new: 12 fail on the previous runner, and the other 3 pin behavior that must not change. The App Flow tool tests pass (48, 6 skipped), and |
What this adds
skills/splash-app-verify/: a development skill for OctoSense Splash script apps (main.splash). This PR contains the author's v3.3 skill files unchanged; the runner's scenario protocol remains version 3. It moves verification to the front, the way backend code is tested:references/SHAPE.md).card-hostwithscripts/uitest.py. Host services are answered from the scenario, so every failure path (no service, an error, no answer, an authorization that ended, a refusal) is reproducible without a real host.FEEDBACK.md: steps, expected, actual and evidence, in the shapedocs/MODEL-VALIDATION.mdasks for, so a model or a person fixes one thing at a time.The runner is Python 3.9+, standard library only. It drives
card-hostthrough the remote routes (/snap,/click,/t,/k,/m), never edits the bundle under test, and starts its own Xvfb on a headless Linux machine.Also in this PR:
AGENTS.md(step 6) and from both READMEs;examples/notestests the App Flow template app;examples/titled-notesis a small app in the narrow shape with ten scenario files and twelve runs across sizes.Current host policy
Capability names are disclosures on current OctoSense hosts (App Hub
e1fdf28, makepadd653bc44), while Rinx still grants a mini-app only the services it was granted. A scenario picks the host with"host_profile":octosense(default): an undeclared service is answered, and the run reports it as undisclosed;rinx: it is refused with Rinx's own text,Mini app was not granted <service>(Rinxcrates/miniapp-core/src/lib.rs:103).The test copy keeps the bundle's capabilities, storage and quota as written. The fake host reports calls on card-host's log with a per-run code, so missing or unreadable call evidence is an error, never "no calls".
Evidence
The recorded example and OnCue scenario runs used
card-hostbuilt from App Hubmain8d3853c, with the Splash runtime Octoscript-Makepad6cf2d90a, on Linux x86_64 (2 cores, Xvfb, Mesa llvmpipe). Unit tests and tool/link checks do not need card-host.scripts/test_uitest.py)examples/notesagainsttemplates/script-app/bundle, recordedexamples/titled-notes, recorded96ce6ee, with this PR's runner); 0.5.4: 85 / 85 runs (OnCue6dab6a1, with the revised runner)main15b1da01Ordinary recorded scenario runs take about 0.5 to 2 seconds; timeout scenarios wait for the app's own timer (20 seconds in the titled-notes "never answers" case). card-host's recorded peak memory was 240 to 265 MB.
How it found real bugs: on OnCue 0.5.2 the first run of 38 scenarios failed 6 of 43 runs, on 5 real defects:
Each was fixed red to green with the scenarios unchanged. Three of the shape rules in
references/SHAPE.mdcome from these fixes.The runner itself was reviewed independently four times. The following fixes have regression coverage:
storage;setup.shnot persisting the card-host path;summary.jsonafter a failed rerun;"false"ortrueaccepted where a boolean or a count was meant;count,callsorfilesaccepted as objects, ornullleaving a summary marked as running;Negative checks on the real card-host: a parse error fails (with a hint, since the runtime logs nothing for it); a budget stop on the older runtime fails; an undeclared service is refused under
rinxand noted underoctosense; a file written to the wrong path fails the exact-path check.Not verified
page.card): out of scope.SKILL.md§5 says how to finish on the real host.