Skip to content

test(gate): a Docker-free two-node fixture on all five legs (port of CIRISServer's mesh-repro chat scenario) - #130

Merged
emooreatx merged 9 commits into
mainfrom
feat/two-node-fixture
Sep 29, 2026
Merged

emooreatx merged 9 commits into
mainfrom
feat/two-node-fixture

Conversation

@emooreatx

Copy link
Copy Markdown
Contributor

Stacked on #97. Ports CIRISServer harness/mesh-repro/scenarios/chat.sh so the five-platform matrix can test flows that need a second person.

  • testing/gate/two_node.py: the leg's own ciris-server binary as a second native process on 5242/5243, own home and key id; claims the peer, announces both, peers both ways (production self-key-record), each owner adds the other as a contact, both open the pair room, the peer sends a message once keyed. down kills it by pidfile and deletes its home.
  • Flow runner: fixture: two_node on a flow; ${PEER_KEY_ID}, ${ROOM_ID} etc. filled from the fixture; an unresolved ${…} is a load error; a value the fixture couldn't produce fails the step with its reason. Fixture-free flows run first; the fixture starts only for flows that ask and pass their floor; torn down in finally and on SIGTERM.
  • Workflow: every leg passes the node binary and its own peer dir, tears down under always(), uploads the peer log.

Local, Linux desktop (the workflow's form, scratch ports; the user's node untouched): people 3/3; CSD-006 receipt 5/6 (the grant-less step skips by design); CSD-005 through the five-fact receipt, then a wrong tag in the draft (btn_scan_contact_code vs _status); CSD-091 through the composer, then no message crosses.

Upstream: two released v0.5.217 nodes never key a pair room (peers stay advisory; frames fail the transport-destination binding) — CIRISServer#698, recorded in evidence/blocked_upstream.tsv. The server's own chat ladder only passes on a test-anchor build.

Not run locally: macOS, Windows, Android emulator, iOS simulator (Windows teardown, the Android fresh-node step and iOS host→app reach are unverified until the matrix runs). The drafts are floored 0.5.225, so the matrix refuses them until that version.

🤖 Generated with Claude Code

https://claude.ai/code/session_0155SkTGdbwnSnR6tWBqJvUM

emooreatx and others added 7 commits September 28, 2026 20:56
The two-node fixture edits CSD §5 lines ("Flow not complete") and drafts that
exist only on main (#128), so it stacks on #97 with main merged in.

Two conflicts: the vendoring digest (re-recorded), and a semantic one —
#97's test_the_real_csd_005_refuses_its_proposed_trust_chip named
`contacts_row_trust`, which main's CSD-005 no longer marks proposed. The test
now derives a real proposed tag from the shipped CSDs instead of naming one.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0155SkTGdbwnSnR6tWBqJvUM
…e chat scenario, without Docker

CIRISServer's harness/mesh-repro/scenarios/chat.sh stands two nodes up on a
compose bridge, which only the Linux leg can host: macOS runners have no
Docker, Windows runners run Windows containers, and neither the Android
emulator nor the iOS simulator sees a compose network. testing/gate/two_node.py
is the same sequence as a native process from the binary the leg already
downloaded, in stdlib Python so all three runner images can run it:

- `config set net.listen_addr` / `net.bootstrap_peers` offline before the first
  boot (node_boot.sh) — own ports 5242/5243, own --home, unique --key-id;
- `identity create` + `claim --cohort-scope self` on the peer's console with
  the PIN from <home>/claim_pin; `POST /v1/federation/announce`;
- the leg's node, claimed by the CLIENT's wizard, is signed in to with the same
  credentials and announced; its owner read off the announce bundle;
- `POST /v1/federation/peering` both ways with the production
  self-key-record (the harness's test-blessed record and test-admit-peer are
  compiled only into test-anchor builds);
- each owner adds the other (`POST /v1/contacts`; falls back to the contact
  code, then the peer NODE, and records which); both open the pair room; the
  peer speaks only once the room is keyed; arrival on the leg's node is
  waited for and recorded, and a message that did not cross is never handed
  to a flow.

`down` kills the peer by its pidfile and deletes its home, for an always()
step; TwoNodeFixture tears down on exit and on SIGTERM.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0155SkTGdbwnSnR6tWBqJvUM
A flow could not name the thing a second node creates: the receipt's
hamburger is `btn_receipt_<keyId>`, the peer row `peer_pick_row_<keyId>`, the
message `chat_msg_<attestationId>`, and a `click:` takes one literal tag. Every
step that needed one was `optional_step` gated on a tag nothing opened, so it
always skipped (CSD-006/047/091 §5 "Flow not complete").

- flow_spec: a flow-level `fixture:` key (known: two_node) and `${NAME}` in any
  tag, text, glob or input value. A name with no fixture to fill it is a LOAD
  error — it would otherwise reach the app as the literal `${NAME}` and fail as
  "element not found". At run time each step is resolved from the fixture's
  values; a value the fixture did not produce FAILS the step, optional or not,
  quoting the fixture's own note for why. `matches:` values are regex-escaped.
- run_flows.run_all: plain flows run first whatever the file order (a fixture
  changes the leg's node, and people.yaml asserts the bare one); a fixture is
  stood up once, just before the first runnable flow that asks for it — a run
  with none, or whose fixture flows its floor refuses, never pays for it — and
  torn down in a `finally`. A fixture that cannot stand up leaves its flows
  cannot-start, naming why.
- run_flows / run_platform: --node-binary, --node-url, --peer-port, --peer-work.
- test_flows: a `${NAME}` tag is carried only if its literal head IS one of the
  client's interpolated prefixes.

Shown red first: test_flow_fixtures.py against the previous flow_spec/run_flows
fails at collection (no UnresolvedVariable, no fixture support).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0155SkTGdbwnSnR6tWBqJvUM
…own under always()

- Every run_platform call (Linux, Android, macOS, iOS, Windows) passes
  --node-binary node/ciris-server and its own --peer-work under $RUNNER_TEMP.
  The peer starts only if a loaded, unrefused flow says `fixture: two_node`.
- Reachability: the fixture runs on the leg's host and talks to both nodes on
  loopback. Desktop legs use 127.0.0.1:4243; the Android emulator reaches the
  same host node through the existing adb reverse and never dials the peer; the
  iOS app's embedded node shares the host loopback, so it is 127.0.0.1:4243
  from the fixture too, and the peer's 5242/5243 do not collide with it.
- Linux: the Android leg shares the desktop leg's node. When the desktop
  fixture seeded it (values.json exists), the node is stopped and the Android
  leg gets a fresh one on a fresh home, so its bare-node flows are not red for
  the desktop's reason.
- Each job runs `two_node down` for its legs under always() (the runner's
  `finally` and SIGTERM handler do not survive a SIGKILL), and uploads each
  peer's log and values.json.
- test_five_platform_workflow: the flags on every call, the always() teardown
  per leg, and the fresh node's position between the two Linux legs.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0155SkTGdbwnSnR6tWBqJvUM
…nstead of skipping

- csd-006-receipt: refreshes People, opens `btn_receipt_${PEER_KEY_ID}` and
  asserts the five facts, the wire dimension and the wire rule, then closes.
- csd-005-people: the populated row, its trust chip and hamburger by key id,
  and the receipt step now clicks the seeded contact's hamburger.
- csd-047-network-content: enters from the hub's `tile_federation_content`
  (NetworkContent has no nav hop) and picks `peer_pick_row_${PEER_NODE_KEY_ID}`.
- csd-091-user-chat: enters the room from People by
  `btn_contacts_chat_${PEER_KEY_ID}` and asserts the peer's message by its
  attestation id — a value the leg's node cannot mint for itself.

Measured on the Linux desktop leg, locally, 2026-09-28 (candidate 0.5.224 run
as 0.5.225, node v0.5.217, leg node on 5142/5143, peer on 5242/5243):
people 3/3; csd_006 pass (5/6, grant-less step skipped as designed); csd_005
row/chip/hamburger/receipt pass, then fails at btn_scan_contact_code (the
desktop shows btn_scan_contact_code_status — unrelated to the fixture);
csd_091 entry/composer/refresh pass, history fails naming why; csd_047 cannot
start (nav_map's hop to LayerGlobalCommons stops on CircleTab).

Why 091 cannot pass on the released line is recorded in
evidence/blocked_upstream.tsv: two unconferred v0.5.217 nodes admit each other
ADVISORY, frames fail the SignedTransportDestination check, and the pair room
never keys in 480 s. CSD §5 lines and both flow READMEs say so.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0155SkTGdbwnSnR6tWBqJvUM

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 9af174cfa4

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread testing/gate/two_node.py
# `finally` unless it is turned into an exit. The peer runs in its own
# session, so nothing else would reap it. (The workflow also runs
# `two_node down` under `if: always()` for a SIGKILL.)
atexit.register(self.down)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Keep the standalone peer alive after up

When the documented python3 -m testing.gate.two_node up ... command succeeds, main() immediately exits and this registered callback invokes self.down(), terminating the peer, removing its PID file, and deleting its home. Consequently the command advertised as “leave the peer running” cannot be used with the separate down command; limit this exit cleanup to the in-process flow-runner path or unregister it before the standalone up command returns.

Useful? React with 👍 / 👎.

…ode down

The exit/SIGTERM cleanup that protects the in-process runner also fired
when the standalone `up` command returned, killing the peer it was asked
to leave running (Codex, PR #130). `up` now detaches after seeding; test
red on the old code.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0155SkTGdbwnSnR6tWBqJvUM
@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.

@emooreatx
emooreatx changed the base branch from feat/csd-flows-on-the-matrix to main September 29, 2026 04:52
@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.

@emooreatx
emooreatx merged commit 743fb7c into main Sep 29, 2026
9 checks passed
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