# GlobalCBP data contract

Static site (no framework, no bundler) + zero-dependency Node 22 ESM scripts. Hosted on GitHub Pages from `site/`.

## Layout
- `data/curated/items.json`     – the fixed item catalogue (do not edit ids)
- `data/curated/blocs.json`     – shared rule sets (eu, gcc) that countries inherit via `"bloc"`
- `data/curated/countries/<ISO2 lowercase>.json` – hand-verified rules from official sources
- `data/live/gov-uk/<slug>.json`, `data/live/canada.json`, `data/live/smartraveller.json` – raw weekly ingests + `data/live/_cache.json` (ETags)
- `scripts/ingest/*.mjs`, `scripts/lib/http.mjs`, `scripts/build.mjs` – ingest + merge
- `site/data/index.json`, `site/data/c/<iso2>.json` – build output the frontend reads

## Status vocabulary (exactly four + unknown)
`clear` (allowed, no formality) · `limit` (allowed up to a quantity/value) · `declare` (must declare, show permit, or pass inspection) · `banned` (prohibited for personal import).
Missing item in a country file = "unchecked" in the UI (never guess).

## Curated country file
```json
{
  "iso2": "jp", "name": "Japan", "region": "Asia", "bloc": null,
  "verified": "2026-10-17",
  "authority": { "name": "Japan Customs", "url": "https://..." },
  "currency": { "code": "JPY", "declare_over": "JPY 1,000,000" },
  "rules": {
    "alcohol": { "status": "limit", "headline": "3 bottles (760 ml each)", "detail": "1–2 sentences, plain English.", "source": "https://exact-page" }
  },
  "gov_uk_slug": "japan",
  "canada_slug": "japan"
}
```
`headline` ≤ 60 chars (shown on a big tile); `detail` ≤ 280 chars; `source` must be a real official URL you actually read. Only record what a source states; omit an item rather than guess.

## Bloc file (`blocs.json`)
`{ "eu": { "name": "...", "members": ["fr","de",...], "authority": {...}, "rules": { same shape as country rules } }, "gcc": {...} }`
A country's own `rules` override its bloc's.

## Item ids (items.json)
alcohol, tobacco, vapes, cash, meds_rx, meds_controlled, cannabis, meat_dairy, plants_seeds, food_honey, drones, batteries, satphone, wildlife, antiquities, weapons, duty_goods, media

## Languages
Prefer English official pages. If a source has no English version, use the native-language page: keep `source` pointing at it, add `"lang": "<BCP-47 code>"` (e.g. `"ja"`) to that rule, and still write `headline`/`detail` in plain English (your own faithful summary). Rules from English sources omit `lang` (default `en`). For live ingests, if there is no English text, store the native text with a `lang` field next to it. The UI will show a small "source in <language>" note; full translations of the interface and rules come later, so keep all user-facing strings in data/JS constants, not scattered in logic.

## Direction: inbound + outbound, and official source lists
Every country file may now also carry:
```json
"outbound": {
  "<item id>": { "status": "banned", "headline": "...", "detail": "...", "source": "https://...", "lang": "xx?" }
},
"sources": [
  { "title": "Prohibited and restricted goods (Customs)", "url": "https://exact-official-page", "kind": "inbound|outbound|both", "lang": "en" }
]
```
- `rules` = what you may bring INTO the country (unchanged). `outbound` = what you may NOT/only conditionally take OUT of it when leaving (export bans & limits: antiquities/cultural property, CITES wildlife, cash export limits, plants/produce, medicines, weapons, subsidised/controlled goods, etc.). Same four statuses, same item ids (use `duty_goods` only for export-limited goods like gold/subsidised items).
- `sources` = the country's OWN official prohibited/restricted-goods lists and traveller pages (customs, border, health, agriculture, cultural-heritage authority) that a traveller can open to see the full list. Include every official page you actually read plus the authoritative "full list" page even when it was unreadable from the sandbox (mark `"unverified": true`). Native-language pages are fine (add `lang`). Never add third-party/blog/embassy-summary sites unless they're the government's own.
- Blocs (`blocs.json`) may also carry `outbound` and `sources`; country values override bloc values.
