test(e2e): fix red dev e2e (leaked guard clone) + add Sweettest CI matrix - #137
Merged
Conversation
This was referenced Aug 15, 2026
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.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Intent
devhas been red on e2e since #126 merged (2026-08-09):build: success, e2e: failureon every run since, which also fails the CI of every PR againstdev(visible as a deterministic failure on the docs-only #131). This PR fixes the root cause ondevdirectly 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 guardtest provisions a group clone cell with a unique network seed and never tears it down. The UI enumerates group clone cells straight offappInfo, 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 afinally(disable + admin delete); the empty-lobby test asserts its precondition first viaexpectEmptyLobby, so a future leak fails with a message naming the leaked groups instead of "element not found".e2e-helpers.ts: newexpectEmptyLobby(page)helper with a diagnostic message; widens theNdoFormInput.regimeunion (inert typing, no test uses the new values).harness.ts: exposesappIdonSeedClient, needed bydeleteCloneCell.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-layer1branch, 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, thenondominium_sharedpackage, and thevendor/hreaworkspace all already exist ondev.How to test
nix develop --command bun run build:happnix develop --command bash -c "cd packages/shared-types && bun run build"nix develop --command bun run e2e-> 18 passed (2.2m), includingempty lobby shows the create-or-join onboarding CTA(23ms)Risk
dev's Sweettest suite has never run in CI. If a shard comes back red, that is a latentdevregression surfaced by the new gate; it gets fixed in this PR, not by weakening the gate.