From 9dded803025b9e41bdccdea0930eb23d4b40dbfe Mon Sep 17 00:00:00 2001 From: Dan Enman Date: Mon, 14 Sep 2026 11:30:19 -0300 Subject: [PATCH] docs(adr-0007): document retained parent ancestry across implementations Records the now-implemented rule in dwn/protocols.md and enbox/records.md, adds conformance checklist items, and updates ADR 0007 to reflect that Enbox and Rust both resolve structural ancestry from the retained initial write (soft delete preserves ancestry; prune removes it and is terminal). References dwn-spec#70. --- conformance/protocols-and-authorization.md | 3 +++ decisions/0007-retained-parent-ancestry.md | 17 +++++++++-------- dwn/protocols.md | 14 ++++++++++++++ enbox/records.md | 15 +++++++++++++++ implementation/dependency-resolution.md | 9 +++++---- 5 files changed, 46 insertions(+), 12 deletions(-) diff --git a/conformance/protocols-and-authorization.md b/conformance/protocols-and-authorization.md index f08afe1..9e61795 100644 --- a/conformance/protocols-and-authorization.md +++ b/conformance/protocols-and-authorization.md @@ -14,6 +14,8 @@ Use with `dwn/protocols.md` and `dwn/authorization.md`. - [ ] Structure paths and rule sets are resolved deterministically. - [ ] Record writes must match the declared protocol/path structure. - [ ] Cross-context parent/path violations are rejected. +- [ ] A parent-bearing write resolves its parent from the retained initial write; a soft-deleted parent still satisfies ancestry. +- [ ] A child of a pruned parent is rejected terminally, not as a retryable missing dependency. - [ ] Unsupported or malformed protocol definitions do not become active authorization policy. ## Actor semantics @@ -38,4 +40,5 @@ Use with `dwn/protocols.md` and `dwn/authorization.md`. - [ ] Wrong protocol path is rejected. - [ ] Wrong context/parent is rejected. - [ ] Role from another context does not leak authority. +- [ ] Parent-bearing child admission is identical across arrival permutations (parent-first, child-first, soft-delete-first, prune-first). - [ ] Delegation outside its scope is rejected. diff --git a/decisions/0007-retained-parent-ancestry.md b/decisions/0007-retained-parent-ancestry.md index 0978932..2088002 100644 --- a/decisions/0007-retained-parent-ancestry.md +++ b/decisions/0007-retained-parent-ancestry.md @@ -1,6 +1,6 @@ # ADR 0007: Structural Parent Ancestry Uses Retained Writes; Only Prune Removes Ancestry -- Status: Accepted (2026-09-13); Enbox parity divergence tracked upstream +- Status: Accepted (2026-09-13); implemented in Enbox #1685 and Rust #304 - Date: 2026-09-13 ## Context @@ -84,10 +84,10 @@ Separate structural ancestry from liveness: unchanged. A soft delete never revoked that authority. - **Soft delete does not seal.** Existing children already survive a soft delete; this makes the rule consistent. To remove a subtree, use `prune`, which purges - it. This is a divergence from current Enbox parity and requires a `dwn-spec` - clarification ("exist"/ancestry = retained write) and an upstream Enbox - proposal. Until Enbox agrees, a Rust implementation is an intentional, - documented divergence, not parity. + it. Current Enbox and Rust both implement this rule (`enboxorg/enbox#1685`, + `enbox-rust-core#304`), so it is no longer a parity divergence. A `dwn-spec` + clarification ("exist"/ancestry = retained write) is filed as + `enboxorg/dwn-spec#70` to make the reading explicit. - The interim receiver-local terminal classification for soft-deleted parents is superseded. The mechanism remains for pruned parents and for roles. @@ -112,10 +112,11 @@ None. - `enboxorg/enbox#991` — closure-based admission model (closed; 3b absence rule) - `enboxorg/enbox#1679`, `enboxorg/enbox#1680` — interim terminal classification -- `enboxorg/enbox#1684` — TypeScript retained-ancestry implementation +- `enboxorg/enbox#1684` — TypeScript retained-ancestry issue (implemented, `enboxorg/enbox#1685`) - `enboxorg/enbox-rust-core#303` — Rust parent verification and classification -- `enboxorg/enbox-rust-core#304` — Rust retained-ancestry implementation +- `enboxorg/enbox-rust-core#304` — Rust retained-ancestry implementation (merged) +- `enboxorg/knowledge#19` — knowledge adoption tracking - `enboxorg/enbox-rust-core#188` — durable feed substrate and reconciliation - `DWN-REC-004` (implementation-contract), `DWN-PROTO-001`, `DWN-PROTO-004`, `DWN-PROTO-005` - `implementation/dependency-resolution.md`, `dwn/protocols.md` -- `enboxorg/dwn-spec` issue — clarify that "exist" means retained, and that prune removes ancestry +- `enboxorg/dwn-spec#70` — clarify that "exist"/ancestry means the retained write, and that prune removes ancestry diff --git a/dwn/protocols.md b/dwn/protocols.md index 41cd69f..d8c3ee1 100644 --- a/dwn/protocols.md +++ b/dwn/protocols.md @@ -72,6 +72,19 @@ Composition turns protocol resolution into a graph rather than one self-containe Namespaces do not simply merge across a `$ref`; the referenced position and children can have different governing protocol/type sources. +### Parent ancestry + +A child's structural parent is proven by the parent's **retained initial write** +in the expected protocol, not by the parent's current liveness. A soft +`RecordsDelete` does not remove ancestry: a child may be written under a +soft-deleted parent and stays addressable by its own Record ID, protocol path, +and context, while the parent itself reads as absent. A `prune` delete removes +ancestry, because prune is the explicit subtree-removal operation; a child of a +pruned parent is terminal and unrepairable. Roles and grants are separate +concerns and remain current-state, so revocation still wins. + +See `decisions/0007-retained-parent-ancestry.md`. + ## Encryption policy Protocols can require encryption and define key-agreement policy. Encryption policy belongs to protocol semantics, while actual decryption key distribution is a separate capability lifecycle. @@ -99,5 +112,6 @@ Protocol directives can affect how Records are delivered or selected for visibil - Do not evaluate historical Records only against the newest protocol configuration. - Do not treat a protocol role as globally valid outside its context. - Do not resolve cross-protocol parents using a same-protocol-only query. +- Do not treat a soft-deleted parent as unusable ancestry; only `prune` removes ancestry. - Do not assume draft directive semantics exactly match current Enbox without checking known divergence issues. - Do not merge authorization policy and encryption key possession into one concept. diff --git a/enbox/records.md b/enbox/records.md index 7290995..df2e2fd 100644 --- a/enbox/records.md +++ b/enbox/records.md @@ -64,6 +64,21 @@ Tracked by `enbox-rust-core#189`. Rust implemented this ordering and store-owned atomic latest-state transitions under `enbox-rust-core#189`. +## Parent ancestry and deletes + +Current TypeScript and Rust resolve a structural parent from its **retained +initial write** in the expected protocol, not from current liveness. A soft +`RecordsDelete` does not remove ancestry: a child written under a soft-deleted +parent is admitted and remains addressable by its own Record ID, path, and +context, while the parent itself reads as absent. A `prune: true` delete removes +ancestry and settles the rejected descendant terminally (replication classifies it +superseded rather than a repairable dependency), so a pruned subtree leaves no +dead letter. Role and grant lookups stay current-state, so soft-delete revocation +still wins. + +Recorded as ADR 0007; implemented in `enboxorg/enbox#1685` and +`enbox-rust-core#304`. + ## Visibility gap Physical retained state and application-visible state are separate concerns. Query/Count/Subscribe route through a shared visibility model including read-time record limits, context boundaries, published selection and initial-write attachment. Rust alignment landed under `enbox-rust-core#190` (PR #262) and is pinned by `ENBOX-REC-003` and `DWN-REC-008`. diff --git a/implementation/dependency-resolution.md b/implementation/dependency-resolution.md index 5c9fc09..7cbf77d 100644 --- a/implementation/dependency-resolution.md +++ b/implementation/dependency-resolution.md @@ -85,7 +85,8 @@ does not seal or hide children); a `prune` removes ancestry and is terminal. See `decisions/0007-retained-parent-ancestry.md`. This restores `DWN-REC-004` for parent-bearing children without retraction or -trusted timestamps. An implementation that consults current liveness — current -Enbox, and the interim receiver-local terminal classification in -`enboxorg/enbox#1680` / `enboxorg/enbox-rust-core#303` — is order-dependent and -leaves a permanent dead letter when the delete precedes the child. +trusted timestamps. The interim receiver-local terminal classification for soft +deletes (`enboxorg/enbox#1679` / `#1680`, `enboxorg/enbox-rust-core#303`) is +superseded. Current Enbox and Rust both resolve parent ancestry from the retained +initial write (`enboxorg/enbox#1685`, `enboxorg/enbox-rust-core#304`); a `prune` +tombstone remains the terminal case.