Last Tuesday I almost shipped a skip link into nowhere. The agent guessed three landmarks from a static mock. I owned the approve click. Checkout sat one skip away from vanishing. Could I reverse the rewrite after publish? No. That missing reverse path is the whole story.
This is not an essay about trusting models. It is one consequential decision. I am the decision owner. The consequence is a keyboard user leaping past pay. The point of reversibility must exist before I click approve. Visual layout is a poster. Landmarks are a trail. Why would a model blaze that trail from pixels?
I now run a from-zero gate before any agent rewrites navigation. Treat this as a proposed tutorial, not a finished study. Each stage has a command and a check. Fail a check, stop. Do not negotiate with an empty slot.
Stage 1 — write the decision card
I refuse to open the model until the card exists. The card is dull on purpose. Dull cards catch glamorous guesses. Save it as gate/decision-card.json.
{
"decision": "approve_inferred_landmarks",
"owner": "design_lead_on_call",
"agent_action": "rewrite_landmarks_and_skip_link",
"consequence_if_wrong": "sr_and_keyboard_users_skip_past_checkout",
"reversibility": "restore_original_map_from_review_record",
"evidence_required": [
"original_landmark_map",
"proposed_landmark_map",
"skip_path_transcript",
"primary_task_success"
],
"empty_slots_block_approval": true,
"hypotheses_are_not_evidence": true
}
Verification: read the file aloud. Can you name the owner in one breath? Can you name the harm? Can you name the restore handle? If any answer turns poetic, rewrite the card. Do not proceed. Poetry is not a control.
Stage 2 — draw only the human click
I sketch the moment a person must decide. I do not sketch the model's inner loop. The agent may draft. The human still owns publish. Here is the flow I actually use.
flowchart TD
A[Agent proposes landmark rewrite] --> B{Evidence file complete?}
B -->|No| C[Hold publish and show empty slots]
B -->|Yes| D[Human runs skip-path rehearsal]
D --> E{Primary task reachable in two skips?}
E -->|No| F[Reject and keep original map]
E -->|Yes| G[Approve with restore handle]
F --> H[Log discarded inference in review record]
G --> H
Verification: put your finger on the diamond that stops publish. If your drawing has no stop, you drew a pipeline. Pipelines do not protect checkout. Would you board a train with no brake lever?
Stage 3 — write two scenarios, not ten
I keep research small so the gate stays honest. Scenario A is the proven skip. Scenario B is the inferred footer. Both live in gate/scenarios.md. The file is the protocol. It is not a slide.
# Landmark gate scenarios
## A — proven skip
Task: from article body, reach checkout.
Success: two skips, visible focus, spoken name matches the label.
Stop if: landing node is not checkout or main.
## B — inferred footer
Task: same purchase after the agent rewrites landmarks.
Stop if: proposed map drops the original main target.
Noise to ignore: extra decorative headings nobody skipped.
Verification: ask the split out loud. Which missing evidence should stop approval? Which extra detail is only noise? If you cannot split those, you will over-research and still ship the wrong skip. More notes are not more safety.
I separate evidence from design hypotheses in the same breath. "Users might prefer a shorter skip list" is a hypothesis. A skip-path transcript is evidence. Do not let the first impersonate the second. The ARIA landmarks guide and bypass blocks describe the trail. They do not bless a visual guess.
Stage 4 — freeze the evidence file
I will not read a chat transcript as a record. Chat evaporates. The gate needs a file the validator can fail. Save the proposal as gate/evidence/proposal.json.
{
"page_id": "checkout-help-article",
"original_landmark_map": [
{"role": "banner", "label": "Store", "id": "site-header"},
{"role": "main", "label": "Help article", "id": "article"},
{"role": "contentinfo", "label": "Legal", "id": "site-footer"}
],
"proposed_landmark_map": null,
"skip_path_transcript": null,
"primary_task_success": null,
"discarded_inferences": [],
"restore_handle": "git:design/landmarks/checkout-help-article@original"
}
Notice the nulls. They are the point. An honest file shows what the agent does not know. Verification: open the file and count nulls. If a proposed map appears before a transcript, you inverted the order. Stop. The agent does not get to fill silence with confidence.
Stage 5 — run a local validator
This script is the original artifact. It does not score the model. It refuses approval when required slots are empty. It also refuses a proposal that drops main. Label it as proposed code. I have not claimed a production rollout.
# validate_landmark_gate.py — proposed local gate, not a model benchmark
import json, sys
REQUIRED = [
"original_landmark_map",
"proposed_landmark_map",
"skip_path_transcript",
"primary_task_success",
]
def roles(maps):
return {item["role"] for item in maps or [] if "role" in item}
def main(path):
data = json.load(open(path, encoding="utf-8"))
missing = [key for key in REQUIRED if not data.get(key)]
if missing:
print("STOP: empty evidence slots:", ", ".join(missing))
sys.exit(2)
original = roles(data["original_landmark_map"])
proposed = roles(data["proposed_landmark_map"])
if "main" in original and "main" not in proposed:
print("STOP: proposed map dropped main")
sys.exit(2)
if not data.get("restore_handle"):
print("STOP: no restore handle, approval is irreversible")
sys.exit(2)
print("PASS: evidence complete; human may now rehearse the skip path")
if __name__ == "__main__":
main(sys.argv[1])
Verification command:
python3 validate_landmark_gate.py gate/evidence/proposal.json
You should see STOP: empty evidence slots on the first run. That failure is success. The gate is awake. If the script prints PASS while transcripts are still imaginary, you broke the validator. Fix the script, not the ethics.
Stage 6 — rehearse the draft on a cheap bench
Only now do I let a model propose a map. I still do not let it publish. I need a place where a bad inference is cheap to throw away. MonkeyCode's free model access and free server option are the rehearsal bench I use for that draft. Disclosure: This article was prepared as part of MonkeyCode's product outreach. I paste the proposed map into the evidence file. I never paste it into production HTML first.
After the draft lands, I fill proposed_landmark_map and nothing else. Then I rerun the validator. It must still stop on the empty transcript. That second stop is the lesson. A fluent proposal is not a skip-path proof. Did the model sound sure? Good. Certainty is not a landmark.
Verification: the evidence file still contains nulls for skip_path_transcript and primary_task_success. If those fields got auto-filled, the bench leaked a hypothesis into the record. Delete the fill. Keep the null.
Stage 7 — run the skip path like a person
I do not ask the model whether the skip works. I walk it. Keyboard only. Screen reader speaking. Two skips toward checkout. I type what I heard, not what I hoped.
# proposed capture — replace with your SR tool's own transcript export
printf '%s\n' \
"skip-1: spoken Store, focus #site-header" \
"skip-2: spoken Help article, focus #article" \
"next: checkout link inside main, focus visible" \
> gate/evidence/skip-path-transcript.txt
Then I copy that text into the JSON field and set primary_task_success to true only if checkout was reachable. If the spoken name disagrees with the visible label, success stays false. Mismatch is a stop, not a polish note. Verification: play the transcript to a colleague who cannot see the mock. If they cannot say where they landed, the evidence is theater.
Accessibility review at this stage is narrow on purpose. I check one main, unique labels, and a skip target that matches an id. I do not pretend this replaces a WCAG audit. I also do not hide recovery behind a tooltip. Focus must be visible because the human has to see the hand-back. Implementation styling is someone else's job. The pattern is mine: no approve click without a spoken, reversible trail.
Stage 8 — keep the discarded map in the record
Rejection is not deletion. If I refuse the proposal, I append it to discarded_inferences and restore the original map through the handle. The agent returns to draft. Publish stays human. That is the hand-back. Analogy time. A librarian does not burn the wrong catalog card. She shelves it as wrong, then keeps the old drawer.
{
"discarded_inferences": [
{
"reason": "skip landed on footer, checkout unreachable",
"proposed_landmark_map": [
{"role": "banner", "label": "Store", "id": "site-header"},
{"role": "contentinfo", "label": "Page end", "id": "site-footer"}
]
}
]
}
Verification: restore the original map in one action. If restore takes a redesign, you never had reversibility. You had optimism. Optimism is not a recovery framework.
What this protocol is not
This gate does not prove the model is safe. It proves you refused to approve silence. Free model drafts can omit empty slots and still sound complete. That is why the validator lives on your machine. I am not publishing quotas, model names, or hardware claims. I have no benchmark to wave. If you need those numbers, this article is the wrong artifact.
Who should not use this approach? Anyone who cannot run a real skip-path rehearsal. Anyone shipping emergency, medical, or legal navigation from a visual guess. Anyone treating a free rehearsal bench as a production control plane. Anyone hoping a longer prompt will replace a restore handle. If your team cannot name a decision owner, stop before stage one.
I still ask the same two questions at the end of every review. Which missing evidence should stop approval? Which extra information would only add noise? For landmarks, an unproven skip path stops me. A debate about decorative headings does not. The agent can keep talking. Checkout does not have to listen.
If you already have a free rehearsal bench, run the validator against one live proposal before you bless a visual guess.
Top comments (0)