Skip to content

harness/ui-chat: drive a two-node chat through the real UI (blocked on CIRISEdge#552) - #523

Open
emooreatx wants to merge 2 commits into
mainfrom
feat/ui-chat-harness
Open

harness/ui-chat: drive a two-node chat through the real UI (blocked on CIRISEdge#552)#523
emooreatx wants to merge 2 commits into
mainfrom
feat/ui-chat-harness

Conversation

@emooreatx

Copy link
Copy Markdown
Contributor

Three containers — a canonical and two nodes — each node running a real ciris-server and the real Compose Desktop client, driven through the app's TestAutomationServer (java.awt.Robot at screen coordinates). No stage is performed over the node's HTTP API; HTTP is read-only, for evidence, and never makes a step succeed.

mesh-repro/scenarios/chat.sh proves the same delivery over HTTP. This proves it through the surface a person uses: contact added in the UI, consent given in the UI, message typed and sent in the UI, assertion reads node B's on-screen transcript.

Merging this is not urgent — it is blocked mid-flow and says so in its README. Landing it now keeps the six defects it found from being rediscovered, and gives #522 an end-to-end check to run the moment edge ships.

A container per node

The client resolves federation-crypto calls to CIRISApiClient.LOCAL_NODE_URL, hardcoded http://127.0.0.1:4243 (CIRISClient#26). Three nodes on one host would send those to whichever node answers 4243 — a different node — and mint the owner's identity in the wrong place. Separate netns makes each node genuinely 127.0.0.1:4243, so the constant is correct rather than tolerated: no client patch, no node moved off default ports, and closer to a real install than a port-juggled host.

Status — blocked at discover:a_sees_b_fedid

stage
first_run ×2 (wizard, auto-claim from <home>/claim_pin)
restart:for_announce_optin
login ×2
discover:a_sees_b_fedid

Both nodes discover each other's node keys and the canonical; neither obtains the other owner's fed-id. The canonical's own directory:

A1  B1  C1  test-accord-holder-0  node-a-jxhyjutjzr  node-b-ur6c7e42rv

Both node keys, neither owner — so add_contact refuses contacts.unknown_fed_id and bound_nodes_of has nothing to resolve.

That is CIRISEdge#552's cell. Key already serves public on the advertise axis, but the RECEIVE axis was subject-only"un-addressable, not undisclosed": Pull is the only by-name read and a node cannot compute the hash of a record it has never held. CIRISEdge#556 widens it to data_subject+any_attributed. Waiting on an edge release carrying that (CIRISServer#522).

Six defects found getting here

  1. default-jre-headless has no X11/AWT — dies HeadlessException: No X11 DISPLAY variable was set, which reads as a missing display and is a missing toolkit.
  2. The automation server binds loopback only — answers docker exec curl perfectly, invisible to the host. Needs the socat forwarder. Any containerised UI platform hits this.
  3. All five trust-root vars are required — the classical two boot a node that roots, arms its gates, then dies PQC signature without pubkey.
  4. A tmpfs home makes the restart wipe the identity the restart exists to apply.
  5. A stale /tmp/.X99-lock survives a restart — second boot only, so it reads like a flake.
  6. A click is a toggle, never an assertion — the driver clicked toggle_announce_ownership to "opt in" and turned it off; SetupState.announceOwnership already defaults true. The run then failed four minutes later in an unrelated stage.

Two more in the driver's own assertions

Both the same shape — a check that cannot fail:

  • contact:a_adds_b passed on the absence of a refusal banner while listContacts reported 0 contact(s) of 0.
  • A navigation stage left the UI on ManageNodes, so the next stage reported "the contacts button never appeared" — true, and pointing at the wrong thing.

Both now assert the positive, and goto() confirms the landing screen.

Design notes worth carrying elsewhere

  • Readiness is /tree returning elements, never the port answering. On Android the automation server came up 200 ms before the process died (CIRISClient#25); a port check calls that green.
  • The arrival assertion keys on the sender's exact typed text, which B cannot produce for itself — the reasoning chat.sh uses when it keys on the sender's attestation_id.
  • The contact is a fed-id, never a node key. An earlier revision fell back to the node key when the fed-id was missing, which made the stage pass while testing node-to-node delivery wearing a person-to-person label.
  • The canonical runs no UI, so a transcript can only come from a node someone actually drove.

Not yet aligned with #520

#520 rules promotion is opt-out, that opting out stops the agent, and that it is recorded as a consent_state: granted row with specific copy. The driver currently walks the wizard's defaults; when #520 lands, first_run should assert that shape rather than accept whatever the wizard does.

The 34 MB wheel and 70 MB uber-jar are gitignored; run.sh stages them and tells you how to build either.

🤖 Generated with Claude Code

https://claude.ai/code/session_017gxnxsTWS8FkN3CT2JEz86

Three containers — a canonical and two nodes — each node running a real
ciris-server AND the real Compose Desktop client, driven through the app's
TestAutomationServer (java.awt.Robot at screen coordinates). No stage is
performed over the node's HTTP API; HTTP is read-only, for evidence, and never
makes a step succeed.

mesh-repro's `chat` scenario proves the same delivery over HTTP. This proves it
through the surface a person uses: the contact is added in the UI, consent given
in the UI, the message typed and sent in the UI, and the assertion reads node B's
on-screen transcript.

A CONTAINER PER NODE, because the client resolves federation-crypto calls to a
hardcoded `LOCAL_NODE_URL` of 127.0.0.1:4243 (CIRISClient#26). Three nodes on one
host would send those calls to whichever node answers 4243 and mint the owner's
identity on the wrong one. Separate netns makes each node genuinely 127.0.0.1:4243,
so the constant is correct rather than tolerated — no client patch, no node moved
off the default ports, and closer to a real install than a port-juggled host.

PASSING: first_run x2 (wizard, auto-claiming from <home>/claim_pin),
restart-for-opt-in, login x2.

BLOCKED at discover:a_sees_b_fedid, on CIRISEdge#552. Both nodes discover each
other's NODE keys and the canonical; neither obtains the other owner's FED-ID, so
add_contact refuses contacts.unknown_fed_id. The canonical's own directory holds
both node keys and neither owner. `Key` already serves public on the advertise
axis, but the RECEIVE axis was subject-only — "un-addressable, not undisclosed",
since Pull is the only by-name read and a node cannot compute the hash of a
record it has never held. CIRISEdge#556 widens it; this waits on a release
carrying that (CIRISServer#522).

SIX DEFECTS FOUND GETTING HERE, each now prevented rather than remembered:
default-jre-headless has no X11/AWT (dies HeadlessException, which reads as a
missing display and is a missing toolkit); the automation server binds loopback
only, so it answers `docker exec curl` and not the host; all five trust-root vars
are required, and the classical two boot a node that roots then dies "PQC
signature without pubkey"; a tmpfs home makes the restart wipe the identity the
restart exists to apply; a stale /tmp/.X99-lock survives a restart and fails the
second boot only; and clicking `toggle_announce_ownership` to "opt in" turned it
OFF, because SetupState.announceOwnership already defaults true — a click is a
toggle, never an assertion.

TWO MORE IN THE DRIVER'S OWN ASSERTIONS, both checks that could not fail:
`contact:a_adds_b` passed on the absence of a refusal banner while listContacts
said "0 contact(s) of 0"; and a navigation stage left the UI on ManageNodes so
the next stage reported "the contacts button never appeared" — true, and pointing
at the wrong thing. Both now assert the positive.

The contact is a FED-ID, never a node key: bound_nodes_of resolves the person
through nodes_stewarded_by and filters to identity_type == node. An earlier
revision fell back to the node key when the fed-id was missing, which made the
stage pass while testing node-to-node delivery wearing a person-to-person label.

The wheel and uber-jar are gitignored — 104 MB of build inputs run.sh stages and
tells you how to build.

Refs CIRISEdge#552/#556, CIRISServer#522, CIRISClient#25/#26, CIRISServer#520.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017gxnxsTWS8FkN3CT2JEz86
@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.
To continue using code reviews, you can upgrade your account or add credits to your account and enable them for code reviews in your settings.

…okup

The driver had a `discover:a_sees_b_fedid` stage that waited for node A to learn
node B's owner fed-id by discovery, and reported it as blocked on CIRISEdge#552.
That was a misreading of the boundary. CIRISServer#524 §6.3:

    "Stranger contact is meant to start from a nodecode, not a directory lookup.
     You hand out an identifier out-of-band, the peer dials that specific node
     (which serves its own record), and consent follows. Building 'search the
     federation for a person' on top of `discover` will work only for people you
     already have a consented relationship with — that is the boundary, not a
     gap to route around."

So the stage was not waiting for a fix. It asked the substrate for an
address-book lookup it refuses by design, because answering a third-party probe
"would make a body-holding server an address-book oracle for records it never
advertised" (§6.1). Left as it was, the harness would have re-run against
v18.12.1, failed identically, and blamed the release.

The flow now matches the product: each side reads its own node code, the harness
carries it across — that carrying IS the out-of-band channel, exactly as a person
handing over a code is — `add_peer_by_code` admits the peer through the UI's
NetworkPeers sheet, and only then is the owner behind that node resolvable.

The receive-axis widening still matters: it is what makes the by-name Pull legal
once a code names the peer. It was never going to be sufficient alone, which is
the part I had wrong.

Stages after the hand-off need a wheel built against edge v18.12.1 to run end to
end; the pins move in CIRISServer#525.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017gxnxsTWS8FkN3CT2JEz86
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant