Skip to main content
Glama
README.md
# Latch

**Standing spend rules for the house.**

Alexa+ filled a $250 cart and added things people excluded. A yes on checkout does not stop the next restock. Latch is the rule that stays after the chat dies.

Grant a cap. It restocks inside that cap. A price jump holds. You undo until 9pm. You revoke, and it stops.

Not a shopping chatbot. Not “confirm this one order.” Not a booking receipt.

Amazon Developer Hackathon · Alexa+ track · 23 Oct 2026. Product name is **Latch**.

## Demo (about three minutes)

1. Grant: Hill’s Science Diet 15lb, max $40, fire at 3 days left, hold if price jumps 15 percent, undo until 9pm. Card shows **LCH-14**.
2. Time-skip to 3 days. It buys at $28. Card shows **LED-8821** and mock order **ORD-440**.
3. Kill the tab. “What did you buy?” Same IDs.
4. Mutate the fixture to $33. Trigger. **Hold. No charge.**
5. Undo the $28 buy. Ledger is `undone`.
6. Revoke. Next trigger is `denied`.

Speech is illegal without `entry_id` and `provider_ref`. Empty ledger → it says it failed.

Local: `npm run dev` then open the printed URL. Buttons on the left are the tape.

## Golden cases

| # | Case | Pass |
|---|---|---|
| 1 | Grant then trigger → commit under cap | `committed` + `LED-` + `ORD-` |
| 2 | Excluded SKU (Sony under a Hill’s rule) | `denied` / `excluded_sku` |
| 3 | Price +18% | `held` / `price_jump` |
| 4 | Undo before 9pm | `undone` |
| 5 | Undo after deadline | rejected |
| 6 | Revoke then trigger | `denied` / `rule_revoked` |
| 7 | New session | same `entry_id` |
| 8 | Empty ledger | cannot say “bought” |
| 9 | Qty 2 when max 1 | `denied` / `qty_exceeded` |
| 10 | Clock advance, no grant | no buy |

```text
npm test
```

## Protocol

- MCP **2025-11-25** Streamable HTTP at `POST /mcp`
- Tools: `grant_latch`, `get_latch`, `run_triggers`, `undo_entry`, `revoke_latch`
- Demo-gated: `advance_clock`, `set_catalog_price`
- Cards: `/apps/grant.html`, `/apps/hold.html`, `/apps/ledger.html` (`resourceUri` on mutating results)
- State: D1 rules + ledger. Catalog: KV fixture `hills-15lb` / `sony-headphones`
- Household: `X-Latch-Household`. No MCP session id required.
- Privacy / terms: `/privacy`, `/terms`

Friction Amazon asked for: [FRICTION.md](./FRICTION.md).

## Setup

Needs Node and Wrangler. No Token Factory key. No model.

```text
npm install
npx wrangler types
npx wrangler d1 migrations apply latch --local
npm test
npm run dev
```

Deploy only when you want a public URL:

```text
npx wrangler d1 create latch
npx wrangler kv namespace create CATALOG
# put the real ids in wrangler.jsonc, then:
npx wrangler types
npx wrangler d1 migrations apply latch --remote
npx wrangler deploy
```

Custom hostname later: `mcp.latch.<your-domain>`.

## What this is not

- No runtime LLM
- No real Amazon cart
- No VoiceCart `confirmed=true` checkout
- No restaurant booking
- No Alexa or Amazon in the product name

Law: [PRODUCT.md](./PRODUCT.md). Design: [DESIGN.md](./DESIGN.md).