Skip to content

feat(resource)!: NDO Layer 1 typed governance rules, classification constraints, and OperationalState - #132

Open
Soushi888 wants to merge 12 commits into
devfrom
ndo-layer1
Open

feat(resource)!: NDO Layer 1 typed governance rules, classification constraints, and OperationalState#132
Soushi888 wants to merge 12 commits into
devfrom
ndo-layer1

Conversation

@Soushi888

@Soushi888 Soushi888 commented Aug 13, 2026

Copy link
Copy Markdown
Collaborator

Intent

Activates NDO Layer 1 (Specified) on top of the existing Layer 0 identity anchor, and closes two long-standing TODOs in the resource zome: the untyped GovernanceRule payload, and the ResourceState enum that conflated lifecycle maturity with operational condition.

The result is that a ResourceSpecification now points at the Layer 0 NondominiumIdentity it activates, governance rules carry a typed schema instead of a JSON blob, and classification coherence (regime x nature x rivalry x action) is enforced by pure predicates shared between the integrity zome, the coordinator, and the UI.

Branch authored by Tibi (ndo-layer1, 6 commits), then reviewed and hardened in four further commits (see Review follow-ups).

Changes

Shared crate (crates/shared)

  • New rule_data module: RuleData tagged enum with four variants (AccessRequirement, UsageLimit, TransferCondition, MaintenanceSchedule). GovernanceRuleType is derived from the discriminant via RuleData::rule_type(), never stored separately.
  • New constraints module: pure, hdk-free predicates over a ResourceClassification (nature, regime, lifecycle, rivalry override). Emits ConstraintViolation { rule_id, message, severity } with Hard / Soft severity. Current rules: nondominium_no_unilateral_capture, ownership_transfer_not_permitted_by_regime, gated_access_contradicts_permissionless_regime, no_transport_for_non_physical_nature.
  • New enums in types: Rivalry (with ResourceNature::default_rivalry()), ResourceScope (Project / Network / Public), OperationalState (Available, Reserved, InTransit, InStorage, InMaintenance, InUse, PendingValidation). New PropertyRegime predicates: is_rivalrous, permits_ownership_transfer, is_uncapturable, default_accessibility.

Zomes

  • ResourceSpecification gains scope, ndo_identity_hash (the immutable, stable Layer 0 pointer), and ndo_state_hash (the NDO action whose lifecycle stage the author observed). Both hashes are immutable after creation. Project-scoped specs skip the global discovery anchor.
  • GovernanceRule replaces rule_type: String + rule_data: String with a typed RuleData, plus ndo_identity_hash and denormalized property_regime / resource_nature / rivalry_override. Integrity reads the referenced Layer 0 record and rejects any classification that contradicts it (see Review follow-ups).
  • EconomicResource.state: ResourceState becomes operational_state: OperationalState; ResourceState is deleted. ResourcesByState becomes ResourcesByOperationalState; lifecycle faceting stays on Layer 0's NdoByLifecycleStage.
  • New NdoToSpecification link type: the Layer 0 to Layer 1 activation edge.
  • zome_gouvernance: new transition module, plus economic-event and commitment updates carrying the constraint context.

UI

  • New components: SpecificationCreateModal, RuleEditorModal (typed rule authoring), CommitmentCreateForm, EconomicEventCreateForm.
  • New helpers: operational-state-labels.ts, rivalry.ts. Governance / resource services, stores, and schemas updated for the new shapes.
  • GroupService stub replaced with a real callZome service layer for the Group DNA.

CI

  • New sweettest job: cargo test -p nondominium_shared plus all five Sweettest targets, sharded one job per [[test]] target with a shared rust-cache key. e2e is now gated behind it, as the workflow's own comment had asked for. Before this PR, the primary backend suite per CLAUDE.md had never gated a merge.

Tests

Sweettest coverage extended in resource, governance, ndo_layer0, and the group ndo_anchor suite (about 800 added lines across the four), plus six negative tests added during review.

Documentation

New: Source-NDO.md, source-ndo-requirements.md, source-ndo-paper.md, source-valueflows-integration.md, complete-resource-specification.md, plus the ArtCoin application docs and user stories. Updated: requirements.md, resources.md, governance.md, ndo_prima_materia.md, implementation_plan.md, IMPLEMENTATION_STATUS.md, resource_zome.md, API_REFERENCE.md, and the valueflows-dsl.md / vf:Source design notes.

Review follow-ups

Four commits on top of Tibi's six. Two are integrity bugs found in review; both were demonstrated red before their fix landed.

1. Layer 0 classification was writer-controlled (09224eb)

validate_create_governance_rule built its ResourceClassification from rule.property_regime / resource_nature / rivalry_override, all supplied by the caller, and never checked them against the referenced ndo_identity_hash. A TransferCondition{Ownership} rule on a Nondominium NDO passed validation simply by declaring property_regime: Private. The capture-resistance guarantee (REQ-RES-03) was self-declared.

Integrity now reads the referenced NondominiumIdentity and rejects any mismatch before evaluating constraints. All three fields are immutable on Layer 0, so the genesis record reached through the stable hash is authoritative. validate_update_governance_rule delegates to create, so updates are covered.

2. The Layer 1 lifecycle gate read the wrong stage (923b3d8)

validate_create_resource_spec called must_get_valid_record(ndo_identity_hash), which returns the genesis record. lifecycle_stage mutates through the update chain, so the gate judged every activation against the creation-time stage: an NDO created at Ideation and advanced to Specification could never grow a spec (the primary intended flow), while one created at Active and since Deprecated still could.

ResourceSpecification now carries ndo_state_hash. Integrity cannot walk an update chain forward (the set of updates grows, so validation would not replay), but backward is deterministic: every Update names exactly one predecessor via original_action_address, and that edge never changes. resolve_ndo_state reads the observed entry, walks back to the genesis Create, and proves the root equals ndo_identity_hash before gating on the stage. Capped at 64 hops. The coordinator derives the field via resolve_latest_ndo_record, so honest clients are correct by construction, and immutability on update prevents re-pointing a spec at a newer state to launder a rejected activation.

3. Test hardening (09224eb, 923b3d8)

Six new Sweettests in dnas/nondominium/tests/src/resource/mod.rs: governance_rule_rejects_classification_drift_from_layer0, ..._nature_drift_..., nondominium_ownership_transfer_not_bypassable_by_misdeclared_regime, governance_rule_accepts_classification_matching_layer0 (over-tightness guard), resource_spec_allowed_after_advancing_out_of_ideation, resource_spec_rejected_after_deprecation. Classification tests were 3 failed / 2 passed pre-fix and 9 passed post-fix; lifecycle tests 0 passed / 2 failed pre-fix and 11 passed post-fix.

The pre-existing check_rule_data_constraints_blocks_nondominium_ownership_transfer looked like it covered bug 1 but did not: it exercises the pure dry-run query, which takes classification as a parameter.

4. Red e2e pipeline (74532aa)

Pre-existing on dev (run 31285564294), not a Layer 1 regression. The Phase 0 clone-signing guard in core-flows.spec.ts created a group clone cell and never removed it; the UI enumerates group clone cells off appInfo, so the leftover cell rendered as a real group, hasGroups became true, and the onboarding CTA never mounted. Fixed with disableCloneCell + admin.deleteCloneCell in a finally block. expectEmptyLobby now asserts the precondition explicitly so a future leak names itself instead of surfacing as "element not found". e2e went from 10 passed / 1 failed / 7 skipped to 18 passed / 0 failed.

Decisions

Option Rejected because
Keep rule_data as a JSON string No schema enforcement, no tooling, and validation could not reason about rule semantics. The typed enum is the GovernanceRuleType migration called for in ndo_prima_materia.md.
Store GovernanceRuleType alongside the payload Two sources of truth that can disagree. Deriving it from the RuleData discriminant makes the mismatch unrepresentable.
Extend ResourceState with more variants It conflated two orthogonal axes. LifecycleStage (Layer 0, maturity) and OperationalState (instance, current process) now move independently, per REQ-NDO-OS-06.
Block every constraint violation in the integrity zome Some coherence rules are advisory, not invariants. Hard blocks at integrity; Soft surfaces as coordinator and UI advice.
Make rivalry a required stored field Nature implies it in nearly every case. It is derived from ResourceNature::default_rivalry() with an immutable rivalry_override for the exceptions (for example a rivalrous Service slot).
Read Layer 0 during rule validation (reversed in review) The original rationale was that integrity validation cannot afford DHT reads. It can: validate_create_resource_spec already did one sixty lines up. Denormalization is kept for the constraint evaluation, but integrity now reads Layer 0 and rejects any field that disagrees, so the declared classification is no longer trusted.
Move the lifecycle gate to the coordinator Cheaper than the backward chain walk, but a coordinator is not a trust boundary: any client can write to the DHT directly.
Resolve the latest NDO stage inside integrity Impossible, not merely expensive. Forward chain resolution is non-deterministic under replay because the set of updates grows after the fact. Hence ndo_state_hash. Accepted limitation: an author writing directly to the DHT can present an old-but-eligible state.

Breaking changes

This changes DHT entry shapes and link types, so it is a DNA-hash-breaking change. No migration path is provided; existing test networks need to be recreated.

  • EconomicResource.state: ResourceState becomes operational_state: OperationalState. ResourceState is removed.
  • GovernanceRule.rule_type / rule_data strings become a single typed RuleData, plus three required classification fields, which integrity now checks against Layer 0.
  • ResourceSpecification gains required ndo_identity_hash, ndo_state_hash, and scope.
  • LinkTypes::ResourcesByState becomes ResourcesByOperationalState; NdoToSpecification is added.

How to test

nix develop
bun install
bun run build:happ

# Sweettest: 2 threads. Six threads SIGTERMs the process mid-run with 11
# conductor-spawning tests in flight, and it reads exactly like a test failure.
CARGO_TARGET_DIR=target/native-tests cargo test -p nondominium_sweettest --test resource -- --test-threads 2
CARGO_TARGET_DIR=target/native-tests cargo test -p nondominium_sweettest --test governance -- --test-threads 2
CARGO_TARGET_DIR=target/native-tests cargo test -p nondominium_sweettest --test nondominium -- --test-threads 2

bun run start   # 2-agent network: create a spec from an NDO, author a typed rule, move operational state

Verified: all five Sweettest targets green locally at --test-threads 2 (governance 4 with 2 ignored, misc 1, nondominium 11, person 2, resource 11); build:happ and e2e 18/18 in CI (run 31731642447); the whole suite green in CI as one job (run 31736241424, 61 min, since sharded). The sharded pipeline's own first run is in flight on this head.

Documentation

Extensive. See the Documentation subsection under Changes for the full list of added and updated files.

Related

Related issues:

Deferred, deliberately out of scope here:

  • Layer 2 activation (NdoToProcess), and wiring the constraint predicates into evaluate_transition. zome_gouvernance/src/transition.rs:7 carries a live TODO(§5 item 2) on whether existing write paths should funnel through evaluate_state_transition.
  • Design collision to settle before Source-NDO lands: check_capture_resistance Hard-blocks Consume on Nondominium, but the Source-NDO requirements this same PR documents mandate recording extraction from Nondominium / CommonPool sources (REQ-PROC-10, REQ-SOURCE-EVENT-01). One of the two has to give.

Closes #136

TiberiusB and others added 10 commits August 6, 2026 15:14
Introduced a TypeScript service layer for Group DNA, replacing the GroupService stub with a callZome implementation. Updated group types and services to support new API functionalities. Enhanced documentation to include API references, architecture overviews, and test commands for Group DNA, ensuring clarity on the new structure and interactions within the system.
…governance framework

Added comprehensive documentation for the Source-NDO, a new ontological primitive representing generative ecological systems. This update includes the `source-ndo-requirements.md` detailing its role, governance patterns, and integration within the Nondominium architecture. Enhanced existing documents to reflect the necessity of the `vf:Source` ValueFlows extension and the adaptive governance loop for ecological systems. This change aims to clarify the framework for managing ecological commons and ensure accurate representation of environmental interactions within the economic information system.
Introduced a new document detailing the integration of the `vf:Source` primitive into the Nondominium's Valueflows model. This comprehensive design outlines the purpose, structure, and implications of the `vf:Source` for generative ecological systems, distinguishing it from existing primitives. Additionally, updated the Valueflows DSL documentation to clarify the distinction between the current and planned capabilities, emphasizing the future integration of the `vf:Source` within the Nondominium architecture.
…management

Added the OperationalState enum to represent the current process condition of EconomicResource instances, allowing for states such as Available, Reserved, InTransit, InStorage, InMaintenance, InUse, and PendingValidation. Implemented functionality for creating, updating, and querying resources by their operational state. Updated relevant tests and documentation to reflect these changes, enhancing the resource management capabilities within the Nondominium architecture.
…nd governance integration

Revised the documentation for the Artcoin application within the Nondominium framework. Key updates include detailed descriptions of user stories for art circulation, distribution, and production, emphasizing the roles of individual patrons and venues. Enhanced governance rules and operational states for artworks are now clearly outlined, alongside the integration of Private Participation Receipts (PPRs) for tracking reputation and custody. This update aims to provide a comprehensive understanding of the Artcoin ecosystem and its alignment with Nondominium's resource-sharing capabilities.
…ules

Added new modules for resource classification and governance rules within the Nondominium framework. The `constraints` module includes predicates for evaluating resource classifications, handling constraint violations, and ensuring compliance with governance rules. The `rule_data` module defines various governance rule types, including access requirements and transfer conditions. This update enhances the integrity validation process and supports the management of resources in a decentralized environment, aligning with the overarching goals of the ArtCoin project.
…mpty

The Phase 0 signing guard creates a `group` clone cell on agent 1 and never
removes it. The UI enumerates group clone cells straight off `appInfo`, so the
leftover cell rendered as a real group in the sidebar, `hasGroups` became true,
and the next test's create-or-join onboarding CTA never mounted.

Disable and delete the clone in a `finally` block, and expose `appId` on
SeedClient for the admin-scoped delete.

Pre-existing failure on `dev` (run 31285564294), not a Layer 1 regression.
The Layer 1 constraint predicates were only ever exercised through
`check_rule_data_constraints`, which takes the classification as a parameter.
Nothing bound a rule's denormalized `property_regime` / `resource_nature` /
`rivalry_override` to the NDO it claims to describe, so capture resistance was
self-declared: an ownership-transfer rule on a Nondominium NDO passed simply by
writing `Private` on the rule entry.

Integrity now reads the referenced NondominiumIdentity and rejects any
mismatch before evaluating constraints. All three fields are immutable on
Layer 0, so the genesis record reached through the stable hash is authoritative
and no update-chain walk is needed — the same read `validate_create_resource_spec`
already performs.

Four Sweettests cover the boundary: regime drift, nature drift, the
misdeclared-regime bypass of REQ-RES-03, and a matching-classification guard so
the binding cannot be over-tight. Verified red against the unfixed zome
(3 failed / 2 passed) before the fix, green after (9/9).

CI gains a `sweettest` job running the shared-crate unit tests and all five
Sweettest targets, with `e2e` now gated behind it as the workflow comment asked.
`--test-threads 2`: 6 threads with 11 conductor tests in flight gets SIGTERM'd.

The empty-lobby e2e test asserts its precondition via `expectEmptyLobby` so a
leaked group names itself instead of surfacing as "element not found".
…st in CI

The Layer 1 lifecycle gate read `must_get_valid_record(ndo_identity_hash)`,
which returns the *genesis* record. `lifecycle_stage` mutates through the
update chain, so the gate judged every activation against the stage the NDO was
created at: an NDO created at Ideation and advanced to Specification could never
grow a spec, while one created at Active and since Deprecated still could.

`ResourceSpecification` now carries `ndo_state_hash` — the NDO action the author
observed — alongside the stable `ndo_identity_hash`. Integrity cannot walk an
update chain forward (the set of updates grows, so validation would not replay),
but backward is deterministic: every Update names exactly one predecessor via
`original_action_address` and that edge never changes. `resolve_ndo_state`
therefore reads the observed entry, walks back to the genesis Create, and proves
the root matches `ndo_identity_hash` before gating on the stage. Capped at 64
hops; the chain is at most 10 stages in practice.

The coordinator derives the field via `resolve_latest_ndo_record`, so honest
clients are correct by construction, and `ndo_state_hash` is immutable on update
so an edit cannot re-point a spec at a newer state to launder an activation the
create-time gate rejected. Accepted limitation: an author writing directly to
the DHT can present an old-but-eligible state.

Two Sweettests cover both directions, verified red against the unfixed zome
(0 passed / 2 failed) and green after (11/11).

CI: the single sweettest job measured 61 min (run 31736241424) — 16 min compile
plus 40 min of tests back-to-back. Sharded one job per target with a shared
rust-cache key, and dropped the custom CARGO_TARGET_DIR: `target/native-tests`
is right locally (keeps native artifacts away from the wasm build) but falls
outside what rust-cache saves, so every run recompiled holochain test_utils.
@Soushi888

Copy link
Copy Markdown
Collaborator Author

Review: REQUEST CHANGES

Reviewed at 923b3d8 against origin/dev. The Layer 1 architecture is sound and the two integrity bugs caught in review were fixed the right way. But the same writer-controlled-classification shape survives on the Layer 2 path, and the CI job this PR builds to gate merges leaves one suite out.

Merge state mergeable, not behind dev
CI build pass; 5 sweettest shards pending; e2e gated behind them
Description 3 areas in the diff not in ## Changes
Docs gate 2 mapped files missed, 1 stale section
Tests gate 6 negative tests, red-before-green demonstrated

Request changes

R1. Layer 2 capture resistance is still self-declared.

EconomicEvent and Commitment each carry their own ndo_identity_hash, and nothing binds it to resource_inventoried_as. validate_action_against_ndo therefore judges the action against whatever NDO the writer names, so Transfer on a Nondominium resource passes by naming a Private NDO instead.

The new test demonstrates the gap rather than closing it: nondominium_transfer_event_is_hard_rejected (dnas/nondominium/tests/src/governance/mod.rs:348) passes a stub resource_inventoried_as of ActionHash::from_raw_36(vec![7u8; 36]), an action hash with no relationship to the NDO whose regime is being enforced.

This is review follow-up #1 relocated, not fixed. Instead of declaring a false regime inline, the writer declares a false NDO pointer. REQ-RES-03 remains self-declared on the Layer 2 write path.

Two ways out:

  • Give EconomicResource a spec pointer (conforms_to) so integrity can resolve resource to spec to NDO deterministically, and check event.ndo_identity_hash against it.
  • Or record the limitation in the Decisions table the way ndo_state_hash was. The PR is admirably explicit about the state-hash limitation; this one is not mentioned at all.

R2. GovernanceRule.ndo_identity_hash is mutable on update.

ResourceSpecification gets explicit immutability checks for both ndo_identity_hash and ndo_state_hash in the UpdateEntry arm (dnas/nondominium/zomes/integrity/zome_resource/src/lib.rs:217 and :225). GovernanceRule gets none: validate_update_governance_rule delegates straight to create, which validates the classification against whatever new pointer the update names.

So a rule created under a Nondominium NDO can be updated to point at a Private NDO, with property_regime changed to match, and its rule_data swapped to TransferCondition{ Ownership }. Mirror the spec's immutability check.

R3. group_sweettest still gates nothing.

The new matrix runs cargo test --package nondominium_sweettest --test ${{ matrix.target }} over [misc, person, governance, resource, nondominium]. dnas/group/tests/Cargo.toml defines two more targets, group and ndo_anchor, and this PR adds ndo_anchor_round_trip_public_regime to the latter. Given the PR's stated point ("the primary backend suite per CLAUDE.md had never gated a merge"), the Group DNA suite should join the matrix.

R4. scope changes leak the global discovery anchor.

create_resource_specification skips the AllResourceSpecifications anchor for ResourceScope::Project. scope is mutable and update_resource_specification carries the new value through without reconciling the anchor. So Project widened to Public stays permanently invisible to get_all_resource_specifications, and Public narrowed to Project stays visible.

R5. Documentation mapping (REVIEW.md §6).

  • documentation/zomes/governance_zome.md documents LogEconomicEventInput (line 284) and ProposeCommitmentInput (line 343). Both gained a required ndo_identity_hash field, and two new externs landed in the same zome (evaluate_state_transition, check_action_constraints). The file is untouched in this diff.
  • documentation/TEST_COMMANDS.md is untouched although -- --test-threads 2 is now load-bearing (6 threads SIGTERMs the run) and the CI invocation changed shape.

R6. IMPLEMENTATION_STATUS.md is stale on the thing this PR ships.

Line 193 still reads:

Governance-as-Operator Architecture ❌ Specified, not implemented

... the Rust DNA does not currently define GovernanceTransitionRequest, TransitionContext, GovernanceTransitionResult, evaluate_state_transition, ...

This PR adds all four (crates/shared/src/io/governance.rs, zome_gouvernance/src/transition.rs). request_resource_transition is still absent, so the section is partial rather than complete, but the ❌ header and the flat denial are both wrong now. The file was edited (+286) here, so this is a miss rather than an untouched file.

R7. Description-to-diff gaps.

Three areas are in the diff but absent from ## Changes:

  • pai/human_ai_collaboration.md (+677) — an essay on AI cognition and cognitive offloading, unrelated to NDO Layer 1.
  • Artcoin.md at repo root (+53) — duplicates the framing already in documentation/Applications/nondominium_artcoin.md.
  • documentation/Applications/Healthnet.pdf — a 2 MB binary, no LFS.

Split them into their own PR or name them.


Suggestions

  • validate_action_against_ndo reads the genesis NDO record via must_get_valid_record(ndo_identity_hash), so lifecycle_stage in its ResourceClassification is the creation-time stage. This is precisely the bug fixed for validate_create_resource_spec. No predicate reads lifecycle_stage today, so it is latent, but a future lifecycle predicate would silently inherit the wrong stage. Pass None, or leave a comment saying why not.
  • create_governance_rule takes property_regime / resource_nature / rivalry_override from the caller, while create_resource_specification derives them from Layer 0. Derive in both, so honest clients cannot hit a spurious integrity rejection they have no way to diagnose.
  • get_resource_specification_with_rules fetches rules via get(original_hash) on the link target, so update_governance_rule results never surface. Relatedly, update_resource_specification links new rules to updated_spec_hash, which that reader is never called with.
  • dnas/group/tests/src/ndo_anchor/mod.rs mirrors rivalry_override as Option<String> where the real field is Option<Rivalry>. Only None is used today so it passes, and it will misencode silently the moment anyone sets it.
  • Worth a second thought on committing a 2 MB PDF to git without LFS.

Notes

The check_capture_resistance vs Source-NDO Consume collision flagged in the PR description is real and correctly deferred. R1 sharpens it: once extraction from a Nondominium or CommonPool source is legitimate (REQ-PROC-10, REQ-SOURCE-EVENT-01), the unbound ndo_identity_hash becomes the only thing standing between "recorded extraction" and "laundered transfer". Worth settling R1 before Source-NDO lands, not after.

Everything else holds up well:

  • resolve_ndo_state's backward chain walk is the right call, and the Decisions table is honest about what it cannot do rather than papering over it.
  • The CI sharding rationale (why no custom CARGO_TARGET_DIR, why shared-key, why --test-threads 2) is documented in the workflow itself, which is exactly where the next person will need it.
  • The four new Svelte components are clean Svelte 5 runes throughout, no $: anywhere.
  • Deriving GovernanceRuleType from the RuleData discriminant rather than storing it alongside is the right call, and the Decisions table gives the right reason.

@Soushi888

Copy link
Copy Markdown
Collaborator Author

Review verdict: APPROVE (Wave 2 — merge after #129, rebase first)

Deep review of the 98-file diff, focused on the four load-bearing surfaces.

Typed RuleData (shared crate) — clean. Tagged enum with four variants, GovernanceRuleType derived from the discriminant and never stored separately, ungated module so integrity zome, coordinator, and unit tests share one schema. UI mirrors in packages/shared-types/governance.types.ts.

Classification-coherence enforcement — this is the strongest part of the PR. validate_create_governance_rule resolves the referenced Layer 0 record with must_get_valid_record and rejects any GovernanceRule whose denormalized property_regime / resource_nature / rivalry_override diverges from it, before the constraint predicates run. Without that binding the classification is writer-controlled and every regime-driven constraint (capture resistance above all) becomes advisory — an ownership-transfer rule on a Nondominium NDO would pass by declaring Private. The Sweettest nondominium_ownership_transfer_not_bypassable_by_misdeclared_regime asserts exactly this attack is closed. must_get_valid_record inside validation also gets Holochain's dependency-wait semantics for free: a rule whose NDO record has not gossiped yet is held, not rejected.

Layer 1 activation gatevalidate_create_resource_spec gates on the stage read through ndo_state_hash (the observed state, update-chain walked) rather than the genesis record, with the right reasoning recorded: genesis always carries the creation-time stage, so gating on it would reject the ordinary "create at Ideation, advance, then activate" flow while accepting a since-Deprecated NDO. The state hash is also checked to belong to the claimed NDO. The UpdateEntry arm enforces immutability of both hashes, including the subtle one: an edit re-pointing ndo_state_hash at a newer eligible state would launder an activation the create-time gate rejected — that path is closed too, with coverage (resource_spec_rejected_at_ideation_stage, ..._allowed_after_advancing_out_of_ideation, ..._rejected_after_deprecation).

OperationalState splitEconomicResource.state: ResourceState becomes operational_state: OperationalState. This is a breaking entry-type change: old serialized entries will not decode as the new struct. Acceptable now (pre-production, no released pilot data, and #130 changes every DNA hash anyway), but it belongs in the release notes of whatever ships first after this lands. The lifecycle test economic_resource_operational_state_lifecycle covers the transition semantics.

One design question, non-blocking: gated_access_contradicts_permissionless_regime fires as Soft even under the Nondominium regime, so a Gated access rule on a Nondominium NDO warns but commits. REQ-RES-01 says permissionless access under defined governance rules, so a discretionary gate is at least in tension with the regime's spirit. Soft-severity here reads as a deliberate configurability choice (Hard would make the constraint unreachable for communities that want it) — worth one sentence in the ADR recording that it is intentional, so a future reader does not "fix" it to Hard.

Severity table for the record — ownership-transfer under uncapturable regime: Hard; under non-uncapturable non-transfer regimes: Soft; gated access on Nondominium: Soft; transport on non-physical nature: from check_transport_applicability. Consistent with the constraints module docs.

Merge mechanics: rebase onto post-#129 dev (base will have moved by two merges + the #137 CI gate), full matrix re-run expected green, then merge.

…fix)

Five conflicts, all resolved by keeping both sides rather than picking one:

- crates/shared/types.rs: #132's Rivalry/ResourceScope/OperationalState and
  dev's NdoDnaProperties are independent additions; both kept.
- zome_resource integrity: kept #132's regime-semantics hook AND dev's ADR-013
  binding check. Dropping the latter would silently remove the guarantee that a
  GovernanceRule's classification cannot diverge from Layer 0.
- ndo_anchor tests: took dev's shared-crate import (#128 deliberately replaced
  the hand-kept mirror, which had already drifted on `initiator`), then
  re-applied #132's additions on top: rivalry_override on the NdoInput/NdoEntry
  mirrors and the Public-regime anchor test. That test was written against the
  old mirror, so it needed adapting: NdoDnaProperties has no `initiator` field,
  and anchor_input_from now takes the initiator as a separate argument.
- ndo.service.ts: took dev's side. #132's mapListingToDescriptor is dead under
  the anchor model (zero callers) and its identityToDescriptor was a duplicate
  definition. rivalry_override survives in the retained field mapper.
- IMPLEMENTATION_STATUS.md: neither side was accurate. Arbitrated against the
  code: 20 externs, and no get_all_groups.
@Soushi888

Copy link
Copy Markdown
Collaborator Author

Rebased onto dev (now carrying #137, #128, #138)

Merged rather than rebased: this branch is 10 commits and the conflicts repeat across most of them, so a merge resolves each one once and the squash-merge flattens it anyway. Five conflicts, all resolved by keeping both sides rather than picking a winner. Full reasoning is in the merge commit message; the two that matter for review:

The ADR-013 binding was at risk. validate_create_nondominium_identity had a conflict between this branch's regime-semantics hook and dev's ADR-013 check (entry classification must match the NDO cell's DNA properties). Taking either side alone would have compiled fine. Taking dev's alone would have dropped the Phase A hook; taking this branch's alone would have silently removed #128's classification binding — the guarantee that an NDO's entry cannot diverge from the DnaHash it was cloned under. Both are kept.

The ndo_anchor test mirror had already drifted. #128 deliberately replaced the hand-kept local enum mirror in that test with the shared-crate definition, precisely because the mirror had drifted once on initiator. This branch still carried the old mirror plus its own additions. Resolution: take dev's shared import, then re-apply this branch's additions on top (rivalry_override on the NdoInput/NdoEntry mirrors, and ndo_anchor_round_trip_public_regime). That test then failed to compile against the current types — NdoDnaProperties has no initiator field, and anchor_input_from now takes the initiator as a separate argument — and was adapted. Caught by a local cargo check, before CI.

One doc claim was wrong on both sides. IMPLEMENTATION_STATUS.md conflicted on the group coordinator API: this branch said 16 externs with no get_all_groups; dev said a different list that included get_all_groups. Neither matches the code. Arbitrated against source: 20 externs, and get_all_groups does not exist. Corrected to that.

Note on the seven-variant PropertyRegime

This branch inserts Public before Nondominium. That ordering would matter if clone properties were index-encoded, since it would shift Nondominium and change the DnaHash of every existing Nondominium NDO cell. They are not: createNdoCloneCell sends a plain JS object, holochain 0.6.0 transports it as YamlProperties, and serde encodes these unit variants by name. So existing cells are unaffected. The empirical check is that same_coordinates_derive_same_dna_hash and create_ndo_rejected_when_name_diverges_from_properties still pass on this merge; the CI run on this push is that check.

Local verification before push

  • WASM zomes: cargo check --release --target wasm32-unknown-unknown clean (warnings pre-existing).
  • cargo check --package group_sweettest --tests clean after the test fix above.

Full matrix (5 Sweettest shards + e2e) is running on this push.

Layer 1 activation puts the word "Specification" on the NDO detail page in
more than one place: the identity panel's lifecycle stage, the Layer 1
specification panel, and its create modal. The multi-agent live-read test
asserted `getByText('Specification', { exact: true })`, which became a
Playwright strict-mode violation the moment Layer 1 rendered.

Add `data-testid="ndo-lifecycle-stage"` to the stage field and assert on
that. A structural selector would have worked too, but it would break again
the next time the panel is restyled; the test id says what the test means.

Local e2e: 19 passed, including the previously failing case.
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.

feat(resource): NDO Layer 1 - typed governance rules, classification constraints, OperationalState

2 participants