| name | add-supporting-feature |
| description | Add a single supporting (non-vulnerable) feature to a working ARENA skeleton app — a catalog, detail page, profile, order history, cart, etc. Triggered when the prompt contains "add-supporting-feature", "ARENA supporting feature", or a Feature block whose vuln is "none". Builds a real, reachable, working page wired into the shared nav. No vulnerability, no solution writeup.
|
add-supporting-feature skill
You are the feature-writer stage, adding exactly one supporting
feature to a skeleton app that already builds and serves /api/health. This
feature hosts no vulnerability — it exists so the lab feels like a real
product and so the vuln features have real pages to link to. Do not touch
other features.
After you report done, a usability-judge agent browser-drives this feature to
confirm it is reachable from the nav, works end-to-end on real seeded data, and
behaves like a real product feature. A failing verdict bounces the feature back
to you with specific feedback. Build it so a real user can reach and use it by
clicking, not by typing the URL.
Inputs (in the user prompt)
- A Feature block:
name, feature_kind, endpoint, auth_role, notes.
- The app theme.
- A prior-attempt feedback block (only on a retry): the usability judge's
issues from the last attempt.
$ARENA_WORKDIR — the app under construction lives at $ARENA_WORKDIR/app/.
0. Plan first
Your prompt includes a "Your contract" + "Implementation plan" section the
feature planner wrote for you. Load its impl_steps into TodoWrite verbatim and
execute them in order — do NOT re-decompose the feature — and honor the contract's
boundary (write only its listed files; link to the exact routes it gives). The
breakdown below is what those steps cover:
- The backend route(s) listed in
feature.endpoints (each method path),
created as your own route fragment (per the tech-<stack> skill — e.g.
routes/<feature>.py, routes/<feature>.js, routers/<feature>.py, or
src/routes/<feature>.php) — a real feature usually spans several routes
(list + detail, form + submit); build them all in that one fragment.
Supporting features are clean: no vulnerable endpoints.
- Any seed / DB tables it reads (reuse what the skeleton already seeded; add
tables in your own
schema/<feature>.sql + seed/<feature>.sql fragments,
idempotent and matching the template, only if genuinely needed). Never edit a
shared DB-init file.
- The UI wiring: your own nav/feature fragment with a real nav entry + a view.
- A self-check that the page renders real data and isn't a dead end.
What you build ($ARENA_WORKDIR/app/...)
You add a feature by creating your own self-contained fragment files and
never editing any shared entrypoint (app.py/server.js/main.py/
index.php, core.py/core.js, App.jsx/App.vue, index.html/app.js,
base.html). The app auto-discovers your fragments. The tech-<stack> skill is
authoritative on the exact filenames and shapes — defer to it.
The app shell owns sign-in / sign-out / auth state — never touch it. The
skeleton already renders the login form, the "Sign out" control, and the
logged-in/out gate. Do NOT add your own login/logout button, re-implement session
handling, or render an auth/account widget; a second sign-out that doesn't clear
shell state is a real bug.
- Add every backend route listed in
feature.endpoints in your route fragment,
reading real seeded rows (parameterized queries — supporting features are
clean by design; do NOT introduce an injection or any other flaw in any of
them).
- Wire it into the UI by creating your own nav/feature fragment — there are no
shared "feature nav / feature section" markers to edit:
- Nav entry comes from your fragment's
nav (a nav/<feature>.json
{label, href, slot} for server-rendered stacks, or the nav export in
your feature module for SPAs), with a real user-facing label
("Catalog", "My Orders", "Profile"), not the raw endpoint. The shared
layout renders the sorted union of all features' nav automatically.
- View that renders the route's response — a list of seeded rows, a
detail view, a form, etc. — shaped to
feature_kind. Never an empty or
placeholder panel.
- Render every page through the same shared layout (e.g.
render_page
for server-rendered stacks) so the nav, sign-in state, and styling match
the rest of the app. Do not hand-roll a second, divergent nav.
- For
auth_role other than anon, gate the route/server behind the
session the skeleton already established.
- Use
notes to wire this feature to its siblings (link to a detail page,
read the same seed table another feature owns, etc.).
Hard rules
- This feature is not vulnerable. Do NOT write a
solutions/<name>.md and
do NOT add any deliberate flaw. (No exploit verifier runs for this stage.)
- Confine all writes to
$ARENA_WORKDIR/app/.
- No hint comments (
// vulnerable, # TODO: sanitize, "exploit", "ctf").
The page must look like a real product.
- Use the credentials from the spec verbatim when a role is needed.
- Do NOT build, serve, run, or curl the app in this session — no Docker, no
serving it (
php -S, flask run, node server.js, npm run) and no
curl/wget against localhost to test your code. You have no running
container here; the verifier rebuilds the image and the browser judge drives
the page afterward. Bash is for at most a one-shot syntax lint — never a
build/run/test loop.
On usability feedback (retry)
If a retry contains a prior-attempt feedback block, read it carefully and fix
the cause — usually "no nav entry / only reachable by URL", "empty results
panel", "broken layout / divergent nav", or "doesn't render seeded data". Fix
only what the feedback describes; don't rewrite the rest.
Integrate with the lab (read the merged ledger, write your own fragment)
The prompt may include a "What already exists in this lab" block — the merged
realized state of the skeleton plus features built in earlier waves. Use it so the
lab feels like one product:
- Reuse, don't reinvent. Render through the listed
shared_layout, use the
listed style_tokens, reuse the listed components by name, and read the listed
tables rather than creating parallel ones. Link from existing pages where it
makes product sense.
- Record YOUR contributions in your own ledger fragment when done — write
$ARENA_WORKDIR/build_ledger.d/<feature>.json (create the dir/file; it is YOURS
alone, so parallel features never collide). It's a BuildLedger-shaped JSON with
just your additions: your nav entry in nav ({label, href, feature}), any new
reusable component in components, any table you added in data_shapes, and
your feature name in features_done. Never write build_ledger.json or
another feature's fragment.
Before you finish
Run your own self-check before reporting done. Invoke the
arena-spawner:validate-feature skill and work its checklist honestly —
usability, behavior/coherence, and hygiene. Skip its exploit-reachability
section: this feature hosts no vuln. A "no" or "unsure" answer is a bug to
fix now, not to hand off — the usability judge will bounce the feature back
otherwise.
Done when
- Your route fragment is implemented and reads real seeded data with
parameterized queries; your schema/seed fragments (if any) are created — and
you edited no shared entrypoint.
- The feature is reachable by clicking a real nav entry (from your own
nav/feature fragment), rendered through the shared layout, and its view shows
real data — not a dead end.
- No solution writeup or deliberate flaw was written.
- You ran the
validate-feature self-check (minus the exploit section) and
every applicable answer is yes.
- Report done — the orchestrator runs the usability judge against your page.