Skip to content

test(e2e): fix red dev e2e (leaked guard clone) + add Sweettest CI matrix - #137

Merged
Soushi888 merged 2 commits into
devfrom
fix/e2e-clone-guard-teardown
Aug 15, 2026
Merged

test(e2e): fix red dev e2e (leaked guard clone) + add Sweettest CI matrix#137
Soushi888 merged 2 commits into
devfrom
fix/e2e-clone-guard-teardown

Conversation

@Soushi888

Copy link
Copy Markdown
Collaborator

Intent

dev has been red on e2e since #126 merged (2026-08-09): build: success, e2e: failure on every run since, which also fails the CI of every PR against dev (visible as a deterministic failure on the docs-only #131). This PR fixes the root cause on dev directly and adds the Sweettest CI gate, extracted from #132 so it does not have to wait on a 98-file review.

Root cause

The clone guard test provisions a group clone cell with a unique network seed and never tears it down. The UI enumerates group clone cells straight off appInfo, so the leaked guard clone renders in the sidebar as a real group. The next serial test, empty lobby shows the create-or-join onboarding CTA, requires an empty lobby and fails. #126's group-persistence work made the sidebar reliably re-enumerate cells, turning a latent leak into a deterministic failure. Playwright retries share conductor state, so the original attempt and both retries fail identically.

Changes

  • core-flows.spec.ts: the guard test tears its clone cell down in a finally (disable + admin delete); the empty-lobby test asserts its precondition first via expectEmptyLobby, so a future leak fails with a message naming the leaked groups instead of "element not found".
  • e2e-helpers.ts: new expectEmptyLobby(page) helper with a diagnostic message; widens the NdoFormInput.regime union (inert typing, no test uses the new values).
  • harness.ts: exposes appId on SeedClient, needed by deleteCloneCell.
  • build.yml: Sweettest lands in CI as a five-target shard matrix (misc, person, governance, resource, nondominium), fail-fast: false, shared rust-cache, --test-threads 2; the e2e job is re-gated behind sweettest so backend regressions fail before the slower browser suite. Same workflow content as validated on feat(resource)!: NDO Layer 1 typed governance rules, classification constraints, and OperationalState #132.

Both files taken verbatim from the ndo-layer1 branch, where they are green (the only difference: this branch does not carry the rest of Layer 1). The e2e hunks reference no Layer 1 UI or zome surface; the five sweettest targets, the nondominium_shared package, and the vendor/hrea workspace all already exist on dev.

How to test

  • nix develop --command bun run build:happ
  • nix develop --command bash -c "cd packages/shared-types && bun run build"
  • nix develop --command bun run e2e -> 18 passed (2.2m), including empty lobby shows the create-or-join onboarding CTA (23ms)
  • CI on this PR: build + all five sweettest shards + e2e

Risk

dev's Sweettest suite has never run in CI. If a shard comes back red, that is a latent dev regression surfaced by the new gate; it gets fixed in this PR, not by weakening the gate.

@Soushi888
Soushi888 merged commit 625c73b into dev Aug 15, 2026
7 checks passed
@Soushi888
Soushi888 deleted the fix/e2e-clone-guard-teardown branch August 15, 2026 23:04
Soushi888 added a commit that referenced this pull request Aug 16, 2026
…rdown

The rebase onto dev (which now carries the clone-guard teardown and the
expectEmptyLobby precondition from #137) dropped this branch's changes to
the same file. Both are needed: the guard teardown and CTA precondition
come from dev, while the two DHT read-backs must enumerate the agent's ndo
clone cells rather than the shared nondominium cell, since under the
per-NDO-cell model the UI writes NondominiumIdentity into its own clone.
Soushi888 added a commit that referenced this pull request Aug 16, 2026
* feat(shared,resource): NdoDnaProperties + ADR-013 DNA-properties binding

- Add NdoDnaProperties (immutable Layer 0 fields, excludes lifecycle_stage)
  to nondominium_shared so both integrity zomes and the UI consume one shape.
- zome_resource_integrity::validate_create_nondominium_identity now checks the
  entry agrees with dna_info().modifiers.properties when they deserialize to
  NdoDnaProperties (ndo cells cloned with properties). On the shared
  nondominium cell (properties: ~) deserialization fails and the check is
  skipped, so legacy shared-cell NDOs keep working.
- Add holochain_serialized_bytes dep to nondominium_shared for the SerializedBytes derive.

* refactor(shared,resource): narrow NdoDnaProperties to 5 fields, classification-only binding

- NdoDnaProperties drops description (entry-only) to match the Sweettest
  NdoCellProperties mirror exactly, so client and tests derive the same DnaHash.
- Binding check validates name/regime/nature only (the classification fields).
  initiator/created_at are bound by the DnaHash but not entry-validated, since the
  entry sets them at create_ndo runtime and the Sweettest agent-key-per-cell
  artifact prevents equality. Full mirror validation deferred to the create_ndo
  refactor. Non-breaking: existing ndo_anchor Sweettests keep passing.

* fix(shared): drop initiator from NdoDnaProperties

holochain 0.6.0 transports create_clone_cell properties as
YamlProperties(serde_yaml::Value), which has no binary variant and cannot
carry an AgentPubKey. An initiator in the properties made client
createCloneCell hang. NdoDnaProperties is now 4 YAML-safe fields
(name, property_regime, resource_nature, created_at); the DnaHash binds
classification + microsecond-unique creation time, and initiator stays
authoritative on the entry and cached on the NdoAnchor for display.

* feat(group): NdoAnchor lifecycle-stage refresh resolver

After an NDO lifecycle transition, lobby/group cards read the anchor's
cached lifecycle_stage; without a refresh the card stays at the
creation-time stage. refresh_ndo_anchor_lifecycle_stage resolves the
anchor by NDO identity (not action hash) via the GroupToNdoAnchors link
and follows the NdoAnchorUpdates chain to the latest version, so repeated
refreshes stay visible to get_ndo_anchors. The client knows only the NDO
identity; original-vs-latest anchor action hashes stay inside the group
link graph.

Refactored latest_anchor_record into latest_anchor (returns latest hash
+ record) so both get_ndo_anchors and the resolver share the chain walk.
Two new sweettests: refresh updates the cache (peer-visible); no-op when
the identity is absent.

* feat(ui): per-NDO-cell model A with DnaHash binding

Each NDO gets its own cloned ndo cell whose DnaHash is cryptographically
bound to the NDO identity via YAML-safe DNA properties (ADR-010 model A,
ADR-013 binding). createNdo provisions the clone, writes the genesis
NondominiumIdentity inside, and anchors it in the group cell with full
clone coordinates. Lobby/group grids read NdoAnchors; NDO detail resolves
the ndo cell from the anchor and reads the live entry. Lifecycle
transitions run on the ndo cell and refresh the group anchor so cards
converge without a reload.

ensureNdoCloneCell does not re-authorize existing-enabled cells (avoids
source-chain-head-moved races). Connect path skips disabled clones;
lobby descriptor builder skips disabled clones. e2e read-backs migrated
to per-cell reads; multi-agent live-transition discriminator added.

* feat(group): enforce NdoAnchor identity-coordinate immutability

An anchor update may only refresh the cached descriptor (name, description,
lifecycle_stage). The identity coordinates are what a peer re-derives the NDO
cell from, so rewriting them silently repoints a group at a different network.
Any group member can update any member's anchor, so the rule belongs in
integrity rather than in the coordinator.

validate_ndo_anchor_update rebuilds the original with the mutable fields
swapped in and compares, so a field added to NdoAnchor later is immutable by
default and must be opted into mutability deliberately.

The new Sweettest exercises the attack through the real API: update_ndo_anchor
takes original_action_hash and previous_action_hash separately, so passing two
different anchors writes A's coordinates over B.

Also drops the hand-kept NdoDnaProperties mirror from the test crate in favour
of the shared definition (that mirror had already drifted once on initiator; a
drifted mirror derives a different DnaHash, the exact failure these tests
exist to catch), and corrects the created_at doc comment: it is micros from a
millisecond clock, not a uniqueness source, which is the per-NDO network_seed.

* fix(ui): associate NDO with a group by writing an anchor, not a dead SoftLink

NdoView exposes "Associate with a group" on every NDO page. It wrote a
SoftLink, but after the anchor migration both read paths follow NdoAnchors
only, so the modal reported success and the NDO never appeared in the target
group. NdoService.associateNdoWithGroup now writes a second NdoAnchor there,
copying the source anchor's clone coordinates, and no-ops if already anchored.
createSoftLink and getSoftLinkTargetHashes lose their last callers and are
marked deprecated for NDO association.

Two related hardenings:

- createNdo no longer swallows the anchor write. The anchor is the only
  pointer any read path follows, so a swallowed failure left a cell nobody
  could reach. The destination group is now resolved before the clone cell is
  provisioned, so a bad group fails cheaply instead of orphaning a cell.

- One scanGroupAnchors() pass answers every "where is this NDO anchored"
  question. The detail read, getAssociatedGroupIds and the post-transition
  refresh share it, and the refresh now touches only the groups that actually
  anchor the NDO instead of walking every group cell.

NdoAnchorInput becomes an alias of NdoAnchorEntry: the coordinator builds the
entry straight from the payload, so a second declaration could only drift.

* docs: record ADR-010..013 in-repo and sync the per-NDO-cell model

The per-NDO-cell change touches the NDO Layer 0 identity model, which TELOS.md
flags as load-bearing, but ADR-010/011/012/013 lived only in .local/ so the
repo carried no record of it. Adds documentation/specifications/adr/
ADR-010-013-per-ndo-cells.md: the four decisions, the YamlProperties transport
constraint that forced initiator out of the DnaHash, the shared-cell skip path,
the open follow-ups, and a map of where each piece lives in code.

Then corrects the statements the migration made stale:

- specifications.md 7.1 gains NdoDnaProperties and NdoAnchorEntry; 7.2 replaces
  the SoftLink-backed ndo.service table with the anchor-backed one
- ui_architecture.md and ui_design.md describe anchors and per-NDO cells
- IMPLEMENTATION_STATUS.md lists NdoAnchor and the four anchor externs
- lobby-dna.md marks the NDO DNA and UI cutover rows shipped
- API_REFERENCE.md gains an NDO Anchors section for all four externs
- requirements.md gains REQ-UI-GRP-06 for the anchor model

SoftLink stays documented as a planning-level link, no longer as the group to
NDO pointer.

* test(e2e): restore per-cell NDO read-back on top of the dev guard teardown

The rebase onto dev (which now carries the clone-guard teardown and the
expectEmptyLobby precondition from #137) dropped this branch's changes to
the same file. Both are needed: the guard teardown and CTA precondition
come from dev, while the two DHT read-backs must enumerate the agent's ndo
clone cells rather than the shared nondominium cell, since under the
per-NDO-cell model the UI writes NondominiumIdentity into its own clone.
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