# The Brick Standard — v0.1 (MVP)

A **brick** is one function for a personal space: a poll, a tip jar, an event RSVP.
Anyone can build one. Every brick runs through review before it can be installed.
This document is the contract between bricks and the host platform.

## 1. Anatomy of a brick

```
my-brick/
  manifest.json    — identity, permissions, entry point
  brick.html       — the entire brick: one self-contained HTML file
```

**manifest.json** (all fields required unless noted):

```json
{
  "id": "polls",
  "name": "Polls",
  "version": "1.0.0",
  "author": "ExM Founding Team",
  "description": "Ask your space a question. Votes animate into live result bars.",
  "permissions": ["storage", "ui"],
  "networkAllowlist": [],
  "entry": "brick.html",
  "icon": "📊"
}
```

**Permissions** (declared up front, enforced by the host):
- `storage` — read/write through the host storage API (namespaced per brick + space).
- `ui` — render UI, resize its frame, show toasts.
- `network` — make network calls, ONLY to hosts listed in `networkAllowlist`. Empty list = no network at all. Any network call to a non-allowlisted host fails review and is blocked at runtime.

**brick.html rules:**
- One file. All CSS and JS inline. No external stylesheets, fonts, scripts, or images.
- No `eval()`, no `new Function()`.
- No `document.cookie`, no `localStorage` / `sessionStorage` — use the host storage API.
- No `fetch` / `XMLHttpRequest` / `WebSocket` unless the `network` permission is declared AND every URL's host is in `networkAllowlist`.
- No access to `window.top`, `top.location`, or `parent` beyond the postMessage API below.
- No form posts to external URLs.

## 2. The sandbox

The host renders every brick inside:

```html
<iframe sandbox="allow-scripts" src="bricks/polls/brick.html"></iframe>
```

- `allow-scripts` only. No `allow-same-origin` (the brick gets an opaque origin — it cannot read the host's cookies, storage, or DOM). No top-level navigation.
- The iframe is sized by the brick via the resize API; default height 240px.

## 3. Host ↔ brick API (postMessage only)

**Brick → host** (`parent.postMessage(msg, '*')`):

| type | fields | meaning |
|---|---|---|
| `brick:ready` | — | brick loaded, ready for context |
| `brick:storage-get` | `key`, `reqId` | read a namespaced value |
| `brick:storage-set` | `key`, `value` | write a namespaced value |
| `brick:resize` | `height` | request frame height (px) |
| `brick:toast` | `text` | ask host to show a toast |

**Host → brick:**

| type | fields | meaning |
|---|---|---|
| `host:init` | `payload: { space, viewer, config }` | sent after `brick:ready`; space = `{id, owner}`, viewer = `{id}` (mock identity in the MVP) |
| `brick:storage-value` | `reqId`, `value` | answer to a storage-get |

**Storage namespacing:** the host stores values under `brickstore:{spaceId}:{brickId}:{key}` in its own storage. A brick can never see another brick's keys, another space's keys, or raw host storage.

**Reference client shim** (copy into every brick — ~25 lines):

```html
<script>
const Brick = (() => {
  let reqId = 0; const pending = {};
  window.addEventListener('message', e => {
    const m = e.data || {};
    if (m.type === 'brick:storage-value' && pending[m.reqId]) {
      pending[m.reqId](m.value); delete pending[m.reqId];
    }
    if (m.type === 'host:init') {
      Brick.ctx = m.payload;
      document.dispatchEvent(new CustomEvent('brick:init'));
    }
  });
  const send = msg => parent.postMessage(msg, '*');
  const Brick = {
    ctx: null,
    ready() { send({ type: 'brick:ready' }); },
    get(key) {
      return new Promise(res => {
        const id = ++reqId; pending[id] = res;
        send({ type: 'brick:storage-get', key, reqId: id });
      });
    },
    set(key, value) { send({ type: 'brick:storage-set', key, value }); },
    resize() { send({ type: 'brick:resize', height: document.body.scrollHeight }); },
    toast(text) { send({ type: 'brick:toast', text }); }
  };
  return Brick;
})();
Brick.ready();
</script>
```

## 4. The review pipeline (three gates, no exceptions)

1. **Gate 1 — automated static scan.** The bundle source is scanned for banned patterns (eval, `new Function`, cookie access, raw storage, top-frame access, network calls outside the allowlist). Output: pass / warn / fail with exact line hits.
2. **Gate 2 — AI review summary.** Heuristic risk card: permissions requested vs. permissions actually used in code, data-exfiltration surface, overall risk (low / medium / high / critical) with reasoning.
3. **Gate 3 — human sign-off.** A reviewer approves or rejects. Approved bricks are cryptographically marked "signed" (signature stub in the MVP; real signing in production) and appear in the brick directory. **There is no auto-approve path.**

## 5. Versioning

- `id` is permanent. `version` follows semver. Every version re-runs all three gates.
- Breaking the contract in an update (new permissions, new network hosts) requires re-review from Gate 1.

---
*Brick Standard v0.1 — written for the Blockparty MVP, 2026-10-09.*
