feat(resource)!: NDO Layer 1 typed governance rules, classification constraints, and OperationalState - #132
Conversation
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.
Review: REQUEST CHANGESReviewed at
Request changesR1. Layer 2 capture resistance is still self-declared.
The new test demonstrates the gap rather than closing it: 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:
R2.
So a rule created under a Nondominium NDO can be updated to point at a R3. The new matrix runs R4.
R5. Documentation mapping (
R6. Line 193 still reads:
This PR adds all four ( R7. Description-to-diff gaps. Three areas are in the diff but absent from
Split them into their own PR or name them. Suggestions
NotesThe Everything else holds up well:
|
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 Classification-coherence enforcement — this is the strongest part of the PR. Layer 1 activation gate —
One design question, non-blocking: 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 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.
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. The One doc claim was wrong on both sides. Note on the seven-variant
|
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.
PR #128 moved every NDO into its own cloned `ndo` cell, but the Layer 1 and Layer 2 UI kept calling `zome_resource` and `zome_gouvernance` on the shared provisioned `nondominium` cell, where the NDO identity was never written. Creating a resource specification therefore failed with `Guest("Entry operation failed: Linked NondominiumIdentity not found")` from `resource_specification.rs:53`, and every NDO-scoped read returned nothing. `NdoService.resolveCellIdForNdo` resolves the NDO's clone cell from its anchor coordinates, provisioning it for a peer who never joined, and returns null for legacy NDOs still living in the shared cell so those keep working. `NdoView` resolves it once per NDO and passes `ndoCellId` to the resources, governance and activity tabs and to the four modals below them. An optional `cellId` threads through `zome-helpers` into both zome services and both stores; every call that does not pass one keeps its previous role-name routing. Two further defects surfaced while verifying this in the browser: - The governance tab's "+ New rule" handler carried a second, unrouted `fetchSpecificationsForNdo`. It returned nothing, so the rule was written with no `specification_hash` and no read path could surface it again. Routed, and guarded so the editor refuses to open when the NDO has no Layer 1 spec. - `anchorToDescriptor` omitted `rivalry_override`, leaving `bun run check` red on the branch. The anchor caches only the card fields, so it is null there and the live read on open supplies the real value. Verified in real Chrome against the 2-agent dev network: a specification and a typed AccessRequirement rule both create and read back. A probe through the app's own client confirms placement, `get_specifications_for_ndo` returning 0 for the shared cell, 0 for the provisioned `ndo` cell and 1 for the NDO's own clone. `bun run check` reports 0 errors and 0 warnings.
Resolves both conflicts by keeping #129's membership additions and the round 1 cell-routing fix together, since neither replaces the other: - NdoView.svelte: the `membersStubMessage` placeholder is gone (membership is real now, so `membersError` carries the failure), and `ndoCellId` stays, because every Layer 1 and Layer 2 tab still needs the NDO's own clone cell. - ndo.service.ts: `resolveCellIdForNdo` keeps its interface entry and its implementation alongside the real `joinNdo` / `getNdoMembers`, which now call `join_ndo`, `is_ndo_member`, and `get_ndo_members` on the ndo cell.
Every Layer 2 read path deserialized the fetched record with
`to_app_option::<EntryTypes>()`. A stored entry is the bare inner struct,
not an externally tagged enum, so that deserialization can never succeed.
Each site then pattern-matched the result inside `if let Ok(Some(..))`,
which discards both the `Err` and the `None`, so the failure surfaced as
an empty vector rather than an error. `propose_commitment` returned ok and
`get_all_commitments` returned 0 for the same commitment, in the same cell,
in the same session: Layer 2 was write-only and said nothing about it.
Read each record as the concrete entry type it holds. Nine sites, all in
zome_gouvernance, all the same one-line correction:
- commitment.rs: get_all_commitments, claim_commitment, get_all_claims,
get_claims_for_commitment
- economic_event.rs: get_all_economic_events, get_events_for_resource
- validation.rs: get_validation_history, get_all_validation_receipts,
check_validation_status
- ppr.rs: extract_private_participation_claim
claim_commitment is worth calling out: it took the same failure through a
`match` and returned "Invalid commitment entry", so it could never claim
any commitment at all.
The anchor paths are not at fault. `Path::from("all_commitments")` is never
`.ensure()`d, but neither is `Path::from("ndo_identities")`, and NDO
discovery has always worked. Confirmed by experiment: fixing only
get_all_commitments turned that test green while the untouched
economic-event test stayed red, and inserting `await_consistency_20_s`
before the read changed nothing.
Tests: two Sweettests asserting an author can read back what it just wrote,
red on the previous head (0 != 1) and green now.
The UI called `zome_resource.get_ndo_transition_history` on every NDO open. The function did not exist anywhere in the Rust tree, the console logged "Attempted to call a zome function that doesn't exist", and the service caught it into `[]`, so the panel read "Lifecycle history - 0 transitions" forever. REQ-UI-NDO-04 was documented as implemented and was not. Implement it over the NondominiumIdentity update chain. `update_lifecycle_stage` is the only writer of updates, and integrity validation rejects any update that touches a field other than lifecycle_stage, successor_ndo_hash, or hibernation_origin, so the chain IS the history: no separate log entry to write, and none that can drift from the entry it describes. Each step reports the stage it came from, the stage it went to, and the author and timestamp of the Update that recorded it. Traversal follows the most recent update at each step, the same rule resolve_latest_ndo_record uses, so the history can never disagree with the stage get_ndo displays. event_hash is the ActionHash of the Update action, which always exists and identifies the transition precisely. It is deliberately not the triggering EconomicEvent: transition_event_hash is optional, is null throughout the MVP, and its NdoToTransitionEvent links hang off the NDO's original action hash with nothing tying a given link to a given step, so attributing one to a specific transition would be a guess. When REQ-NDO-LC-03 makes event generation automatic, that hash gets its own field. UI: stop catching the call into an empty array, and distinguish "no transitions yet" from "the read failed" in the panel. Collapsing those two into one message is what let a missing zome function look like ordinary empty state. Tests: three Sweettests covering an untransitioned NDO, a three-step forward chain, and hibernate-then-resume. All three failed on the previous head with ZomeFnNotExists and pass now.
A Nondominium NDO is uncapturable by design and a Public one is open access
by the stewarding body's policy, but a Layer 1 spec scoped to Project or
Network is omitted from the global discovery anchor. The resource stays
unownable while becoming invisible to everyone outside the narrowing group.
That is enclosure by visibility, which is the outcome REQ-RES-03 exists to
prevent, so the predicate is Hard rather than advisory.
The predicate already had no call site anywhere outside constraints.rs, and
SpecificationCreateModal defaults scope to Project, so the invalid value was
what the default path produced. A red run against the ungated tree committed
ResourceSpecification { scope: "Project" } on a Nondominium NDO and returned
a real spec_hash.
Enforced on update as well as create: scope is mutable, so a create-only gate
is laundered by one edit. The UpdateEntry arm already freezes ndo_identity_hash
and ndo_state_hash, so the NDO reached on update is the one the create gate
judged.
The UI locks rather than warns: the scope select is fixed to Public and
disabled on an open-access NDO, with the reason shown in place of the generic
hint. propertyRegime threads from NdoView through ResourcesTab to the modal.
Tests: 4 Sweettests in the resource target (two negative, two positive) and
5 unit tests on the predicate. Red 13 passed / 2 failed, green 15 passed / 0
failed. Both positives pass in both runs: this gates enclosure without
blocking Layer 1 activation or touching regimes where scope and regime are
legitimately orthogonal.
The lifecycle history panel rendered `agent` and `event_hash` as comma-separated decimal bytes (`132,32,36,253,73,...`) because it called `.slice(0, 10)` on values the conductor returns as Uint8Array. `.slice` on a typed array returns a typed array, and Svelte stringifies that as a byte list. It was the only surface in the app showing a Holochain hash that way. The copy affordance made it more than cosmetic: the clipboard received the byte-array string rather than an action hash, so REQ-UI-NDO-04's copy-to- clipboard yielded something that cannot be pasted anywhere expecting a hash. Root cause is the type, not the component. `NdoTransitionHistoryEvent` declared `agent: string` and `event_hash: string`, which is false at runtime and made `.slice(0, 10)` typecheck as a string slice. Corrected to AgentPubKey and ActionHash so the compiler stops endorsing the mistake, with the reason recorded on the fields. Verified red then green on the type: with the corrected type and the old component, `bun run check` reports `Argument of type 'HoloHash' is not assignable to parameter of type 'string'` on the clipboard call. Note the compiler catches the copy defect but not the render defect, since `.slice` on a Uint8Array is legal; the render fix is not compiler-enforced and stays a convention shared with ActivityTab and CommitmentCreateForm.
PM review, round 2: APPROVEReviewed against the round 2 briefs for B0 through B3 plus the findings that came out of dogfooding this branch in a real browser. Four commits landed since round 1: Re-derivedEvery claim below was produced by this reviewer, not read off a report.
Taken on trust, and whyB1's economic-event half. The Log event form requires a resource action hash and nothing in the app can create an The finding behind the findingsThree defects this round lived at a boundary the Sweettests structurally cannot see, because they assert what the zome returns and stop there: the cell-routing bug, the swallowed call to a function that did not exist, and a panel rendering Two diagnoses were also wrong and were corrected by evidence rather than argument: the anchor BlockingNone. Non-blocking, deferred past merge
State
|
Correction to the round 2 verdict: not mergeable yetThe APPROVE above was posted while the The failure is B2's own stale testtest('lifecycle history panel present (backend rows pending)', async () => {
// TODO(backend Phase 2.3): `get_ndo_transition_history` is not implemented
// in zome_resource on dev — assert the explicit stub state today, and
// replace with a from→to row assertion when the backend lands.
await expect(page.getByText('No transitions recorded.')).toBeVisible();
});The backend landed in Why this belongs in the coverage finding rather than beside itThis is the fourth instance of the same pattern, and the sharpest yet. A criterion written for a world where the feature was missing, left asserting its absence after the feature arrived. Every Rust gate passed while a browser test naming the exact function that was implemented went red. The other three were the cell-routing bug, the swallowed call, and the type that vouched for the wrong value. Also worth a look, not blocking
StateBlocking until |
…placed `lifecycle history panel present (backend rows pending)` asserted `No transitions recorded.`, the copy shown when get_ndo_transition_history did not exist. d7b7197 implemented the function and replaced that copy with "No transitions recorded yet. This NDO is still at the stage it was created in.", so the substring no longer occurs anywhere in the DOM and the test went red the moment the feature it named arrived. Its own TODO asked for this replacement. The reload is load-bearing, not defensive. TransitionHistoryPanel fetches onMount only, and the previous test transitions an already-mounted page, so the row is on the DHT and absent from component state. Asserting the row without reloading fails. Two assertions beyond the row cover F9: the row text must contain a base64 hash prefix and must not contain a decimal byte-list. Neither the compiler nor any Sweettest can catch that regression. `.slice` on a Uint8Array is legal TypeScript, and the zome was always returning the correct bytes; only a rendered assertion sees it. Verified red then green on the full file rather than in isolation, since the suite is serial and shares one page. With the render reverted to raw `.slice()`: 1 failed, 10 passed, failing on Expected pattern: /uhC/ Received string: "Ideation → Specification By 132,32,36,50,154,19,249,16,75,207… …" With the fix in place: 13 passed.
PM review, round 2 final: APPROVE, ready to merge
What closed since the correction
It also adds two assertions that were not asked for: the row text must match The coverage finding, restated for the recordFive defects this round lived where the existing tests structurally cannot look. Four were green results proving less than they appeared to: a passing suite over a cell-routing bug, a swallowed call to a function that did not exist, a passing typecheck over a shared type that declared The fix is a class of test rather than five patches, and Deferred past merge
All three are on the board in Backlog. Merge is @Soushi888's call. |
Layer 1 shipped with 7/7 green CI and was broken end to end in a browser: every Layer 1 and Layer 2 call went to the shared `nondominium` cell while the NDO identity lived on its own clone (b898f41). Sweettest could not see it, because it creates the NDO and the spec in the same cell, and no e2e touched Layer 1 at all. The suite closed a test criterion and nothing else. Three tests, each written so the load-bearing claim is one only a two-cell browser test can make: - a specification created through the UI lands on the NDO's own clone cell and NOT on the shared one. The NdoToSpecification link is what `get_specifications_for_ndo` traverses, so a non-empty result is the Layer 1 activation edge itself (REQ-NDO-L1-01). - a typed AccessRequirement rule is reachable *through* the spec's SpecificationToGovernanceRule link, which is the only assertion that distinguishes an attached rule from the silent write-only orphan the unrouted read produced. Asserts the RuleData discriminant, so a stringly-typed regression surfaces here. - an open-access regime fixes the scope select to Public and disables it, and the value that reaches the DHT is Public (REQ-RES-03). The DOM assertion is the affordance; the read-back is the claim, because the UI lock derives from the cached anchor regime and fails open on a cold cache. Each was demonstrated red by reverting the behaviour it guards, one at a time: routing the modal to the shared cell leaves the create dialog open on "Linked NondominiumIdentity not found"; dropping the specification hash writes a rule that never renders; forcing the scope lock false leaves the select enabled. Green again with all three restored: 23/23 across both spec files, +7s of test time. Also returns the clone cell alongside the entry from the NDO locator, so every Layer 1 assertion names the cell it read from rather than asserting mere existence.
Review round 3: APPROVECI 7/7 on What closed since round 2Layer 1 had no end-to-end coverage. All eighteen e2e tests passed and the eleven "Specification" matches in them were the lifecycle stage, not the Layer 1 entry. That mattered because the round 1 blocker, the whole Layer 1 and Layer 2 UI calling the shared cell instead of the NDO's clone, was found by hand, fixed by hand, and guarded by nothing. With an optional
23/23 locally and in CI, first try, no retries. About 29 seconds of added e2e time. Merging with these open, deliberately
Doc drift for the sync PR (#131)
On authorshipTibi's six commits are intact and were checked rather than assumed: |
Merging nowDescription refreshed against the actual diff before merge. What changed in it:
On the squashMerging with GitHub's squash, per Green end to end: 7/7 on |
Issue #135 sequenced this pass behind PR #132: Layer 1 changed what these documents describe, so the harmonization pass had to land after it and then correct the sections it invalidated. Every claim below was checked against the merged source, not against another document. Contradictions corrected. IMPLEMENTATION_STATUS said governance-as-operator was "specified, not implemented" and named six types and functions as absent; three of the types and `evaluate_state_transition` now exist in `crates/shared/src/io/governance.rs` and `zome_gouvernance/src/transition.rs`. Only `request_resource_transition` and `evaluate_governance_transition` are still missing, which makes the evaluation path parallel and advisory rather than the mandatory funnel, and that is now what the document says. It also claimed GovernanceRule semantics are not evaluated, while `constraints.rs` enforces classification predicates Hard at integrity and Soft as advisory; the row is split into the part that is enforced and the part that is not. ndo_prima_materia had Layer 1 marked not-started in four places. `NdoToSpecification` is in `LinkTypes` and is created on every `create_resource_specification`, so REQ-NDO-L1-01 is closed, the status matrix and §8.4 link tables are corrected, and Pattern 2 no longer says activation is unconstrained by stage. REQ-NDO-L1-02 said Layer 1 may activate at or after `Ideation`; the integrity gate rejects `Ideation` and accepts `Specification` through `Active`, matching §5.2's own activation table, so the requirement now says that. Struct listings regenerated from source in resources.md, zomes/resource_zome.md, zomes/governance_zome.md, API_REFERENCE.md and specifications.md. `ResourceSpecification` gained `scope`, `ndo_identity_hash` and `ndo_state_hash`; `GovernanceRule` replaced the `rule_type`/`rule_data` string pair with the typed `RuleData` enum plus three denormalized classification fields; `EconomicEvent` and `Commitment` gained `ndo_identity_hash`, as did `LogEconomicEventInput` and `ProposeCommitmentInput`. `created_by` and `created_at` were documented on entries that never had them. The API reference's `ResourceSpecificationInput` and `GovernanceRuleInput` blocks described a schema with no counterpart in code at all. The three governance design documents keep their target architecture but now open with a status banner saying which half of it exists, so a reader cannot mistake `request_resource_transition` for a callable function. The same correction lands in `pai/cursor-rules/20-architecture.md`, which is always-loaded agent context and taught the wrong call chain. SUMMARY.md now lists every document in the tree. The Source-NDO set, the Phase B design record, ADR-010-013, the hREA release plan and two Applications pages were unreachable from the mdBook TOC. Two items the previous commit deferred are closed by #132 rather than here: `PropertyRegime` is seven variants in both Rust and TypeScript, and `complete-resource-specification.md` is no longer empty. Link integrity: 105 documents, 1 broken, the known em-dash anchor that resolves on GitHub.
pai/human_ai_collaboration.md arrived from #132 at the pai/ root. It reads as harness-agnostic, so pai/shared/ is the likely home, but it is Tibi's document and moving it as a side effect of this refactor is his call to make, not mine.
It arrived from #132 at the pai/ root, which the new structure does not use. The content is harness-agnostic reference material, so it classifies as shared by the same test as everything else there: its content would not change if we dropped support for a tool. Move only, no edits to the document. Nothing referenced the old path except the README note now replaced by the listing. Authorised by @Soushi888.
It arrived from #132 at the pai/ root, which the new structure does not use. The content is harness-agnostic reference material, so it classifies as shared by the same test as everything else there: its content would not change if we dropped support for a tool. Move only, no edits to the document. Nothing referenced the old path except the README note now replaced by the listing. Authorised by @Soushi888.
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 untypedGovernanceRulepayload, and theResourceStateenum that conflated lifecycle maturity with operational condition.The result is that a
ResourceSpecificationnow points at the Layer 0NondominiumIdentityit 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)rule_datamodule:RuleDatatagged enum with four variants (AccessRequirement,UsageLimit,TransferCondition,MaintenanceSchedule).GovernanceRuleTypeis derived from the discriminant viaRuleData::rule_type(), never stored separately.constraintsmodule: pure, hdk-free predicates over aResourceClassification(nature, regime, lifecycle, rivalry override). EmitsConstraintViolation { rule_id, message, severity }withHard/Softseverity. Current rules:nondominium_no_unilateral_capture,ownership_transfer_not_permitted_by_regime,gated_access_contradicts_permissionless_regime,no_transport_for_non_physical_nature.types:Rivalry(withResourceNature::default_rivalry()),ResourceScope(Project/Network/Public),OperationalState(Available,Reserved,InTransit,InStorage,InMaintenance,InUse,PendingValidation). NewPropertyRegimepredicates:is_rivalrous,permits_ownership_transfer,is_uncapturable,default_accessibility.Zomes
ResourceSpecificationgainsscope,ndo_identity_hash(the immutable, stable Layer 0 pointer), andndo_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.GovernanceRulereplacesrule_type: String+rule_data: Stringwith a typedRuleData, plusndo_identity_hashand denormalizedproperty_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: ResourceStatebecomesoperational_state: OperationalState;ResourceStateis deleted.ResourcesByStatebecomesResourcesByOperationalState; lifecycle faceting stays on Layer 0'sNdoByLifecycleStage.NdoToSpecificationlink type: the Layer 0 to Layer 1 activation edge.zome_gouvernance: newtransitionmodule, plus economic-event and commitment updates carrying the constraint context.UI
SpecificationCreateModal,RuleEditorModal(typed rule authoring),CommitmentCreateForm,EconomicEventCreateForm.operational-state-labels.ts,rivalry.ts. Governance / resource services, stores, and schemas updated for the new shapes.GroupServicestub replaced with a realcallZomeservice layer for the Group DNA.packages/shared-typescarries the wire contract for all of the above:RuleDataand its four variants,ResourceScope,Rivalry,OperationalState, and the newResourceSpecificationfields (+194 lines acrossresource.types.tsandgovernance.types.ts).CI
sweettestjob:cargo test -p nondominium_sharedplus all five Sweettest targets, sharded one job per[[test]]target with a sharedrust-cachekey.e2eis now gated behind it, as the workflow's own comment had asked for. Before this PR, the primary backend suite perCLAUDE.mdhad never gated a merge.Tests
Sweettest coverage extended in
resource,governance,ndo_layer0, and the groupndo_anchorsuite (about 800 added lines across the four), plus six negative tests added during review.Three e2e tests in
core-flows.spec.tscover Layer 1 in a real browser: specification creation on the NDO's own clone cell, a typed governance rule reachable through the spec link, and the open-access scope lock. The suite goes from 18 to 21 tests, 23 includingmulti-agent.spec.ts.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 thevalueflows-dsl.md/vf:Sourcedesign notes.Review follow-ups
Fourteen commits on top of Tibi's six, across three review rounds. Every behavioural fix below was demonstrated red before its fix landed.
Round 1 — integrity (
09224eb,923b3d8,74532aa)1. Layer 0 classification was writer-controlled (
09224eb)validate_create_governance_rulebuilt itsResourceClassificationfromrule.property_regime/resource_nature/rivalry_override, all supplied by the caller, and never checked them against the referencedndo_identity_hash. ATransferCondition{Ownership}rule on aNondominiumNDO passed validation simply by declaringproperty_regime: Private. The capture-resistance guarantee (REQ-RES-03) was self-declared.Integrity now reads the referenced
NondominiumIdentityand 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_ruledelegates to create, so updates are covered.2. The Layer 1 lifecycle gate read the wrong stage (
923b3d8)validate_create_resource_speccalledmust_get_valid_record(ndo_identity_hash), which returns the genesis record.lifecycle_stagemutates through the update chain, so the gate judged every activation against the creation-time stage: an NDO created atIdeationand advanced toSpecificationcould never grow a spec (the primary intended flow), while one created atActiveand sinceDeprecatedstill could.ResourceSpecificationnow carriesndo_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 viaoriginal_action_address, and that edge never changes.resolve_ndo_statereads the observed entry, walks back to the genesis Create, and proves the root equalsndo_identity_hashbefore gating on the stage. Capped at 64 hops. The coordinator derives the field viaresolve_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_transferlooked 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 incore-flows.spec.tscreated agroupclone cell and never removed it; the UI enumerates group clone cells offappInfo, so the leftover cell rendered as a real group,hasGroupsbecame true, and the onboarding CTA never mounted. Fixed withdisableCloneCell+admin.deleteCloneCellin afinallyblock.expectEmptyLobbynow 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.Round 2 — the feature was broken end to end while CI was green (
b898f41,2e3129a,d7b7197,f57ef43,b3bf2ae,ca3f6d6)Verified in a real browser against the live two-agent network, which is how all five of these were found. CI was 7/7 green throughout.
5. Layer 1 and Layer 2 called the wrong cell (
b898f41). PR #128 moved every NDO into its own clonedndocell. The whole Layer 1 and Layer 2 UI still calledzome_resource/zome_gouvernanceon the shared provisionednondominiumcell, where the NDO identity was never written, and failed withLinked NondominiumIdentity not found.NdoService.resolveCellIdForNdonow resolves the NDO's clone from its anchor coordinates (provisioning it for a peer who never joined) and an optionalcellIdthreads through to every NDO tab and modal. The same commit routesGovernanceTab's second, unrouted spec fetch, which was writing rules with nospecification_hash: committed fine, unreachable by every read path, a silent write-only rule.6. Layer 2 was write-only (
2e3129a).propose_commitmentreturned ok andget_all_commitmentsreturned zero in the same cell and session. Cause:record.entry().to_app_option::<EntryTypes>()deserializes into the enum wrapper rather than the inner struct, so the match arm never fired, every record was skipped, and the read returned zero with no error anywhere. Ten sites acrosscommitment.rs,validation.rs,economic_event.rsandppr.rs; zeroEntryTypesreaders remain underdnas/orcrates/. An earlier hypothesis (an un-ensure()d anchor path) was falsified by experiment: fixing one read left a second untouched test red.7.
get_ndo_transition_historydid not exist (d7b7197). The UI called it on every NDO open; the function was absent from the entire Rust tree and the service swallowed the error, so the panel read "0 transitions" forever and REQ-UI-NDO-04 was documented as implemented. Implemented over the Layer 0 update chain, and a failed read now renders "unavailable" rather than disguising itself as empty state.8. Open regimes imply Public scope (
f57ef43, REQ-RES-03). A spec on aNondominiumNDO was accepted withscope: Project, which omits it from the global discovery anchor: the resource stays unownable while becoming invisible to everyone outside the narrowing group. That is enclosure by visibility.check_scope_coherenceis a Hard predicate enforced on create and update (a create-only gate is laundered by one edit), and the form no longer offers the invalid choice. Negatives and positives both tested:Publicon Nondominium andProjecton Commons still pass, so it is a gate, not a blanket refusal.9. Transition history rendered raw byte arrays (
b3bf2ae). The panel showedBy 132,32,36,253,…where the rest of the app showsuhCAk…, and the copy affordance put the byte string on the clipboard. Root cause was a type declaringagent: stringwhen it isUint8Arrayat runtime, which is why.slicetypechecked andbun run checkstayed green over the defect.Round 3 — e2e coverage for the headline feature (
39accc7)Rounds 1 and 2 make the same point twice: a green suite closed a test criterion and nothing else. Sweettest creates the NDO and the spec in the same cell, so it cannot observe a cell-routing bug, and no e2e touched Layer 1 at all. Three tests now do, each written so the load-bearing claim is one only a two-cell browser test can make:
get_specifications_for_ndotraversesNdoToSpecification, so a non-empty result is the Layer 1 activation edge itself (REQ-NDO-L1-01).AccessRequirementrule is reachable through the spec'sSpecificationToGovernanceRulelink, the only assertion that distinguishes an attached rule from an orphan. Asserts theRuleDatadiscriminant, so a stringly-typed regression surfaces here.Publicand disables it, and the value that reaches the DHT isPublic. The DOM assertion is the affordance; the read-back is the claim, because the UI lock derives from the cached anchor regime and fails open on a cold cache.Each was demonstrated red by reverting the behaviour it guards, one at a time. 23/23 across both spec files, +7s of test time.
Decisions
rule_dataas a JSON stringGovernanceRuleTypemigration called for inndo_prima_materia.md.GovernanceRuleTypealongside the payloadRuleDatadiscriminant makes the mismatch unrepresentable.ResourceStatewith more variantsLifecycleStage(Layer 0, maturity) andOperationalState(instance, current process) now move independently, per REQ-NDO-OS-06.Hardblocks at integrity;Softsurfaces as coordinator and UI advice.rivalrya required stored fieldResourceNature::default_rivalry()with an immutablerivalry_overridefor the exceptions (for example a rivalrous Service slot).Read Layer 0 during rule validation(reversed in review)validate_create_resource_specalready 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.EconomicEvent/Commitmentto their NDO through the resourcevalidate_action_against_ndojudges the action against whateverndo_identity_hashthe writer supplies, and nothing binds it toresource_inventoried_as;EconomicResourcecarries no spec or NDO field, and integrity cannot follow a link deterministically. Accepted limitation: the Layer 2 capture-resistance gate is writer-controlled in the same way Layer 1's was before09224eb. Closing it means givingEconomicResourcean NDO pointer, which is its own PR.zome_gouvernance'sOpEntry::UpdateEntryarm guardsNdoHardLinkandAgreementand falls through for the other seven entry types, so a create-validated event can be updated past the predicate, and aValidationReceipt.approvedcan be flipped after the fact. This is pre-existing and byte-identical ondev, not introduced here, and the fix splits by fact versus plan (events, receipts and claims immutable; commitments re-validated with frozen identifying fields), which is five or more red-then-green pairs on a suite whoseresourceshard already runs 19 minutes. Follow-up issue.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: ResourceStatebecomesoperational_state: OperationalState.ResourceStateis removed.GovernanceRule.rule_type/rule_datastrings become a single typedRuleData, plus three required classification fields, which integrity now checks against Layer 0.ResourceSpecificationgains requiredndo_identity_hash,ndo_state_hash, andscope.LinkTypes::ResourcesByStatebecomesResourcesByOperationalState;NdoToSpecificationis added.How to test
Verified: all five Sweettest targets green locally at
--test-threads 2; the full e2e suite green locally at 23/23 across both spec files. CI 7/7 green on39accc7(run 33276486740):build, five Sweettest shards, ande2eat 23 passed with no retries. The three new Layer 1 tests were each confirmed present and passing in the CI e2e log rather than inferred from the job's status.Documentation
Also in the diff, from Tibi's own commits and listed here so nothing in the changed-file list is undescribed:
pai/human_ai_collaboration.md(a research note on AI cognition and cognitive offloading),pai/cursor-rules/10-domain-enums.md, rootArtcoin.md,documentation/Applications/(ArtCoin and Healthnet application docs, including a 2 MB PDF), the ArtCoin user stories, anddocumentation/hREA/valueflows-1.0-compliance.md.The deprecated Tryorama suite under
tests/was updated to the new entry shapes so it still compiles; it remains deprecated pertests/DEPRECATED.mdand gates nothing.Extensive. See the Documentation subsection under Changes for the full list of added and updated files.
Related
Related issues:
NdoToSpecification. Layer 2 (NdoToProcess) keeps the epic open.GovernanceTransitionRequest,GovernanceTransitionResult,TransitionContext,EconomicResourceView, andNdoClassificationViewland incrates/shared/src/io/governance.rs.crates/shared/src/constraints.rsis the pure, hdk-free predicate engine, shared verbatim by integrity validation, the coordinator, and the UI dry-run path.sweettestCI job, gatese2ebehind it, fixes the leaked clone cell that took out 8 of 18 e2e tests, and adds six negative Sweettests plus three Layer 1 e2e tests. Thegroup_sweettestpackage is still outside the CI matrix, so test(v0.1.0): harden e2e and Sweettest coverage for the peer-mesh release #127 stays open.evaluate_state_transitionnow exists, but the existing write paths stay parallel to it. The refactor itself is out of scope here.NdoToSpecification,ResourcesByOperationalState) with no CreateLink/DeleteLink validation, widening security(integrity): add per-LinkType validation for CreateLink/DeleteLink across all three zomes #85's surface.LifecycleStage/OperationalStatesplit) and theGovernanceRuleTypemigration inndo_prima_materia.mddocs/sync-harmonize) waits on this oneFollow-ups filed rather than fixed here, so the deferrals are visible:
zome_gouvernanceintegrity validates neither the update path (seven of nine entry types fall throughOpEntry::UpdateEntry, which is pre-existing and byte-identical ondev) nor the binding between anEconomicEvent'sndo_identity_hashand itsresource_inventoried_as. The second is the same writer-controlled-classification shape09224ebfixed at Layer 1, one layer up; it needs an NDO pointer onEconomicResource, which is a DHT-breaking entry change. Both recorded in the Decisions table above.scopenever reconciles theAllResourceSpecificationsanchor, so widening stays invisible and narrowing stays visible. Not a capture-resistance hole: uncapturable regimes are pinned toPublicon create and update.EconomicResource, so Layer 2 events cannot be authored through the app; and the scope lock derives from the cached anchor regime, so it fails open on a cold cache. The integrity gate is the real enforcement, the lock is an affordance.resourceSweettest shard at 18m53s, after one run that hit the 60-minute job timeout.Deferred, deliberately out of scope here:
NdoToProcess), and wiring the constraint predicates intoevaluate_transition.zome_gouvernance/src/transition.rs:7carries a liveTODO(§5 item 2)on whether existing write paths should funnel throughevaluate_state_transition.check_capture_resistanceHard-blocksConsumeonNondominium, but the Source-NDO requirements this same PR documents mandate recording extraction fromNondominium/CommonPoolsources (REQ-PROC-10, REQ-SOURCE-EVENT-01). One of the two has to give.Closes #136