id-kit / README.md
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
# ardegazu-id-kit

One identity across the [ardegazu.ro](https://ardegazu.ro) suite.

An identity is a single 32-byte Ed25519 seed (43 chars of base64url — short
enough for a password manager). This package holds everything the suite's apps
share about it:

- **`identity`** — the Ed25519 identity: seed → keypair, session binding
  assertions, raw signatures, 4-emoji fingerprints.
- **`xkey`** *(separate entry: `ardegazu-id-kit/xkey`)* — the deterministic
  X25519 encryption identity derived from the same seed, signed X-certs, and
  HPKE-shaped sealed boxes ("wraps"). Needs `@noble/curves` (peer dependency);
  apps that never seal anything skip this entry and the dependency.
- **`profile`** — name, chosen hue, chosen glyph; stable identity-derived
  colors (`hueOfPub`) replacing the old per-session peerId colors.
- **`bridge-host` / `bridge-client` / `protocol`** — the identity
  bridge. The host runs in a tiny page at `https://ardegazu.ro/id/`; every app
  embeds it as a hidden iframe (same-site on `*.ardegazu.ro`, so its storage is
  unpartitioned) and syncs through versioned postMessage with compare-and-set.
  Apps keep a per-app mirror (`<ns>:id`) so booting never waits on the iframe:
  offline PWAs, private windows and apex outages behave exactly like the
  pre-bridge suite. The record also carries an `apps` map — one section per
  app, keyed by the app's own origin (`bridge.putAppState(state)` writes it,
  `lastRecord().apps` / `bridge.appStateOf(key)` read anyone's). Write access
  is origin-isolated and read access is suite-wide, so an app can publish a
  little directory the rest of the suite can act on.

### What a section is for, and what it costs

A section is **directory metadata** — a small index one app publishes so the
rest of the suite can act on it (which banks you've added, which boards you
follow). It is not a place to keep history: the whole identity lives in one
`localStorage` entry on the apex origin, and that quota is an external
constraint, so the record is hard-capped and sections are budgeted inside it.

| limit | value | what it bounds |
|---|---|---|
| `MAX_RECORD_BYTES` | 131072 | the whole serialized record |
| `MAX_APP_STATE_BYTES` | 16384 | one app's section (hundreds of entries) |
| `RESERVED_CORE_BYTES` | 32768 | headroom sections may never take |
| `MAX_APPS_BYTES` | 98304 | the `apps` map as a whole |

The reserve is what keeps `soc` and the profile writable no matter how many
apps hold sections — before it existed, two apps at the per-entry cap could
saturate the record and conflict every other writer forever.

**Units: UTF-16 code units of the JSON serialization** (`JSON.stringify(x)
.length`), never UTF-8 bytes — one rule for every limit above, and the same
rule the suite's sync layer uses. They agree for ASCII and diverge above
U+007F (a Romanian `ș` is one code unit but two bytes), so measuring the other
way would accept locally what the other end drops.

A refused `putAppState` says why: `putAppStateDetailed(state)` resolves
`{ ok, reason }` with `"cas"` (lost the race — retry), `"key"` (this origin
owns no section), `"size"` (over the per-entry cap), `"full"` (the sections'
budget is spent), or `"noseed"` / `"offline"` decided client-side.
`putAppState(state)` still resolves a plain boolean.

Since v2.0.0 the implementation is ClojureScript
(`src/ardegazu/id/*.cljs`, per the suite's CLJS canon in `dev/docs/CLJS.md`).
Consumers see compiled, deterministic ESM in `dist/` plus hand-authored
`types/*.d.ts` — the wire and storage contracts are unchanged and locked by
golden vectors extracted from the v1 TypeScript implementation
(`test/vectors/`, byte-exact signatures included).

## Consuming

```jsonc
// package.json
"dependencies": { "ardegazu-id-kit": "git+https://git.ardegazu.ro/id-kit.git#v2.0.0" }
```

The package ships compiled ESM (`dist/`) with TypeScript declarations
(`types/`) — no transpile-the-source config needed (v1 consumers can drop
their `optimizeDeps.exclude` / `fs.allow` entries whenever convenient).

```ts
import { IdBridge } from "ardegazu-id-kit";

const bridge = new IdBridge({ ns: NS });
const boot = await bridge.boot();
// boot.identity  — Identity | null (null = run identity-less, as always)
// boot.profile   — { name, hue, glyph }
// boot.conflict  — bridge holds a different identity → show the chooser:
//                  bridge.adoptBridgeSeed(rec) / bridge.keepLocalSeed(rec)
```

npm records the resolved commit in `package-lock.json`, so builds are pinned
even though the mirror moves. Update by bumping the `#tag`.

### Working on id-kit itself

In the consuming app: `npm install ../id-kit` (a `file:` link), iterate, then
restore the git URL before releasing. Note that on `localhost` the bridge
iframe is cross-site to the apex, so dev sessions see partitioned (empty)
bridge storage — dev identities are per-origin, which is expected.

### Dev identities on localhost

The bridge origin allowlist covers `*.ardegazu.ro` plus localhost ports 4173
and 5173 (the suite's pinned dev/preview ports).

## Releasing

```sh
npm run check                       # shadow-cljs compile + consumer.ts type smoke
npm run build && npm test           # rebuild dist/, vectors + bridge tests against it
deploy/check-dist.sh                # committed dist == fresh build, no local paths
git commit … && git tag vX.Y.Z
cd ../git && ./assemble.sh id-kit
```

`dist/` is committed and must be byte-deterministic: exact-pinned
clojurescript + shadow-cljs in `deps.edn`, `scripts/normalize-gensyms.mjs` in
the build, and `deploy/check-dist.sh` as the gate (see `dev/docs/CLJS.md`).

The publish pipeline is the suite's usual one: `deploy/publish-repo.sh` builds
an anonymity-gated, dumb-HTTP-clonable bare mirror that assemble.sh pins into
git.ardegazu.ro. Tags ride along in `info/refs`, which is what lets npm
resolve `#vX.Y.Z` over the IPFS gateway.

## License

MIT.

static mirror of HEAD · about · clone: git clone https://git.ardegazu.ro/id-kit.git