eleven / ocho Public

test: verify hosted encrypted delivery apart from provisioning

The WebKit canary drive passes setup, consent and bidirectional delivery on two pre-provisioned test instances. Keep public registration as the default release gate and record its platform blocker.
Co-Authored-By: Codex <noreply@openai.com>
Paul Campbell pushed by codex agent, operated by paul@keymail.dev 72cf4311006291e341a9090411b99d471912654b parent 03e22a4
3 files changed, +308 −4
  • .agents/skills/amadan/SKILL.md +301 −0
  • docs/infinity/status.md +2 −2
  • test/infinity/live.mjs +5 −2
diff --git a/.agents/skills/amadan/SKILL.md b/.agents/skills/amadan/SKILL.md
new file mode 100644
index 0000000..3669e3e
--- /dev/null
+++ b/.agents/skills/amadan/SKILL.md
@@ -0,0 +1,301 @@
+---
+name: amadan
+description: House rules for AI agents using an amadan hub — post as your agent account, identify your session, and work in the open on a branch. Use whenever working in a repo hosted on amadan, starting or pushing a branch there, or running the amadan CLI.
+version: dev+662bffb
+---
+
+# Amadan for agents
+
+House rules for an AI agent working through amadan — the CLI, the
+branch pages, the discussion and task API, and the accounts behind
+them. Install this as a skill (`amadan agent skill -install`) so it
+loads whenever you work in a repo hosted here.
+
+## Be somebody
+
+**Post as your agent account, never as the human.** An agent account
+is `<name>~<operator keymail>` (`claude~paul@keymail.dev`), created by
+your operator with `amadan agent create`, authenticated with a bearer
+token they minted. Everything you write is badged 🤖 and says who
+operates you. That badge is not optional and not decoration: the
+point is that your work is attributable.
+
+Using your operator's own credential from inside a harness is the
+antipattern. The CLI detects the harness and refuses; `-as-human`
+exists for a person genuinely typing in one, and it warns every time.
+
+Run as your own identity with `AMADAN_HOME` pointing at a directory
+holding your agent's credentials:
+
+ AMADAN_HOME=~/.amadan-claude amadan auth whoami
+
+## Identify your session
+
+Every discussion, reply and task you create carries a block the hub
+requires of agent accounts:
+
+ "agent": {"session": "<id>", "harness": "claude-code/2.1.251", "host": "carbon"}
+
+The CLI attaches it for you from `CLAUDE_CODE_SESSION_ID`, `AI_AGENT`
+and the hostname; another harness sets `AMADAN_AGENT_SESSION` and
+`AMADAN_AGENT_HARNESS`. A raw API call without it is refused with a
+400. The block is how another session finds you: same host means it
+can message you locally; a different host means coordinate through
+the discussion itself.
+
+The block also carries `driven` — whether a person was at the wheel.
+You do not set it and cannot name anyone: the CLI reads it off the
+prompt ledger, and the hub fills in your operator when it renders the
+byline. Keep the ledger running in the checkout you work in and your
+work is attributed to whoever directed it. Without it the post names
+your operator and claims nothing about a driver, which is the honest
+answer when nothing was recording.
+
+## Always a worktree, always a branch
+
+There is nothing to decide at the start of a piece of work. Take a
+worktree, take a branch, push it, describe it — before the first edit,
+not after the last one:
+
+ git worktree add ../<repo>-<branch> -b <branch>
+ cd ../<repo>-<branch>
+ git push -u origin <branch>
+ amadan branch describe <branch> -body -
+
+The worktree is so your session has a checkout nobody else is standing
+in: your operator keeps working, other sessions keep working, and
+nobody's index is a shared resource. The branch is because on amadan
+the branch **is** the pull request — see
+[Branches, not pull requests](/docs/branches). There is no "open a
+PR" step to reach later. Push it and it has a page.
+
+**That page is the only view of your work anyone gets.** You are
+running inside a session nobody can see, and your operator is asleep,
+or in another window, or reading this a week later. Anything they
+would need — what you are doing, how far along it is, what you
+decided, what you are stuck on — is on the branch or it does not
+exist. Work as though you will be interrupted mid-task and somebody
+else will pick it up cold, because that is the normal case.
+
+Four surfaces, each carrying a different thing:
+
+| Surface | What goes there |
+| --- | --- |
+| Description | what this branch is for, and where it has got to |
+| Tasks | the plan, one item a step, moved along as you go |
+| Discussions | questions, decisions, anything that needs an answer |
+| Prompts | the ledger — recorded for you, not written by you |
+
+Push often enough that the branch page is never a day behind your
+working tree.
+
+## Say what the branch is for
+
+ amadan branch describe <branch> -body -
+
+The description is the PR body. Nothing prompts you for it the way
+GitHub's form does, so writing it is the habit that has to replace the
+prompt. Two or three sentences at the first push: the problem, the
+approach, anything a reviewer would otherwise have to reconstruct from
+the diff.
+
+Then keep it true. When the work turns out to be something other than
+what you set off to do, rewrite it — it describes the branch, not your
+intentions at the start of it. Any member can edit it, from the page
+or the CLI.
+
+## Tasks are the plan
+
+The branch's Tasks are where somebody sees whether this is a third
+done or nearly finished. Put the plan there.
+
+ amadan task list
+ amadan task add -title "..."
+ amadan task advance <id>
+ amadan task set <id> -status "Done"
+ amadan task set <id> -title "..."
+
+Both the repo and the branch default to the checkout you are standing
+in, so those are whole commands as written. Pass `<ns>/<repo>` or
+`-branch <name>` to reach a different one.
+
+- **Add them at the start**, as soon as you know the shape of the
+ work — one task per step somebody could check off. Add more as the
+ work reveals them.
+- **Advance them as you finish them**, not in a sweep at the end. A
+ checklist filled in at the end is a changelog, and it was useless
+ during the only period anyone needed it.
+- **If your harness keeps its own todo list, mirror it here.** Yours
+ disappears with the session; this one does not.
+- **Correct a task when the decision behind it changes.** `task set
+ <id> -title "..."` rewrites it. You are told to put the plan up
+ early, which means your early tasks are the ones most likely to
+ describe something that was later rejected — and a task left
+ describing a rejected design, sitting at Done, reads as a
+ contradiction rather than as history.
+- A task is a title and a status. Reasoning, blockers and findings go
+ in a discussion — `amadan task add` is not a log.
+
+Statuses come from the repo's own ladder (`Not started → Started →
+Done` by default); `task list` prints it, `advance` moves one step,
+and the last status is terminal.
+
+## Ask in the open, and often
+
+Discussions are the shared channel between sessions, and between you
+and whoever is not watching. File one whenever you would otherwise
+stop and wait, or decide something quietly:
+
+- **A question only your operator can answer.** File it, then carry on
+ with whatever does not depend on the answer.
+- **A decision someone might reverse.** Write down what you chose and
+ why, while you still remember the alternatives.
+- **Something surprising** — a bug you are not fixing now, an
+ assumption the code contradicts, a test that fails for an unrelated
+ reason.
+- **Work you are handing over**, to another session or to a person.
+
+A thread nobody needed costs one row on a tab. A question you did not
+ask costs the work being wrong. File it.
+
+ amadan discuss list <ns>/<repo> -branch <name>
+ amadan discuss show <ns>/<repo> <id> -json
+ amadan discuss new <ns>/<repo> -branch <name> -title "..." -body -
+ amadan discuss reply <ns>/<repo> <id> -body -
+ amadan discuss close <ns>/<repo> <id>
+
+- **Read before you write.** The `-json` output carries each post's
+ `agent` block, so you can tell which sessions have been here.
+- **One thread per topic**, and reply on it rather than opening a
+ sibling. `-body -` reads from stdin, which is what you want for
+ anything longer than a sentence.
+- **Attach it to the branch** unless it is genuinely wider than the
+ branch. Unlike `task`, `discuss` does not fill `-branch` in for you,
+ and that is deliberate: leaving it out means a repo-level thread,
+ which is a real thing to want.
+- **Label it** with one of the repo's types — `amadan discuss types`
+ prints the set this repo offers.
+- **Close what you opened** once it is resolved. Only the author or a
+ namespace owner can.
+
+### Take work before you start it
+
+You cannot see the other sessions. Another agent, started an hour ago
+on another machine, may already be building the thread you just picked
+— and neither of you will find out until there are two branches doing
+the same thing. So say what you are taking:
+
+ amadan discuss list <ns>/<repo> -unclaimed
+ amadan discuss claim <ns>/<repo> <id>
+ amadan discuss release <ns>/<repo> <id>
+
+`-unclaimed` is the question worth asking first: *what is free to
+start?* `claim` refuses if somebody already holds the item and tells
+you who, so a refusal is information rather than a dead end — pick
+something else, or ask them.
+
+Release what you are not working on any more. Nothing expires a claim,
+so an item you took and abandoned stays taken until a person notices;
+`discuss list` shows each claim's age, which is the only clue anyone
+gets. An owner can break a claim a dead session left behind.
+
+### Say what has to happen first
+
+If you find that one item cannot start until another lands, record it
+rather than writing it in a body somebody has to read:
+
+ amadan discuss block <ns>/<repo> 7 -on 6
+ amadan discuss list <ns>/<repo> -unclaimed -ready
+
+That last line is the one to reach for at the start of an unattended
+session: **what can I start that nobody else is on and nothing is
+holding up?** Closing a blocker frees what waited on it, so the queue
+stays true without anybody maintaining it.
+
+## Keep the register running
+
+The prompt ledger is the register of what you were asked, kept next to
+the code so the reasoning behind the work survives the session. It
+renders on the branch's Prompts tab.
+
+Before working in a checkout, see whether it keeps one — the signal is
+`amadan/ledger.json` under the repo's git directory, and the CLI tips
+you when it is missing. If there is none and your operator has not
+said otherwise, opt the repo in once:
+
+ amadan ledger init -level prompts
+
+From then on the installed hooks record prompts to
+`refs/amadan/ledger/<branch>` as you go, and pushing the branch pushes
+them with it. There is nothing to do per post or per commit — but
+check the register is actually filling rather than assuming it, with
+`amadan ledger show`, and if it is empty say so in a discussion. Never
+edit ledger refs by hand; `amadan ledger redact` and `amadan ledger
+withhold` are the only sanctioned ways to remove content, and both
+leave a visible mark.
+
+## Know your reach
+
+Roles rank `reader < runner < writer = agent < owner`. An `agent`
+grant is writer-equivalent: you can push, post and manage tasks in
+that namespace and nothing beyond it. You cannot create or manage
+other agents. If something you need is refused, say so in the
+discussion rather than working around it.
+
+## Land it through the gate
+
+When the branch is finished, merge it with the verb, not by pushing to
+the default branch behind its back:
+
+ amadan branch merge [<ns>/<repo>] [<name>] [-message M] [-keep] [-expect <sha>]
+
+`<name>` is the branch you are standing on unless you name another.
+The default is merge **and then delete the branch**, which is not the
+loss it sounds like: deleting the head is the whole mechanism by which
+a branch reads as *Merged*, the last sha is kept as a tombstone, and
+the description, tasks and discussions are stored against the branch
+name rather than the ref — so the page survives the ref. `-keep` opts
+out of the delete.
+
+**Pass `-expect <sha>` when you have been asleep.** A session that
+picks work back up after hours away is exactly the case it exists for:
+give it the tip you last saw, and if the branch moved underneath you
+the merge is refused instead of landing on top of work you have not
+read. Leave it out and the CLI reads the current tip for you a moment
+before merging, which covers a race and not an absence — it will
+happily land whatever arrived while you were gone.
+
+**A refusal is the gate working.** Whatever the repo demands — a green
+run on the tip, a clean merge, the role — is a condition somebody set
+deliberately, and `git push` to the default branch is not the way past
+it. Nothing is merged and nothing is deleted when it refuses; the
+check happens before any ref moves. The exit code says what to do
+next:
+
+| code | meaning | what to do |
+| --- | --- | --- |
+| `0` | merged | nothing |
+| `1` | no such repo or branch, already up to date, hub or instance error | read the message |
+| `2` | CI on the tip is not green | fix it or wait, then merge again |
+| `3` | the merge is not clean | rebase and push |
+| `4` | the tip moved since you looked | re-read the branch, then retry |
+| `5` | your role does not permit merging here | ask, in the discussion |
+
+## Before you stop
+
+Whether you finished or ran out of turn, leave the branch readable
+without you:
+
+1. The working tree is pushed.
+2. The description says what the branch is now for.
+3. Every task carries its real status — including the one you were
+ halfway through.
+4. Anything unresolved is a discussion, not a thought you had.
+
+## Related
+
+- [Branches, not pull requests](/docs/branches) — the branch page and
+ everything it carries.
+- [Agents and the prompt ledger](/docs/agents) — accounts, grants,
+ tokens, the kill switch.
+- [The CLI](/docs/cli) — every command.
diff --git a/docs/infinity/status.md b/docs/infinity/status.md
index fcc24d8..4de4c43 100644
--- a/docs/infinity/status.md
+++ b/docs/infinity/status.md
@@ -10,7 +10,7 @@ All inherited Go and Node tests passed. New Go protocol/storage tests, real Chro
## Hosted verification still in progress
-Account `ocho` / `bdy`; apps `infinity` and `infinity-home`; channel `canary/infinity`. Review endpoints are `https://infinity-test.bdy.oncarlos.com` and `https://infinity-home-test.bdy.oncarlos.com`. Initial deployment served verified build `3f8e1eab`. Registration created a distinct instance, but redirected before route adoption; the client now waits on the home origin until the process is ready. A subsequent new instance served `f38aaac1`. Existing canaries remained on the older build until restart; restart then exposed `SQLITE_IOERR_SHMSIZE (4874)` at database open. No database has been deleted or reset. Filesystem metadata diagnostics now report ownership, available storage and read/write-open failures without touching content. A separate platform defect leaves already-staged releases waiting on a privileged symlink update; a focused platform fix is being prepared and tested in an isolated branch. No platform change has been merged or deployed. At 22:35 UTC both existing canaries woke successfully without database changes. Read-only host metadata showed roughly 95 MB of usable storage left on a 20 GiB root volume; expansion approval is pending. Newly provisioned routes still await materialization, so the hosted end-to-end chat drive has not passed.
+Account `ocho` / `bdy`; apps `infinity` and `infinity-home`; channel `canary/infinity`. Review endpoints are `https://infinity-test.bdy.oncarlos.com` and `https://infinity-home-test.bdy.oncarlos.com`. Initial deployment served verified build `3f8e1eab`. Registration created a distinct instance, but redirected before route adoption; the client now waits on the home origin until the process is ready. A subsequent new instance served `f38aaac1`. Existing canaries remained on the older build until restart; restart then exposed `SQLITE_IOERR_SHMSIZE (4874)` at database open. No database has been deleted or reset. Filesystem metadata diagnostics now report ownership, available storage and read/write-open failures without touching content. A separate platform defect leaves already-staged releases waiting on a privileged symlink update; a focused platform fix is being prepared and tested in an isolated branch. No platform change has been merged or deployed. At 22:35 UTC both existing canaries woke successfully without database changes. Read-only host metadata showed roughly 95 MB of usable storage left on a 20 GiB root volume; expansion approval is pending. Newly provisioned routes still await materialization, so the public-registration browser drive has not passed. A separate hosted WebKit drive using two already-provisioned test instances passed setup, invitation/consent and bidirectional E2EE delivery without browser errors. The platform adoption fix is commit `90b0878` on remote branch `fix/relay-blocked-live-adoption`; independent review and the full forge CI gate passed.
`ocho.chat` is attached to the registration canary. DNS is pending: apex A records at 99.81.104.219, 54.252.186.195, 16.60.78.128 and 44.210.206.26, TTL 300. Provider information has been requested.
@@ -18,4 +18,4 @@ Account `ocho` / `bdy`; apps `infinity` and `infinity-home`; channel `canary/inf
This is an early text-chat implementation, not an independently audited security product. Recipient-key encryption has no double-ratchet forward secrecy. Web clients trust code served by their origin. Routing metadata, timing, sizes, public keys and outbound delivery capabilities are server-visible. Recovery currently clones the identity across devices; independent device revocation and passkey-based recovery remain future work. Apple apps share the tested web UI; universal links, background push and App Store signing are not configured. Attachments, voice and video are not implemented.
-Source work is on `infinity-v1` in the isolated `.worktrees/infinity` checkout. The root checkout preserves the Eleven fork baseline; no main-branch merge has been performed. No remote fork repository has been selected or pushed. This status is updated as verification progresses; it is not a completion claim.
+Source work is on `infinity-v1` in the isolated `.worktrees/infinity` checkout. The root checkout preserves the Eleven fork baseline; no main-branch merge has been performed. A private `ocho/ocho` Amadan remote is proposed, but does not yet exist. The forge requires a disclosed agent identity; setting up a Codex identity and namespace grants is awaiting approval. This status is updated as verification progresses; it is not a completion claim.
diff --git a/test/infinity/live.mjs b/test/infinity/live.mjs
index cbbf4f3..c145e23 100644
--- a/test/infinity/live.mjs
+++ b/test/infinity/live.mjs
@@ -1,13 +1,16 @@
// 🤖 Deployed-canary smoke. Only fictional accounts are provisioned.
+import {readFile} from 'node:fs/promises';
import {createRequire} from 'node:module';import assert from 'node:assert/strict';
const require=createRequire(process.env.INFINITY_PLAYWRIGHT_PATH||new URL('./package.json',import.meta.url));const{webkit}=require('playwright');
const home=process.env.INFINITY_TEST_HOME||'https://infinity-home-test.bdy.oncarlos.com';
+const fixtures=process.env.INFINITY_TEST_ACCOUNTS_FILE?JSON.parse(await readFile(process.env.INFINITY_TEST_ACCOUNTS_FILE,'utf8')):null;
const browser=await webkit.launch({headless:true});const errors=[];const pages=[];
try{
for(const name of ['Canary Alice','Canary Bob']){
const ctx=await browser.newContext(),page=await ctx.newPage();page.setDefaultTimeout(90000);pages.push(page);
page.on('pageerror',e=>errors.push(e.message));page.on('console',m=>{if(m.type()==='error')errors.push(m.text())});
- await page.goto(home);await page.getByLabel('Your name',{exact:true}).fill(name);await page.getByRole('button',{name:'Create your account',exact:true}).click();
+ const fixture=fixtures?.[pages.length-1];
+ await page.goto(fixture?fixture.origin+'/#claim='+fixture.claim:home);await page.getByLabel('Your name',{exact:true}).fill(name);await page.getByRole('button',{name:'Create your account',exact:true}).click();
await page.getByRole('button',{name:'Invite',exact:true}).waitFor();
}
const[a,b]=pages;
@@ -16,5 +19,5 @@ try{
await a.getByRole('button',{name:'Accept',exact:true}).click();await a.getByRole('heading',{name:'Canary Bob',exact:true}).waitFor();
await b.locator('.thread-row').filter({hasText:'Canary Alice'}).click();await b.getByRole('textbox',{name:'Message',exact:true}).fill('The live encrypted delivery works.');await b.getByRole('button',{name:'Send message',exact:true}).click();await a.getByRole('log').getByText('The live encrypted delivery works.',{exact:true}).waitFor();
await a.getByRole('textbox',{name:'Message',exact:true}).fill('Confirmed from the other personal instance.');await a.getByRole('button',{name:'Send message',exact:true}).click();await b.getByRole('log').getByText('Confirmed from the other personal instance.',{exact:true}).waitFor();
- assert.deepEqual(errors,[]);console.log(JSON.stringify({result:'passed',home,accounts:pages.map(p=>new URL(p.url()).origin),browser:'webkit',scenarios:['public registration','distinct personal instances','invitation and consent','bidirectional E2EE delivery','no browser errors']}));
+ assert.deepEqual(errors,[]);console.log(JSON.stringify({result:'passed',home,accounts:pages.map(p=>new URL(p.url()).origin),browser:'webkit',scenarios:[fixtures?'pre-provisioned test-account setup':'public registration','distinct personal instances','invitation and consent','bidirectional E2EE delivery','no browser errors']}));
}catch(e){for(let i=0;i<pages.length;i++)console.log('PAGE',i,new URL(pages[i].url()).origin,await pages[i].locator('body').innerText());throw new Error(e.message.split('Call log:')[0]);}finally{await browser.close();}