---
schema: v2_plus (informal "v3" — additive, backward-compatible)
filed: 2026-04-28
sprint: S152
supersedes: nothing — additive over WALKTHROUGH_V2_SCHEMA.md
---

# Schema delta — V2 plus the cards_v2 affordances

The V2 schema at `~/garrison/site/WALKTHROUGH_V2_SCHEMA.md` already
covers `mock_ui`, `data_recipients`, `transforms`, `data_fate`,
`cursor_path`, `public_ceremony`, and `simulation_posture`. This file
adds five cards_v2-specific affordances:

1. `narrate` — Web Speech API read-aloud, per card
2. `tag_along` — per-card .md module export; accumulates in a session
   bundle that doubles as a Lego-block library for future composition
3. `attestation_strip` — bottom-of-card cryptographic surface
4. `fingerprint` — click-path accumulator yielding the graduation cert
5. `comment_redirect` — per-card comment field that round-trips to
   the visitor's own claude.ai session for critical review, with an
   invitation to post the dialogue back to MerkleTrust

Plus one step-level addition:

- `step.attestation` — what cryptographic event fires when this step
  plays, in three modes (live, simulated-online, offline)

Backward compatibility: full. Anything that does not declare these
fields renders the existing V2 surface. Anything that does, gets the
new affordances on top.

## Top-level additions

```jsonc
{
  "card_id": "40h_sim_small_dental_practice",
  "title": "...",
  "schema_version": 3,                          // bumped from 2; V2 still readable

  "narrate": {                                  // OPTIONAL but recommended on every card
    "enabled": true,
    "voice_hint": "en-US, neutral, conversational",  // Web Speech API filter
    "rate": 1.0,                                // 0.5–2.0; 1.0 default
    "pitch": 1.0,                               // 0.5–2.0; 1.0 default
    "auto_start_on_step": false,                // visitor-triggered by default
    "highlight_word_while_speaking": true,      // accessibility
    "stop_on_navigate": true                    // back / next stops current speech
  },

  "tag_along": {                                // OPTIONAL but recommended on every card
    "enabled": true,
    "label": ".md",                             // button label
    "tooltip": "Add this card's summary to your take-along bundle",
    "icon": "document-arrow-down",
    "card_summary": {                           // computer-optimized markdown for LLM consumption
      "title": "...",                           // mirrors card title
      "audience": "...",                        // one-line persona
      "what_happened": "...",                   // 2–4 sentences in plain English
      "data_movements": [ /* who-saw-what summary */ ],
      "cryptographic_events": [ /* what real ops fired */ ],
      "verifiable_now": [ /* what the visitor can re-prove */ ],
      "build_on": [ /* invitation lines, where applicable */ ]
    }
  },

  "attestation_strip": {                        // OPTIONAL but recommended on every card
    "enabled": true,
    "default_state": "collapsed",               // collapsed | expanded
    "expand_label": "what just happened, cryptographically",
    "modes": {
      "live": {                                 // online + GarrisonNode reachable
        "label": "live · attesting to garrison node",
        "color_hint": "merkle_green",
        "show_real_anchor": true,
        "show_real_leaf": true,
        "endpoint": "https://merkle.example.com/api/v3/leaf"
      },
      "simulated_online": {                     // online + GN unreachable
        "label": "simulated · code is real",
        "color_hint": "neutral",
        "show_real_anchor": false,
        "show_real_leaf": false,
        "honest_note": "If a Garrison Node were reachable, this click would emit a leaf to the next ceremony anchor."
      },
      "offline": {                              // running on visitor's metal
        "label": "local · your node is anchoring",
        "color_hint": "merkle_green",
        "show_real_anchor": true,
        "show_real_leaf": true,
        "endpoint": "http://localhost:9100/api/v3/leaf"
      }
    }
  },

  "fingerprint": {                              // OPTIONAL — declare once per card-series, lives on every card
    "enabled": true,
    "accumulator_kind": "click_path_sha256",    // hashes ordered (card_id, step_id, ts_rounded_to_second) tuples
    "seal_at": "graduation",                    // when does the digest become the cert
    "cert_filename": "merkletrust_visitor_cert.json",
    "tamper_test_invitation": "Edit one byte in your cert and try logging in again. The next byte revert restores access.",
    "validation_modes": ["local_only", "anchored_to_public_chain"]
  },

  "comment_redirect": {                         // OPTIONAL but recommended on every card
    "enabled": true,
    "field_label": "Your thought, question, or counter-example",
    "field_placeholder": "What stood out? What seems off? What would you ask?",
    "submit_label": "Run it past Claude",
    "redirect_target": "claude_ai",             // claude_ai | chatgpt | custom_url
    "redirect_url_template": "https://claude.ai/new?q={prompt_url_encoded}",
    "prompt_assembly": {
      "header": "Above is a Merkle Trust walkthrough card. The user has the following thought / question / objection. Give your current independent take. Be specific about what holds up and what doesn't.",
      "card_md_block": true,                    // include the tag_along.card_summary as the first block
      "user_comment_block": true,               // include the visitor's comment, verbatim
      "footer": "Reply with your honest current view. The user can paste your reply back to Merkle Trust if they want."
    },
    "return_invitation": {
      "enabled": true,
      "label": "Bring it back if you want",
      "placeholder": "Paste your dialogue here as: 'this is me and Claude on this sim.'",
      "destination": "card_discussion",         // card_discussion | sovereign_inbox | none
      "moderation": "pre_publish_review"
    },
    "fallback_for_non_claude_users": {
      "show_alternate_targets": true,
      "alternates": [
        { "label": "ChatGPT", "url_template": "https://chat.openai.com/?q={prompt_url_encoded}" },
        { "label": "Copy prompt to clipboard", "action": "clipboard" }
      ]
    },
    "privacy_note": "Your comment never leaves Merkle Trust unless you press submit. The redirect opens in your own session on the LLM provider you choose; their handling is theirs."
  },

  // ... existing V2 fields (mock_ui, data_recipients, transforms,
  // simulation_posture, cursor) unchanged.

  "steps": [ /* see step-level addition below */ ]
}
```

## Step-level addition: `step.attestation`

```jsonc
{
  "id": "minutes_5_14",
  "title": "Minutes 5–14 — The first concrete moment",
  "prose": "...",
  "cursor_path": [ /* unchanged */ ],

  "attestation": {                              // OPTIONAL per step
    "fires_on": "step_enter",                   // step_enter | step_exit | cursor_at_waypoint
    "operations": [
      {
        "op": "sha256",
        "subject": "the seal of Mrs. Alvarez's op-note",
        "input_summary": "op_note_v1.pdf bytes (synthetic)",
        "output_label": "leaf_a3f7c2…b91d",
        "real_in_browser": true,                // crypto.subtle.digest fires
        "real_in_garrison_node": true           // emits to the running JAR9 instance if live
      },
      {
        "op": "merkle_leaf_emit",
        "subject": "added to today's ceremony tree",
        "input_summary": "leaf_a3f7c2…b91d",
        "output_label": "tree_position_18241",
        "real_in_browser": false,               // pure server-side
        "real_in_garrison_node": true
      }
    ],
    "strip_summary": "1 file sealed · 1 leaf added to today's tree · anchor at next ceremony 07:42",
    "deep_link": "/anchor-receipts/2026-04-28-07-42"  // resolves to a real receipt page when live
  },

  // step.interactive arrays are deprecated for cards_v2 but
  // remain valid input to the schema; the runtime renders them
  // only in V2-fallback mode.
}
```

## What the runtime needs to do (engineering hooks)

Specifying for the next thread, not implementing here:

- **Read the new top-level blocks** and surface the four affordances
  (narrate, tag_along, attestation_strip, fingerprint) regardless of
  step content.
- **Mode detection** at runtime startup: probe the live endpoint with
  a 200ms timeout; on success, mode = `live`; on failure, mode =
  `simulated_online`; if `window.location.protocol === "file:"` or
  the local endpoint responds, mode = `offline`.
- **Cursor-only navigation**: ignore step.interactive arrays unless
  the card is in V2-fallback mode (i.e., schema_version < 3).
- **Tag-along bundle** in `window.merkletrust.tagAlong`, an array of
  card summaries the visitor accumulates. At graduation, serialize
  to `journey.md` and trigger download.
- **Fingerprint accumulator** in `window.merkletrust.fingerprint`,
  rolling SHA-256 over `(card_id, step_id, ts_seconds)` tuples. At
  graduation, sign with a session keypair and emit `cert.json`.
- **Attestation strip** mounts at the bottom of every card. Reads
  `step.attestation.strip_summary` for the current step. Expand reveals
  the full operations list with deep links to live receipts.
- **Narrate** uses Web Speech API. Voice selection from the available
  list filtered by `voice_hint`. Falls back to default voice if no
  match.

## Validation

A JSON schema file at `cards_v2/schema/cards_v3.schema.json` will
codify the additions. Out of scope for this CONTRACT pass; queued
for the engineering thread.

---

*Filed: 2026-04-28 · Sprint S152*
