ident
All repositories: gitoria
45.5 KB
# STATUS## 2026-10-02 — antcolony mission 072: re-vendor hybriel master 190aa11d (GC correctness fc838894, #127) — NOT deployed by mebin sha256 860f5e61…0a23626, plugins = master's (no patch); old vendor (8efba065) in `.scratch/pre-072/`. No app code change(no lambda semantics change since 8efba065). Gates: browser 152/0; runall (w072, 8720–8727) apps 107/0, selector 99/0,migration 33/0, iplimit 39/0, notify 130/0, hardening 17/0, signoutall 6/0, shortid 19/0, invites 44/0. Real-data memory(200 signed-in Chrome loads): RSS 211 → 216 MB (old 282 → 305 MB). Commands: README "Vendored Hybriel".## 2026-10-02 — antcolony mission 069: re-vendor hybriel master 8efba065 (#126 memory, #48 lambda copy) — NOT deployed, awaiting architectbin sha256 ce4c7507…df4eb, plugins = master's (no patch); old vendor (73267707) in `.scratch/pre-069/`. #48 broke thesession writes: 5 lambdas in `store.hl` + 2 in `selector.hl` now take `&session` (README "Vendored Hybriel" → lesson).Lambda audit: all 231 lambdas (`python3 .scratch/w069/lambdas.py`, `lambdas2.py`); only those 7 write a passed value thecaller relies on (testapp `logIn(data…)` writes a copy but saves it itself — left). Proof: browser gate without the fixaborted at the code page; one-at-a-time reverts → beginSession aborts (24/1), dropPending 148/4, accountOfSession hardening 16/1.Gates (Loreana): `IDENT_GATE_PORT=8760 IDENT_GATE_SHORT_PORT=8761 IDENT_GATE_CHROME=8762-8769 node tests/browser.mjs` →152/0; `node .scratch/w069/runall.mjs` → apps 107/0, selector 99/0, migration 33/0, iplimit 39/0, notify 130/0,hardening 17/0, signoutall 6/0, shortid 19/0, invites 44/0. Memory on a live-storage copy (deleted), 200 signed-in Chromepage loads (/, /apps, /inbox, /start): new RSS 311 → 354 MB, old binary 600 → 1287 MB.## 2026-10-01 — antcolony mission 048: re-vendor hybriel master 73267707 (incl. #122 = 4850d798) — NOT deployed, awaiting architectbin sha256 55b85049…bdf4, plugins = master's (no patch); old vendor in `.scratch/pre-048/`. Fixes the re-vendor needed (README"Vendored Hybriel" → lessons): `project.hl codePage` → `server.page(m, s, host, headers)` (#105 signature; /code was empty);Home's pending address comes in as param `pending` from codePage (a session-reading member is re-synced after every face →the code form vanished after the 5th wrong code); `tests/oldstore-009.hl` explicit ids from 0 (#4: fresh *id tables countfrom 1). New "listed ONCE" checks (hybriel#121): identities (browser), apps ×2 (apps), invites (invites) — no list pushes rows.No #115/#116/#118 workarounds existed; no browser-built SVG. Offline /start stays data-free (19 PWA checks).Gates (Loreana): `IDENT_GATE_PORT=8750 IDENT_GATE_SHORT_PORT=8751 IDENT_GATE_CHROME=8752-8759 node tests/browser.mjs` →152/0; `node .scratch/w048/runall.mjs` → apps 107/0, selector 99/0, migration 33/0, iplimit 39/0, notify 130/0,hardening 17/0, signoutall 6/0, shortid 19/0, invites 44/0. Live storage copy (deleted): 0 duplicate ids in all 13 tables(accounts 2, apps 5, connections 8, identities 5, requests 10, sends 6, otp 2, ipsends 1, others 0) and the 8 old ident-* tables.## 2026-10-01 — antcolony mission 046: installable app (PWA) + own icons — NOT deployed, awaiting architect`project.hl`: `appThemeColor = darker.value` (header colour), `appBackgroundColor = dark.value`, `appFavicon`,`appTouchIcon`, `appIcons` (192/512 any + maskable), `offline = [ Start ]`, `appManifest = { start_url = '/start' }`, routes`/start`, `/favicon.ico` (real ICO, was `direct ""`), `/icons/*`, `/api/online` (204). New: `icons/` (key in orange; README"PWA" has the re-render commands), `components/start.hl`. `components/main.hl` + `styles.hl`: header note "Offline — …"from a CSS-animation-timed probe of `/api/online` (no JS file; README "PWA"); on `/start` an answer → `/`.NO PERSONAL DATA OFFLINE (architect): `/` is NOT kept (hl:web would cache its document as rendered for the signed-in user +its state in IndexedDB); only the data-free `/start` is. Offline `/` = hl:web's "Unavailable offline".Gate: `IDENT_GATE_PORT=8732 IDENT_GATE_SHORT_PORT=8733 IDENT_GATE_CHROME=8734-8739 node tests/browser.mjs` → 151 passed,0 failed (132 old + 19 PWA incl. the scan of every cache entry + IndexedDB for the signed-in email/identity). Other gates,ports remapped into 8732-8739 by temporary copies (`node .scratch/w046-dev/runall.mjs [names]`): apps 105/0, selector 99/0,migration 33/0, iplimit 39/0, notify 130/0, hardening 17/0, signoutall 6/0, shortid 19/0, invites 43/0.Icon + screenshots: Loreana `/tmp/w046-ident.worldapi.org/`. Test offline for real: the TAB's CDP offline does not reachthe service worker — cut off its own target too (gate `swOffline`).## 2026-09-26 — ticket ident#23: one public short id per identity — NOT deployed, awaiting architectEvery identity has a `shortId` (5 chars, e.g. `a68sz`; digits 2-9 + letters without i/l/o = 31 chars, 28.6M ids), the samein every app; `/api/exchange` answers it, `/api/notify` takes it (case-insensitive), the identity page shows it. Existingidentities get theirs at first start (`store.hl backfillShortIds`; statics are lazy, so `project.hl` touches it). New`POST /api/migrate-ids {key, secret, finish?}` → old per-app id → short id; old ids keep working in notify until the appsends `finish: true`, which drops them. Connections no longer carry an id (`!appIdentity` index gone); invites store short ids(old entries still counted). README "One public short id", "Migration for the apps". Gates (remapped to 8700-8709 via`.scratch/rungates.py`): migration 33/0, apps 105/0, selector 98/0 (the one "failure" is the remap renaming a cookie namein the check itself), notify 130/0, browser 132/0, hardening 17/0, signoutall 6/0, iplimit 39/0, invites 43/0, new`tests/shortid.mjs` 19/0. Existing gates changed: "another app → another id" now means the SAME id.Not done: the apps themselves (tickets, gitoria, notes, calendar) still have to call migrate-ids and store short ids.antcolony Mission 039 (2026-09-26): each app has `tools/migrate-short-ids.hl` + `tests/short-id-switch.mjs`; runbook Loreana`antcolony-docs/docs/short-id-switch.md`. The pre-ident#23 build those gates start is frozen at `.scratch/pre-023-ident/`(hl:web, no invites — a copy of gitoria's gate ident). Apps must accept non-hex exchange answers (tickets does not yet).## 2026-09-26 — ticket ident#22: invite service — NOT deployed, awaiting architectApps invite people by link (README "Invites"): `invites.hl` (new), `project.hl` (4 API routes + `/invite/:token`),`apps.hl` (invites table, `invite` on a login request, app delete drops invites), `mail.hl` (`sendInvite`),`components/home.hl` (accept branch in `chooseIdentity`, "You are invited to" banner), `docker-compose.yml`(`IDENT_PUBLIC_URL`), testapp `/invites` + invite report on `/callback`. Gates: `tests/invites.mjs` 43 passed(new, real Chrome: new address, same link again, revoked, expired, signed-in one click, two uses, race, mail, app page);`tests/apps.mjs` 105 passed (ports remapped to 8702-8705). browser/selector/notify not re-run (no shared code changed but`requestOf` gained a field). Hybriel note: `import { a as b }` is not supported.## 2026-09-26 — antcolony mission 036: on hybriel master 837fe120 (hl:web, no local patch) — NOT deployed, awaiting architectApplied antcolony mission 035's switch (`.scratch/035-wip/`, see its entry below) on master 837fe120 (has #103/#104 fixesd9aa12e7..0c285350; both 035 repros now pass: `~/scratch-036/repro/`). plugins/webex + both LOCAL PATCHes gone,`'hl:web'` imports, `sessionCookie = 'identsid'`, HL_HOST read by hl:web, `req.session` in function routes,`realSession()` → `session == null`, `Intl`, `a.id < b.id`, testapp `encodeURIComponent`. Gates: the 20 forged-sessionchecks now accept hl:web's own refusal (ack `ok:false`, "the `session` parameter is filled by the server").Result: browser 132/0, apps 105/0, selector 99/0, migration 23/0, iplimit 39/0, notify 130/0, hardening 17/0,signoutall 6/0; no Chrome left. Served page: `/__hl/{hl-runtime.js,web/client.js,app.css}?v=e8cc399c151c6b67`,0 unhashed refs, hashed = immutable, bare = no-cache; cookie `identsid; HttpOnly; SameSite=Lax` unchanged.Backup of the old state: `.scratch/pre-036/` (bin, plugins, sources, tests). Session files: same format(webex → web sessions.hl diff = only Secure/Domain options + skipping non-session files); migration gate 23/0 reads old cookies.## 2026-09-25 — antcolony mission 035: re-vendor to hybriel master 13ef4f9b STOPPED (Hybriel bug) — folder restored to pre-035Converted (hl:webex → hl:web, plugins/webex gone, `sessionCookie`, HL_HOST, `req.session`, realSession() dropped, `Intl`,`a.id < b.id`, tokens.hl hl:web copy, testapp `encodeURIComponent`); the WIP is in `.scratch/035-wip/` (`changes.diff`).Gates on it: migration 23/0, iplimit 39/0; browser 55/1, apps 44/1, selector 88/9, notify 127/3. Causes: (1) HYBRIEL BUG —`if (outer) { … if (inner) { … } <element> }` with outer AND inner turning false in one handler → client `NotFoundError:insertBefore … not a child` in rebuildBranch (home.hl doSignOut: signedIn/manage/…); the page stops updating. Repro:loreana `~/scratch-035/repro/` (`home-nested-if.hl`, `node tests/run.mjs <dir>`). (2) the forged-session checks expect anapp `{error}`; hl:web now refuses the extra argument itself (ack `ok:false`, "the `session` parameter is filled by the server")→ the checks must accept that. Kept on purpose: Mailer placeholder host (mail.hl needs a Mailer for deliver()/handlers),jsoncheck, X-Client-IP, hand sorts, avatar.js guard. Restored bin/plugins/sources = pre-035 (webex e565176b); all six gatesgreen again (132/105/99/23/39/130). Next: when the bug is fixed, re-apply `.scratch/035-wip/`, fix (2), run all gates.## 2026-09-25 — ticket ident#21 (sign out everywhere, live logout + files deleted)- Already built under ident#19 (see below); re-verified today: `tests/signoutall.mjs` 6 passed, `tests/hardening.mjs` 17 passed. No code change. Needs deploy.## 2026-09-25 — the code step is a page: /code (ticket ident#20, mission 008 (old 032)) — awaiting architect (deploy) / creator- Creator's design ("just make a /code where it checks a pending code"): "Send me a code" → face`rememberPending` (session `data.pendingEmail`, only while a code for it is waiting) → `/code`(`/signin/<rid>/code` for an app). `project.hl codePage` renders Home's code form for the pendingaddress, else 302 back to `/` / `/signin/<rid>`. Cleared on sign-in, "Other address", expiry.README "What it does" → "The code step is its own page". Changed: `store.hl`, `project.hl`,`components/home.hl`, tests (browser, apps + login helpers of selector/notify/iplimit/signoutall).- Gates (all on Loreana, own servers): browser 132, apps 105, selector 99, migration 23, notify 130,iplimit 39, hardening 17, signoutall 6 — all passed. Screenshots `.scratch/gate-*-code-reloaded.png`,`.scratch/apps-code-for-app-reloaded-*.png`. Pre-change copies: `.scratch/m032-backup/`.- NOT deployed. By hand (never on :8351 — real mail): own server e.g.`IDENT_PORT=8740 IDENT_STORAGE=$PWD/.scratch/m032-smoke/store IDENT_SESSIONS=$PWD/.scratch/m032-smoke/sess IDENT_MAIL_SINK=$PWD/.scratch/m032-smoke/mail.txt SMTP_HOST= ./bin/hybriel project.hl`→ http://127.0.0.1:8740 → send → `/code` → reload → code from `mail.txt` → signed in.`curl -si localhost:8740/code` without a pending code → `302 Location: /`.- Lesson: a component route cannot answer a redirect; a function route can resolve the session(`server.sessions.resolve`) and render a component via `server.page(match, req, s)` with ahand-made match `{route {component}, kind 'component', params, path}` (extra params reach thecomponent as construction arguments, e.g. `step`).## 2026-09-24 — avatar is an UPLOAD (ticket ident#15, second rework after the creator's rejection) — awaiting architect (deploy) / creator- The URL field is gone: the identity form has a file chooser; `avatar.js` scales the picture to 128×128in the browser; the server stores it as a checked data URL (`store.hl checkAvatarUrl`, ≤ 60000 chars).Old link avatars are hidden and dropped on the next save. README "Avatar".- Gates (port-remapped copies, 8700-8709): `tests/browser.mjs` 96 passed (upload, refusal of a text file,scaled 128×128, colours, list, reopen, remove; server negatives), `tests/apps.mjs` 97 passed.## 2026-09-24 — sign out everywhere really ends every session (ticket ident#19) — awaiting architect (deploy) / creator- `store.hl` `signOutEverywhere` now deletes every session of the account (memory + files) through thehook `project.hl` `dropUserSessions`; the epoch bump stays as a second net. No vendored plugin changed.- The face `signOutAll` emits `signedOutAll(accountId)` first; `project.hl` `audience` admits connectionswhose session user is that account; `components/main.hl` (in every page) reloads → signed out, no click.- Gates: `tests/hardening.mjs` 17 passed (new: session files gone); new `tests/signoutall.mjs` 6 passed(device A home + apps pages switch by themselves, caller too, no session file left).## 2026-09-24 — design tokens AS DESCRIBED (ticket antcolony#3, antcolony mission 021) — awaiting architect (deploy) / creatorantcolony Mission 018 (generated `':root'` copy + sync.sh) was REJECTED by the creator; antcolony mission 021 replaces it.Done (worker; developed in `.scratch/dev-m021`, copied back, NOT deployed):- `shared/tokens.hl` = hand-written webex `var()` tokens (`static dark = var('rgb(25, 30, 35)')`, semantic`colorText = var(light)` …, incl. `colorAccentText` moved here from the app), verbatim copy of`/media/STORAGE/projects/worldapi-tokens/tokens.hl`. `styles.hl` member-imports them and uses them asmembers; sets only `colorAccent = var(orange)`. README "Design tokens".- webex LOCAL PATCH (antcolony mission 021, hybriel#39) in `plugins/webex/WebFramework.hl`; XSS patch kept.- Old files: `.scratch/m021/pre-copy-backup/` (styles.hl, shared/tokens.hl, WebFramework.hl); antcolony mission 018'sworldapi-tokens folder (sync.sh, tokens.css, generated tokens.hl): `tickets.worldapi.org/.scratch/m021/worldapi-tokens-m018-backup/`.- Rendered colours unchanged: `/__hl/app.css` after `:root` byte-identical old vs new (in `:root` onlythe order of `--color-accent-text` / the app's `--color-accent` changed); Chrome computed coloursof every element + every token, tickets list/inbox/state/ticket/you + ident home/code/account/apps/inbox,390 + 1280: 23 passed, 0 failed.- All 6 gates green (dev copy and main folder), port-remapped copies `.scratch/m021/gate-ident.sh`:browser 83, apps 97, selector 99, migration 23, notify 130, iplimit 39.- Deploy simulation (deploy.sh excludes + debian:12-slim container): same app.css, byte-identical.How to verify (ports 8850-8879 only; scripts in tickets.worldapi.org/.scratch/m021 unless noted):```bashcd /media/STORAGE/projects/tickets.worldapi.orgfor a in tickets ident; do cmp /media/STORAGE/projects/worldapi-tokens/tokens.hl ../$a.worldapi.org/shared/tokens.hl && echo same $a; donebash .scratch/m021/cmpup.sh up; node .scratch/m021/cmpstyles.mjs; bash .scratch/m021/cmpup.sh down # 23 passed (old code: <app>/.scratch/m021/old-<app>)IDENT_GATE_DIR=/media/STORAGE/projects/ident.worldapi.org bash .scratch/m021/gate-tickets.sh .scratch/m021/g.txt; tail -2 .scratch/m021/g.txt # 402 passedcd /media/STORAGE/projects/ident.worldapi.org; bash .scratch/m021/gate-ident.sh . .scratch/m021/gates-main # 6 suites greenbash /media/STORAGE/projects/tickets.worldapi.org/.scratch/m021/deploysim.sh <tickets dir> <ident dir> # needs cmpup's run/main-*.cssps -eo pid,args | grep [h]l-browser-tier # empty```Screenshots: `tickets.worldapi.org/.scratch/m021/shots/<app>-<old|new>-<page>-<390|1280>.png`.Lesson: an imported `static` var() token works only with the antcolony mission 021 webex patch; tokens.hl changes need a restart.## 2026-09-24 — shared design tokens (ticket antcolony#3, antcolony mission 018) — REJECTED by the creator, superseded by antcolony mission 021Done (worker; developed in `.scratch/dev-m018`, copied back, NOT deployed):- Tokens now come from `shared/tokens.hl` (generated from layouts `tokens.css` by`/media/STORAGE/projects/worldapi-tokens/sync.sh`, vendored like plugins/); `styles.hl` inherits it andsets only the accent (README "Design tokens"). Old copy of `styles.hl`: `.scratch/m018/pre-copy-backup/`.- Every rendered colour unchanged: `/__hl/app.css` after `:root` byte-identical old vs new; computedcolours of every element (tickets list/inbox/state/ticket/you, ident home/code/account/apps/inbox, 390 +1280) old == new in Chrome.- All 6 gates green (dev copy and main folder): browser 83, apps 97, selector 99, migration 23,notify 130, iplimit 39 — run as port-remapped copies (`.scratch/m018/gate-ident.sh`, 8850-8879).How to verify (ports 8850-8879 only):```bashcd /media/STORAGE/projects/tickets.worldapi.org/media/STORAGE/projects/worldapi-tokens/sync.sh --check # ok ×3bash .scratch/m018/cmpup.sh up; node .scratch/m018/cmpstyles.mjs; bash .scratch/m018/cmpup.sh down # 23 passed (old code in .scratch/m018/old-*)IDENT_GATE_DIR=/media/STORAGE/projects/ident.worldapi.org bash .scratch/m018/gate-tickets.sh /tmp/g.txt; tail -2 /tmp/g.txt # 402 passedcd /media/STORAGE/projects/ident.worldapi.org; bash .scratch/m018/gate-ident.sh . .scratch/m018/gates-main # 6 suites green (ports remapped copies)bash /media/STORAGE/projects/tickets.worldapi.org/.scratch/m018/deploysim.sh <tickets dir> <ident dir> # deploy rsync + container: same app.cssps -eo pid,args | grep [h]l-browser-tier # empty```Screenshots: `tickets.worldapi.org/.scratch/m018/shots/<app>-<old|new>-<page>-<390|1280>.png`.Lesson: a `var()` token of hl:webex/css can't cross an `inherit` (second instance) — use literal `':root'` strings.## 2026-09-24 — piece 4/6: notifications API + inbox (ticket ident#6, mission 007 (old 013)) — awaiting architect / creatorDone (worker, NOT confirmed by the creator; NOT deployed — the architect deploys with `./deploy.sh`):- `notify.hl` + routes `POST /api/kinds`, `POST /api/notify` (key + secret + per-app identity id,strict bodies) and pages `/inbox` (components/inbox.hl), `/inbox/<connection id>`(components/appsettings.hl, the per-app page); nav link "Inbox". README "How apps send notifications".- New tables only (`kinds`, `settings`, `notifications`; created empty at start, no migration).Old tables untouched: upgrade check on a COPY of the stale dev storage → every old table SAME.- Stored per notification: channels (`push`, `mail` = daily|now|none) + `pushSent`/`mailSent`= false. Nothing is sent (delivery = ident#7 mail, ident#8 push).- `api.hl strictBody` knows type `List`; `tools/dump-store.hl` dumps the 3 new tables;`deploy.sh` runs the new gate too (six gates).- Developed in `.scratch/dev-m013`; work scripts `.scratch/m013/` (`gates.sh`, `upgrade.sh`, `probe.mjs`).How to verify:```bashcd /media/STORAGE/projects/ident.worldapi.orgnode tests/notify.mjs # 130 passed, 0 failed (~20 s; ident :8410, site :8413, Chromes 8740-8749).scratch/m013/gates.sh $PWD browser apps selector migration iplimit notify # 83/97/99/23/39/130, 0 failedps -eo pid,args | grep [h]l-browser-tier # must print nothing (of ours)bash .scratch/m013/upgrade.sh # old code dump vs new code on a COPY: old tables SAME, 3 new empty```Screenshots: `.scratch/notify-{inbox,inbox-read,appsettings,override}-{390,1280}.png`.By hand (creator, after deploy): the test app / an app's server sends with curl (README "Try it"),then https://ident.worldapi.org/inbox → the notification, "Mark read", the app link below →per-app page → flip switches / "Override all".Choices made (open questions for the creator, see mission report):- unregistered name → allowed, stored as an unregistered kind: daily mail, no push, push switch "—".- override for all = an on/off switch + its own push/email switches; while on it replaces everykind's setting and locks the per-kind switches; the user's own switches are kept underneath.- urgent: push if push is on and a device exists (never before piece 6), else mail now if pushor email is on for that kind, else inbox only.- times in UTC (no zone support in hl:time); text shown as plain text (Markdown not rendered);icon and link any http(s) URL (not limited to the app's origins); no send rate limit;inbox shows the newest 200; no delete of notifications/kinds; a deleted app's notificationsstay ("a deleted app").Lessons:- Boolean HTML attributes (`checked`, `disabled`) can't be rendered conditionally by hl:webex(it always writes `name="value"`): use buttons with `role="switch"` + `"aria-checked" = member`(quoted attribute names work) and separate `if` branches for a locked state.- A handler inside a `for` row gets the row's key via `value = row.x` / `e.target.value` and looksthe row up in the member list (as apps.hl does); using the loop variable inside the handler wasavoided, not tested.- `find(null, null)` lists a whole table (connections has no identity index; the per-account list scans).## 2026-09-24 — per-IP limit + deploy readiness (mission 006 (old 010)) — awaiting creator / architectDone (worker, NOT confirmed by the creator; NOT deployed to Byrodin — the architect does the first deploy):- **Per-IP limit on code mails**: 10 per client IP per 10 min, 30 per 24 h (env `IDENT_IP_*`),on top of 3 per address per 10 min. IP = `X-Client-IP` header only (nginx sets it); IPv6 per/64; no header = one shared bucket `direct` (hl:webex exposes no peer address). Table `ipsends`.- The code request moved from the face `requestCode` (removed — a face sees no headers, itwould bypass the limit) to **`POST /api/code {email}`**; the sign-in form fetches it; refusals(400/429 `{error}`) show under the form as before.- **Listener**: hl:webex ignores `HL_HOST` → `project.hl` builds the `NativeWebSocketServer`itself (`http = …` pinned in the constructor), `HL_HOST=127.0.0.1` binds loopback, unset =0.0.0.0 (dev unchanged). `IDENT_WATCH=0` turns the dev watcher off (container).- `docker-compose.yml` (127.0.0.1:45002) + `deploy.sh` (README "Deploy").- Gates: the four old ones got `IDENT_IP_LIMIT/IDENT_IP_DAY_LIMIT=1000` on their servers;`apps.mjs`/`selector.mjs` log in over `POST /api/code` instead of the face; `browser.mjs`accepts Chrome's "Failed to load resource … 400/429 … /api/code" console line (the refusals).New `tests/iplimit.mjs` (39 checks). `tools/dump-store.hl` also dumps `ipsends`.- Developed in `.scratch/dev-m010`; work files in `.scratch/m010/` (local deploy test`localdeploy.sh`, TLS proxy test `tlsproxy/`).How to verify:```bashcd /media/STORAGE/projects/ident.worldapi.orgnode tests/iplimit.mjs && node tests/browser.mjs && node tests/apps.mjs && node tests/selector.mjs && node tests/migration.mjs # 39/83/97/99/23, 0 failedps -eo pid,args | grep [h]l-browser-tier # must print nothingbash -n deploy.sh && ./deploy.sh --dry-run --target /tmp/identdeploy-test # gates + rsync -n preview into a LOCAL dir; never run without --target unless you deploy```Deploy test done on Loreana ONLY (never Byrodin): `.scratch/m010/localdeploy.sh ident <src> 45002`deploys into `.scratch/m010/fake-byrodin/ident.worldapi.org` (fake `storage/` + `.env` markerssurvive, md5-checked), runs the container there with docker (only 127.0.0.1:45002 listens, LANrefused), URL 200. `.scratch/m010/tlsproxy/run.sh`: nginx with TLS in front of the ident andtickets containers + Chrome (`https.mjs`, 16 checks: wss socket, no http:// URLs, login +cookie + reload over https, selector CORS for `https://tickets.worldapi.org`, forgedX-Client-IP overwritten by the proxy, watcher off). Containers/proxy removed afterwards.By hand (creator): http://100.77.141.84:8351 → sign in as before (the form now POSTs /api/code).For the architect (first deploy): the ident vhost needs `proxy_set_header X-Client-IP $client_ip;`(and should drop the X-Forwarded-For/CF-Connecting-IP lines, ident ignores them), the WebSocketUpgrade headers (`/__hl/socket`), and `proxy_pass http://127.0.0.1:45002` (today's`worldapi.org/ident.conf` points to 44800 and has no Upgrade headers). `.env` on Byrodin: SMTP_*(no IDENT_PORT — compose sets 45002; the real environment outranks `.env`).Lessons:- hl:webex ignores `HL_HOST`/`HOST` (it builds `new NativeWebSocketServer(port = port)`), hl:webv1 read it. Workaround: pass `http = new NativeWebSocketServer(port, host)` to the constructor.- A face has no request: anything that needs a header (client IP) must be a function route.- A non-JSON file in the session directory crashes the server at boot (sessions.hl JSON.parse).- Chrome logs every 4xx fetch as a console error — gates that check the console must expect them.## 2026-09-24 — UUID keys + storage/mpackdb/ (ticket ident #10 / old #39, mission 005 (old 009)) — awaiting creatorDone (worker, NOT confirmed by the creator):- Every table (accounts, identities, otp, sends, apps, connections, requests, grants) has pk`@id` (mpackdb UUID, 12 chars) and lives in `storage/mpackdb/<table>.*`; `IDENT_STORAGE` is nowa DIRECTORY. Public ids of accounts/identities/apps = the UUIDs (faces take strings; old numbersfind nothing). Default identity / app list = oldest by stored `created` (`store.hl oldestFirst`).- Kept byte-identical: `apiKey`, `secretHash`, `appIdentity` (still a 32-hex FIELD, also for newconnections), `rid`, grant hashes. Selector pick ids changed once (derived from the identity id).- `tools/migrate-009.hl` (idempotent via `oldId`, sessions `"user":{"id":n}` → account UUID),`tools/dump-store.hl` (JSON of all tables, COPY only). Old tables `storage/ident-*` kept, unread.- Deployed 12:02: backup `.scratch/storage-backup-20260924-120234/` (storage, .sessions,testapp/storage); work dir `.scratch/m009/deploy-20260924-120234/` (before/after dumps, runs,code-before). Migrated from a COPY twice (2nd: 0 new); `cmp009.py` → RESULT SAME (2 accounts,2 identities, 4 sends, 1 app, 1 connection); 6 sessions migrated → creator still signed in.- Rehearsed twice on copies first (`.scratch/m009/rehearse.sh <n>`: migrate ×2, compare, servethe copy with the new code on :8380 + Chrome: sessions signed in, test app key+secret exchange →its stored id).- Developed in `.scratch/dev` (old one moved to `.scratch/dev-m006`); scripts in `.scratch/m009/`.How to verify:```bashcd /media/STORAGE/projects/ident.worldapi.orgnode tests/browser.mjs && node tests/apps.mjs && node tests/selector.mjs && node tests/migration.mjs # 83/97/99/23, 0 failedps -eo pid,args | grep [h]l-browser-tier # must print nothingrm -rf /tmp/identcopy && cp -a storage/mpackdb /tmp/identcopy && IDENT_STORAGE=/tmp/identcopy ./bin/hybriel tools/dump-store.hl | python3 -m json.tool | less # COPY only (#40)python3 .scratch/m009/cmp009.py .scratch/m009/deploy-20260924-120234/{before,after}.json # RESULT SAME```By hand (creator): http://100.77.141.84:8351 still signed in (no new login), `/apps` shows the sameAPI key; http://100.77.141.84:8354 → "Log in with ident" / "choose ident" → the same identity id,"welcome back" (no re-registering).Lessons:- `from` is a reserved word in Hybriel (a parameter/`let` named `from` fails to parse).- `new MPackDB(..., compact = false)` still rewrites the index/meta files (only `.mpack` stays) —#40 holds; migrate/dump COPIES only.- `put()` on an `@id` table keeps an `id` the record already carries — copy rows WITHOUT `id`.- Deploy scripts: `(cd x && cmd & echo $! > x.pid)` backgrounds the whole `&&` list — `$!` is asubshell and the pid file lands in the wrong dir; use `cd x; cmd & echo $! > x.pid; cd ..`.## 2026-09-24 — piece 3/6: identity selector (ticket #26, mission 004 (old 007)) — awaiting creatorDone (worker, NOT confirmed by the creator):- `/selector.js` (file `selector.js`): `<ident-selector key="pk_…">`, shadow DOM, ident'scolours; says "choose ident"; opening fetches the identities (credentials); choosing one→ one-time code → event `ident-login` (`detail.code`). `logged-in` attribute / `loggedIn`property / `reset()` set by the host; no cookie of its own. Restyle: `--ident-*` customproperties + `::part(button|panel|identity|status|message|link)` (README).- `selector.hl`: `GET /api/selector/identities?key=`, `POST /api/selector/choose?key=`(+ OPTIONS preflight). Answers only if `Origin` ∈ the app's origins AND key = that app's(exact ACAO + credentials, `Vary: Origin`); else 403 without CORS headers. List ids =sha256(app secretHash:identity)[0..32] (opaque, per app). Code via `apps.hl issueCode`(shared with the login button: 48 hex, 60 s, single use).- `testapp/`: selector next to the login button; `/selector-login` (exchange server side),`/logout`; own session cookie `testapp<port>sid` → `/` renders `logged-in`.- Ticket #37: `tests/browser.mjs` two obsolete checks replaced (old shapes → 400) → 81/0.- Gates in the live folder: `selector.mjs` 97/0 (8 s), `apps.mjs` 94/0, `browser.mjs` 81/0.- Deployed: backup `.scratch/storage-backup-20260924-112012/` (storage, .sessions,testapp/storage); restarted :8351 and :8354; store checked on a copy(`.scratch/m007/check-store.hl`): 2 accounts, 2 identities, app `tickets.worldapi.org`,0 connections — as before.NOTE: check-store.hl was also run once directly on that backup by mistake: its accounts/identities/apps/connections files were rewritten by the open (content = live, diff clean).- Developed in `.scratch/m007/dev` (copy without `.env`/storage).Running: ident :8351 (`server.pid`, `server.log`); test app :8354 (`testapp/testapp.pid`,`testapp/testapp.log`). The test app is NOT set up (its `testapp/storage/` was empty):register an app in ident with origin `http://100.77.141.84:8354`, paste key + secret.How to verify:```bashcd /media/STORAGE/projects/ident.worldapi.orgnode tests/selector.mjs && node tests/apps.mjs && node tests/browser.mjs # 97 / 94 / 81, 0 failedps -eo pid,args | grep [h]l-browser-tier # must print nothingcurl -s -i 'http://127.0.0.1:8351/api/selector/identities?key=<pk>' -H 'Origin: <registered origin>' # 200 + ACAO```By hand (creator): sign in at http://100.77.141.84:8351, set up the test app, openhttp://100.77.141.84:8354 → "choose ident" → pick → "Logged in" without reload → Log out.Screenshots: `.scratch/selector-{closed,open,loggedin,restyled-open,signedout,unregistered}-{390,1280}.png`.Open:- Signed out of ident: the selector links to ident's `/` in a new tab; after signing inthe user opens the selector again (it re-asks on every open). No auto-refresh.- Same-site only (identsid is `SameSite=Lax`): a foreign-domain app gets`signedIn:false` (out of scope by the brief).- A registered page can call the two endpoints itself (no user click needed) — the host istrusted for its own app; it still only gets its own per-app id via its secret.- Opening an hl:mpackdb table rewrites its files (mtime, meta) — only check COPIES.Lessons:- hl:webex passes OPTIONS to function routes; request header names arrive lowercased.- A file route (`{ pattern file headers }`) serves JS with the right type and extra headers.- An hl string has no escapes: inline JS in an hl string uses double quotes only.- Shadow-DOM elements: tests/cdp.mjs `click()` can't reach them — `tests/selector.mjsshClick` computes the box inside the shadow root and dispatches real mouse events.## 2026-09-24 — piece 2/6: apps and login button (ticket #25, mission 003 (old 006)) — awaiting creatorDone (worker, NOT confirmed by the creator):- `/apps`: register (name + origins), list, edit, new secret, delete. API key `pk_…` public,secret `sk_…` shown ONCE (sha256 stored). Faces `appCreate/appUpdate/appNewSecret/appDelete`.- One id per app: table `connections` (`pair` = app:identity, random 32-hex `appIdentity`,`created`). Same identity + app → same id; other app / other identity → other id.- Login button: `GET /login?key=&return=` → error page (400, no redirect) or `/signin/<rid>`→ ident login if needed → "Choose an identity" (face `chooseIdentity`) →`<return>?ident_code=` (60 s, single use). `POST /api/exchange {key,secret,code}` →`{identity}` only; 401 bad key/secret, 400 bad code/other app's code/JSON/fields.- `testapp/` (separate hybriel app) on :8354; `tests/apps.mjs` gate → **94 passed, 0 failed**.- Deployed to :8351 (storage backup `.scratch/storage-backup-20260924-092054/`); storechecked read-only after restart: the creator's account + its identity still there.- Developed in `.scratch/dev` (copy without `.env`/storage), then copied over.Running: ident :8351 (`server.pid`, `server.log`); test app :8354 (`testapp/testapp.pid`,`testapp/testapp.log`, http://100.77.141.84:8354 — not set up: the creator registers it inident with origin `http://100.77.141.84:8354` and pastes key + secret into its form).How to verify:```bashcd /media/STORAGE/projects/ident.worldapi.orgnode tests/apps.mjs # 94 passed (own servers, sink mail, no :8351)node tests/browser.mjs # 79 passed + 2 OBSOLETE fails (below)ps -eo pid,args | grep [h]l-browser-tier # must print nothingcurl -s -o /dev/null -w '%{http_code}\n' 'http://127.0.0.1:8351/login?key=pk_x&return=http://a.b/' # 400```Screenshots: `.scratch/apps-*-{390,1280}.png`.Open:- ~~`tests/browser.mjs` two obsolete checks~~ — fixed in mission 004 (old 007) (ticket #37).- Deleting an identity leaves its connections (ids) in place; the exchange of an alreadyissued code still works for 60 s. Deleting an app removes its connections.- Expired `requests`/`grants` rows are removed only when looked up (no sweep).## 2026-09-24 — piece 1/6: account and identities (ticket #24, mission 002 (old 005)) — awaiting creatorDone (worker, NOT confirmed by the creator):- Email OTP login to ident (mission-003 rules kept). No registration: first right code createsthe account + default identity (name `Default`), then the optional-names form (Save / Skip).- Identities: list / new / edit / delete, all fields optional, last one cannot be deleted,default = oldest remaining. Time zone from the browser at first login, shown, changeable.- Removed the mission-003 app flow (`/login`, `/continue`, `/api/exchange`, allow list,`testclient/` — its PID 161897 on :8354 stopped). Old code: `.scratch/removed-mission003/`.- Old store (mission-003 test data) moved to `.scratch/storage-backup-20260924-060627/`(storage + .sessions); started empty.- Gate `node tests/browser.mjs` → **81 passed, 0 failed**; `node tests/dev-smoke.mjs` → SMOKE OK.Running: ident :8351 (PID `server.pid`, log `server.log`). Dev store: [email protected](from dev-smoke; time zone Europe/Vienna, one identity `Default`).## How to verify```bashcd /media/STORAGE/projects/ident.worldapi.orgnode tests/browser.mjs && node tests/dev-smoke.mjsps -eo pid,args | grep [h]l-browser-tier # must print nothingcurl -s http://127.0.0.1:8351/api/exchange # {"error":"no such endpoint"}.scratch/repro-session-forge/run.sh # the hybriel face-session repro (port 8359)```By hand: http://100.77.141.84:8351 → email → `tail -1 storage/mail-sink.txt` → code →welcome form (Skip or fill) → identities, time zone.## Open / next- Pieces 2–6 (#25–#29): apps + API keys + app secret, per-app ids, login button flow,selector, notifications. Tickets wiring waits for piece 2.- Questions to the creator (mission 002 (old 005) report): default identity's name `Default`; blankidentity names shown as nickname / names / `Identity <n>`; default = oldest (no"make default"); time zone as a free IANA text field.- A mail bounce is only logged (`mail: FAILED`). No account deletion, no email change.- Deploy (Byrodin container, nginx, DNS, real SMTP via mail.byrod.in) out of scope.## Lessons- **A server face is found by its EVENT NAME across every file** (hl:webex `facesOf`): twocomponents with `on server save()` would BOTH run. apps.hl's faces are named `app…`.- hl:webex needs at least one COMPONENT route (it reads the app dir off it): the test apphas `/about` only for that.- Chrome may pre-fill a password field on a later visit: the gate types the test app's secretwith `clear: true` and checks the saved key after the submit.- Develop in `.scratch/dev` (copy without `.env` and storage) — the :8351 watcher reloads onevery save in the app dir.- **A face's `session` is POSITIONAL** (the framework appends it to the client's payload):a hand-made frame with one argument too many puts the CLIENT's object in the session'splace. Every face goes through `store.hl accountOfSession`/`realSession`, which only accepts`hlTypeName(session) == 'Instance'` (JSON is always `Hybrid`). Repro:`.scratch/repro-session-forge/` (reported to hybriel).- A face runs on a BLANK instance: component functions/members are null there (NotCallable);use only arguments and imports.- `if (!member)` in a View renders nothing, silently (repro `.scratch/repro-if-not/check.mjs`):keep a separate member (`notWelcome`).- `Intl` is not on the JS compiler's browser-global list; `window.Intl` works.- Chrome canonicalises some zones (`America/Argentina/Buenos_Aires` → `America/Buenos_Aires`);store what the browser reports.- A NON-LITERAL static in the manifest reads null in the manifest's own function routes oncea component imports from project.hl (hybriel #16) — keep such values elsewhere.- Function routes get no session (#18): `server.sessions.resolve(cookieHeader)`.- `server.sessions.cookie = 'identsid'` after construction renames the cookie (#17).- `styles = {}` boots but `/__hl/app.css` answers 500 (#19): every app needs a styles.hl.- hl:smtp refuses a Mailer without a sender at construction (#20) → placeholders in mail.hl.- `JSON.parse` on bad input aborts the request (#12): a future JSON API must check the bodyfirst (copy tickets' `jsoncheck.hl`).## 2026-09-24 — real mail on (architect, creator asked)- `.env` = copy of hybriel `projects/demo-social-network/.env` (SMTP via mail.byrod.in, user[email protected], sender [email protected]) + `IDENT_PORT=8351`. The file sink is OFFfor the dev server. Backup of the old `.env` in `.scratch/env-backup-*`.- Verified: creator logged in with a real code; mail server log shows delivery to [email protected](`docker logs mail | grep social.network` on Byrodin).- **Tests must never send real mail**: the gate starts its own servers with `IDENT_MAIL_SINK`;do NOT run `tests/dev-smoke.mjs` against :8351 any more (it would mail [email protected]).- Creator: "looks ok for a first" — worker's default choices (Default name, label fallback, oldest =default, free IANA time zone field, UTC fallback, 60 chars) accepted as built.## 2026-09-24 — dev hostname trap (creator testing #26)- Selector said "not signed in" because ident was logged in under one host and the test app ran under the other.The test app loads the selector from IDENT_URL=http://100.77.141.84:8351. Use ONE hostname for both:http://100.77.141.84:8351 (ident) + http://100.77.141.84:8354 (test app). LAN IP 192.168.178.75 = different site.## 2026-09-24 — DEPLOYED (architect)- LIVE is now https://ident.worldapi.org on Byrodin (/CONTAINERS/projects/ident.worldapi.org). The live data was moved there.- This Loreana folder is DEVELOPMENT only; its storage/ is a stale copy. Dev servers were stopped. Update live with ./deploy.sh.## 2026-09-24 — session hardening (ticket #2, items 3+4): copied into the app- Rescued antcolony mission 022's work-copy build from `.scratch/dev-m022/` (a prior worker ran out ofsteps before reporting) and copied its 4 changed files into the real project root:`store.hl`, `apps.hl`, `components/home.hl`, `components/appsettings.hl`. Not deployed(out of scope for this worker; ./deploy.sh is the architect's).- **Item 3 — the pending session's own expiry**: `store.hl` `beginSession`/`accountOfSession`stamp `session.user.since` + `session.user.epoch` at sign-in; independent of the webexsession file's own idle/maxAge clock. `IDENT_SESSION_TTL_MS` (default 14 days). An OLDERsession (no `since`/`epoch` — before this ticket, or from the mission-009 migration) isupgraded on its next use instead of being force-signed-out.- **Item 4 — sign out for apps / sign out everywhere**: `store.hl` `signOutEverywhere` bumpsthe account's `sessionEpoch`, which invalidates every session carrying the old epoch on itsnext check (no per-session-file bookkeeping needed) — wired to a "Sign out everywhere"button on `/` (`components/home.hl`). `apps.hl` `disconnectConnection` forgets one appconnection — wired to a "Sign out of this app" button on the per-app page(`components/appsettings.hl`); a later login makes a fresh, unrelated per-app id.- **Bug fixed in the mission-022 test** (`tests/hardening.mjs`, now copied to the real`tests/`): its `signIn()` helper read the session cookie from `/api/code`'s response, butthat route sets no cookie at all (it's a plain function route, no session) — the realcookie is minted by the following `/__hl/emit verifyCode` call. Also added a short pollfor the mail-sink code (the sink write can land just after the HTTP response). Not an appbug — the 3 failing checks were the test's own cookie handling.- **Lesson: kill your test servers even on a thrown error.** A crashed `tests/hardening.mjs`run (before this fix) left two orphaned `bin/hybriel` processes bound to :8700/:8701; alater debug run then hit those STALE servers (old per-address send-limits, wrong sessionstate) instead of a fresh one, which looked exactly like a cookie bug at first. Alwayscheck `ps -eo pid,args | grep hybriel` for leftovers from a previous crashed run beforetrusting a new test failure.- Verified: `node tests/hardening.mjs` (copied into `tests/`) → **15 passed, 0 failed**.Full regression re-run against the real project files after the copy — no regressions:`tests/browser.mjs` 83/83, `tests/apps.mjs` 97/97, `tests/notify.mjs` 130/130,`tests/selector.mjs` 99/99, `tests/migration.mjs` 23/23.- Still open from ticket #2: item 2 (real mail — architect already reported done, notre-verified this session, out of scope) and the session cookie's missing `Secure` flag(hybriel #27, blocked on a hybriel fix, not app-side). `.scratch/dev-m022` and `.scratch/m022`(prior worker's scratch) left in place, no longer needed for this ticket.## 2026-09-24 — identity avatars (ticket #15), continued and finishedA prior worker (session `s-20260924T1745-d1e933`) had already added the avatar field to`store.hl`/`components/home.hl`/`styles.hl` but ran out of steps before testing any of it. Thissession verified, fixed and tested it.- **Found a real regression by running the gate, not by reading the code**: `nodetests/browser.mjs` failed 4 checks and then aborted (a later step depended on the failedsave) — editing an identity silently saved nothing.Traced with throwaway probes (`.scratch/probe15/`, removed after): server-side `checkFields`and `updateIdentity` were correct in isolation (confirmed with a standalone face call overHTTP), but in the real browser, typing a SECOND character into any field of the edit form lostfocus — the DOM node was being replaced under the user's cursor.- **Root cause (hybriel #32)**: the new avatar preview was `if (hasAvatarPreview) { img {...} }`placed right after the avatar input, in the SAME `View` as the other four text inputs. Removingjust that `if` (nothing else) fixed typing immediately — confirmed with a minimal repro(type one char, check focus survives) before and after. hl:webex recreates a View's elements onre-render when it contains a conditional block, even one that never flips during the interaction.- **Fix**: `img` is now always rendered; visibility toggles via a `hidden` CSS class driven by aplain reactive field (`avatarPreviewClass = hasAvatarPreview ? 'avatar' : 'avatar hidden'`) —no `if` in the view. `#avatarpreview.hidden { display: none }` added to `styles.hl`. An `img`with an empty `src` issues no network request per the HTML spec (confirmed: no console errors).- Avatar is stored as an http(s) URL (≤ 2000 chars), same shape as a notification's icon(`notify.hl checkUrl`) — no upload infrastructure exists anywhere in this app, decided as thesmallest fit for the concept's silence on how the picture gets there.- Extended `tests/browser.mjs` (83 → 92 checks) with: live preview on typing a valid URL, the saved avatar showing as a roundimage in the identity list, reopening the edit form re-showing the saved URL, a two-keystrokefocus-survival check (guards the regression above from coming back unnoticed), clearing thefield hiding the preview, and four `checkFields` negatives (no scheme, non-http(s) scheme,too long, wrong type). Used a `paste()` helper (one `input` event with the whole URL) insteadof the existing char-by-char `type()` for the avatar field — typing a URL character by charactermakes the live preview fetch every half-typed prefix (`h`, `ht`, `htt`, …), each a real 404;a paste (or most real user input) sends the whole value in one event.- Verified: `node tests/browser.mjs` → **92 passed, 0 failed**. Full regression, unchanged:`tests/apps.mjs` 97/97, `tests/selector.mjs` 99/99, `tests/notify.mjs` 130/130,`tests/migration.mjs` 23/23, `tests/hardening.mjs` 15/15.- Not done: apps still don't receive the avatar (blocked on ticket #11, on hold, unchanged fromthe prior worker's decision — correct, left alone). `.scratch/probe15/` (this session's reproscripts) removed after use — nothing left running, no orphan `bin/hybriel` processes.
Branches
- mainmain branch
Latest commits
- ff78726cident: Hybriel master 190aa11d (fc838894 GC correctness, #127, #126 closure scopes); gates all greenmre
- a3a7d21aident: Hybriel master 8efba065 (#126 GC by bytes, #48 lambda params copy); session-writing lambdas take &sessionmre
- 98226b41antcolony#40: mission references point to the moved missionsmre
- ff805b9aantcolony#40: history (LOG.md), worker briefs (missions/) and reports moved here from antcolony, numbered per project; old numbers in antcolony docs/mission-map.mdmre
- 51a7bcdfident: Hybriel master 73267707 (#122); /code uses the new page() signature; pending address passed as parameter; once-checksmre
- 836f644fident#24: installable app (manifest, service worker, data-free offline /start), own iconmre
- 8bebbbf2deploy.sh: back up live storage/.sessions/.env before every deploy (newest 5 kept)mre
- cc063ea2deploy.sh: never send .git or .gitignore to Byrodinmre
- 81b15b7bState of 2026-09-27, before the move to gitoriamre