Skip to content

Add sstack-demo lane and scripted UI evaluation with visual review - #3

Merged
sangyeT merged 8 commits into
mainfrom
claude/sstack-demo-skill
Sep 30, 2026
Merged

sangyeT merged 8 commits into
mainfrom
claude/sstack-demo-skill

Conversation

@sangyeT

@sangyeT sangyeT commented Sep 29, 2026

Copy link
Copy Markdown
Owner

Why

Tickets ship well through the delivery loop, but demos paid the full cost of that loop: Jira, controller, frozen evals, independent review, CI and merge. This PR adds a separate demo lane, plus a UI checker that both demos and delivery can use. With the checker, an agent drives the UI, then looks at every screenshot, with computer use or by opening the images, before the UI counts as passing.

What changed

sstack-demo skill (tools/sstack/skills/sstack-demo/SKILL.md)

  • The lane runs script → build and self-check → honesty check → two rehearsals, then either promotes the demo to tickets or archives it. It skips the delivery gates.
  • The script has 3–7 steps, each labeled real or faked. A real step needs proof, and every mock in the branch diff must appear on the faked list.
  • Demo work stays on demo/* branches, never merges, and never counts as ticket evidence (AGENTS.md).
  • Kizen writes defer to platform.md approval. Record writes go only to a sandbox approved for this demo, and resets use a fresh run marker instead of deleting records.
  • Promotion maps real steps to schema-valid eval cases: business values, phase and surface. Faked steps become scope with their own acceptance criteria.
  • Routing: /sstack and sstack-mode send demo requests here. The skill is registered in both host skill folders, install.py, the contract checker, CLAUDE.md, the README and the installer's managed instructions.

tools/sstack/ui_eval.py

  • run SCRIPT.json --approved-origin <host> drives a JSON step script in Chromium. Steps are goto, click, fill, press, wait_for, expect_text and capture.
  • Evidence per run:
    • a screenshot for every step, and optionally a video
    • console errors, page errors, 5xx and network failures, and top-level 4xx
    • a {run_marker} value unique to each run
    • captured values in the eval result-case shape
  • Origin guard:
    • top-level navigation elsewhere is aborted before any request is sent; this covers links, delayed scripts, popups and every hop of a server redirect chain
    • goto paths must stay under base_url, so file:, scheme-relative and backslash paths are rejected
    • the saved login (--storage-state) must live outside the repository
    • recorded URLs drop query strings and fragments
  • review REPORT VERDICTS holds the visual verdicts. It binds each pass/fail verdict to its screenshot's SHA256, blocks on missing verdicts or reports with nothing reviewable, and fails on any fail verdict or failed automated check. Verdicts are the reviewer's declarations and are not authenticated.
  • Docs: tools/sstack/skills/sstack-verify/references/ui-eval.md. sstack-demo and sstack-verify use this checker for UI steps.

Dependencies and CI

  • playwright==1.56.0 is added to requirements-dev.txt. This is the stack's first non-stdlib runtime tool; it is imported lazily, so the other tools stay stdlib-only.
  • CI installs Chromium and sets SSTACK_REQUIRE_BROWSER=1, so browser tests fail rather than skip there. Locally they skip only if Chromium can't launch.
  • test_install.py counts host links from SKILLS instead of a hard-coded 8.

Validation

  • The full stack gate passes: control.py verify changed --base origin/main --jobs 2 ran 124 tests, lint and docs checks, with the browser tests running in real Chromium.
  • Mutation checks: each of these makes a test fail:
    • removing the route guard
    • replacing checked redirects with direct fulfilment
    • removing the post-run settle
    • disabling the 5xx or page-error listeners
    • review ignoring automated failures
    • dropping the sign from number capture
      One mutation isn't caught at run time: letting a screenshot failure pass, because tests can't force a screenshot error. Review still blocks that step.
  • One independent reviewer covered five rounds: the skill, the checker, and three re-reviews. Its exploits for local files, off-origin hosts, popups, form posts, meta refresh, single and multi-hop redirects, late navigation and empty reviews are all blocked now. It also confirmed that cookies across redirect hops, POST→303→GET, same-origin chains, timing, history and screenshots behave correctly.

Known limits

  • The checker has been tested only against a local fixture, not against a live Kizen login (SSO, MFA, session expiry). A login redirect to another host fails the run by design, so real runs need a saved session made with npx playwright codegen --save-storage=….
  • Redirect hops are now script navigations: Referer is the previous hop's URL, and user activation isn't carried over. 307/308 redirects of form posts fail the run.
  • CI downloads Chromium on every run with no cache, and the post-run settle raises stack-test time from about 20s to about 30s.
  • Upgrades: existing consumer installs will be told "Managed instructions changed" or "Existing file differs" and need a manual reconciliation, like any release that changes tool files.

🤖 Generated with Claude Code

https://claude.ai/code/session_01HSV4zUy6rF1vgHFSyUmuva


Generated by Claude Code

Demos no longer pay the full delivery path. The new skill runs a scripted
build, self-check, honesty check and rehearsal loop outside Jira, the
controller, review, CI and merge, and hands approved demos to /sstack for
promotion into tickets. Demo code stays under demos/ or demo/* branches.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HSV4zUy6rF1vgHFSyUmuva
Drop the demos/ registration example, which made the stack gate fail and
could not unblock verification anyway. Defer Kizen writes to platform.md's
approval rules, keep demo work on demo/* branches, and make promotion emit
schema-valid eval cases with acceptance for faked steps. Route demos before
the PM skill loads delivery references and mention the skill in installed
instructions.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HSV4zUy6rF1vgHFSyUmuva
Deleting earlier records is itself a write outside the approved demo
scope. Use a fresh run marker per run instead, which the readback
already matches on.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HSV4zUy6rF1vgHFSyUmuva
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HSV4zUy6rF1vgHFSyUmuva
ui_eval.py drives a JSON UI script in Chromium against one approved
origin, screenshots every step, records console, page and request errors,
and emits captured values as eval result cases. The review command binds
an agent's per-screenshot visual verdicts, from computer use or viewing
the images, to the screenshot hashes. The demo and verify skills use it,
and CI installs Chromium and requires the browser tests.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HSV4zUy6rF1vgHFSyUmuva
Abort top-level navigations to other origins before any request is sent,
covering links, delayed scripts, redirects and popups, and reject goto
paths that can leave base_url, including file and scheme-relative URLs.
Screenshot failures now fail the step, and review blocks reports with no
reviewable screenshots. Numeric capture needs exactly one standalone
number, recorded URLs drop query strings, and ignore_console only
suppresses console noise. Browser tests skip only when Chromium is
missing, unless CI requires them.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HSV4zUy6rF1vgHFSyUmuva
Browsers follow network redirects without routing them, so top-level
pages are now fetched with redirects disabled and each Location is
checked before the browser follows it. The run settles briefly after the
last step so a delayed off-origin navigation is caught, and numeric
capture rejects ranges, dates and attached units.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HSV4zUy6rF1vgHFSyUmuva
Browsers follow redirects without routing them, so a same-origin hop
could lead to an unchecked off-origin one. Each same-origin redirect is
now replaced by a new navigation, which the guard fetches and checks
again, with a hop limit and no method-preserving redirects of form
posts. Fetch failures are recorded.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HSV4zUy6rF1vgHFSyUmuva
@sangyeT
sangyeT merged commit 0cf040a into main Sep 30, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants