Fork-deletion safety: deleting a parent with fork children preserves the pack set as a meta repository and re-points children (no-children path unchanged) #451

Closed
opened 2026-09-13 13:49:24 +00:00 by crueber · 3 comments
Owner

What's requested

Ruling (user): deleting a fork parent that still has fork children must not break the children. The deleted parent's storage (shared pack objects) is preserved as a meta repository — a storage-only prefix with no servable repo manifest — and every fork child is re-pointed to it so reads, pushes, and clones keep working. Fork-network bookkeeping (meta/forks.json, fork indexes, GC walk inputs) is updated to match. The no-children deletion path is unchanged: deleting a childless repo (fork or not) wipes the full prefix exactly as Registry.Delete does today.

Evidence (current behavior, static read — no live repro)

  • internal/wal/registry.go Registry.Delete (~line 287): deletes the manifest as the linearization point, then pages through every key under repos/<owner>/<repo>/ and deletes it — including wal/<checksum> pack objects that fork children reference under the parent's prefix. Nothing checks meta/forks.json. A parent with children is deleted identically to a childless repo; children lose every pack their manifests reference and every read falls off the end of the chain.
  • internal/wal/forkread.go (resolveForkChain / readForkDoc): children resolve ancestry through per-ancestor fork.json (Parent + Root), trying the own prefix first and falling back outward on miss. Once the parent prefix is fully wiped, readForkDoc on the parent returns ok=false and the chain simply ends — the fallback finds nothing. Comment in-file: "a deleted ancestor contributes nothing".
  • internal/maintain/forknet.go (forkNetworkLive, readForkIndex): the GC walk unions live packs from the parent's meta/forks.json and children's manifests; it skips subtrees whose child manifest is 404. A fully-deleted parent prefix means readForkIndex on the parent finds nothing — children's packs stop being protected the moment the parent goes, so maintain's removeSuperseded may delete packs live children still reference.
  • internal/pulls/pulls.go ForksKey (repos/<o>/<r>/meta/forks.json, CAS'd, frozen overwritable-key class) and internal/pulls/forks.go ListForks: the parent-side index is the bookkeeping source of truth.
  • cmd/walhub/serve.go repoRegistry.Delete and internal/api/summary.go repoDelete (DELETE Sub: "", AuthAdmin): today's delete surface passes straight through to Registry.Delete with no fork check.

Cross-check: audit ticket #449 lists "parent deletion/GC" among its audit areas — this ticket is the ruling, delivered ahead of the audit findings, so the audit should mark this item as covered by this ticket rather than re-report it.

Architecture notes

  • Meta repository shape (recommended, planner's call): the parent's prefix repos/<o>/<r>/ survives as a storage-only "meta repo": the wal/ pack set is preserved verbatim, the servable repo state (manifest, refs, checkpoints, issues, pulls, policy, access) is deleted, and a small bookkeeping doc is kept — the preserved meta/forks.json (child list) plus a marker file (e.g. meta/tombstone.json) recording what happened. The parent id does not change, so children's existing fork.json pointers (parent, root) stay textually correct; "re-pointing" means verifying/repairing those pointers and updating bookkeeping, not renaming ids. If planner prefers deleting the parent id entirely and electing a child (or a synthetic id) as the new root, that changes every child's fork.json — pick one and note it.
  • Child read path: internal/wal/forkread.go must treat a meta (tombstoned) ancestor as chain-alive: readForkDoc currently ends the chain when the ancestor's fork.json is gone — decide whether the meta repo keeps a minimal fork.json (so existing chain logic works untouched) or the reader learns to read meta/tombstone.json as a chain node. Keeping a minimal fork.json is the smaller diff; note the choice.
  • GC bookkeeping: internal/maintain/forknet.go walks from the parent's meta/forks.json and reads child manifests. The preserved index keeps that walk working — verify the walk's parent probe (exact-key GET, no LIST) tolerates the parent manifest being absent while the index is present, and that packs preserved in the meta prefix are unioned as live. Keep it fail-closed (corrupt/absent meta index must never unlock deletion of children-referenced packs).
  • Delete surface: the fork check belongs at the registry (internal/wal/registry.go Registry.Delete) so every caller (internal/api DELETE handler, CLI ops in cmd/walhub) inherits it — or in the api.Repos adapter if registry-level scope is wrong for some caller; pick one and note it. The API contract (docs/features/03 §8, docs/go/05 §5.1.2 delete) needs the amendment stated explicitly: "delete with children → meta-repo conversion, not a wipe".
  • Summary/ETag: children's fork_parent projection (internal/api/summary.go, ~f ETag suffix) must keep resolving through the meta repo; the count and "forked from" line must not regress. ForkInfo in internal/api/env.go is the seam.
  • No-children path unchanged: a repo with an empty/absent meta/forks.json deletes exactly as today — full prefix wipe, same linearization point, same error shape. Acceptance criterion below pins this.
  • Reusable primitives: the frozen overwritable-key class for meta/forks.json (internal/pulls/pulls.go header), (repo, kind) single-flight tasks (internal/wal/tasks.go) if the conversion runs as a narrated task, exact-key GETs (never LIST) per law 4, and the Create-arbitrates-the-name discipline in internal/pulls/forkexec.go (a meta repo must not collide with a racing re-create of the same name — decide: conversion makes the name unusable until tombstone GC, or re-create adopts/absorbs the meta prefix).
  • What does not exist yet: no tombstone/meta-repo marker object anywhere in the tree today; owners are manifest-derived prefixes, so a manifest-less prefix is invisible to listRepos/liveRepos (cmd/walhub/serve.go liveRepos requires a manifest HEAD) — that invisibility is probably desirable but must be stated, and explore/repos listings must not surface meta repos.

Acceptance criteria

  • Deleting a parent with ≥1 live fork child preserves the parent's wal/ pack set and fork bookkeeping; children's clone/push/read paths keep working afterwards (fork read fallback resolves through the meta repo).
  • Every fork child's bookkeeping reflects the new shape (fork.json chain valid end-to-end; parent-side meta/forks.json preserved and consistent with the child list).
  • Fork-network GC (internal/maintain/forknet.go) still protects packs referenced by live children after a parent deletion; a corrupt/missing meta index fails closed.
  • Deleting a repo with no fork children behaves byte-identically to today's Registry.Delete (full prefix wipe, manifest-first linearization, same errors).
  • Meta repos do not appear in repo listings, explore, or owner repo lists; a re-create of the same name has a defined, tested behavior (absorb or refuse — planner's call, noted).
  • Summary projection (fork_parent, forks count) on children keeps working through the meta repo, ETag suffix intact.
  • Docs amended: docs/features/03 §7/§8 (fork GC + fork creation) and docs/go/05 §5.1.2 (delete) state the meta-repo rule; the check lands in the delete path at exactly one layer with tests covering both paths (children present / no children).
## What's requested Ruling (user): **deleting a fork parent that still has fork children must not break the children.** The deleted parent's storage (shared pack objects) is preserved as a *meta repository* — a storage-only prefix with no servable repo manifest — and every fork child is re-pointed to it so reads, pushes, and clones keep working. Fork-network bookkeeping (`meta/forks.json`, fork indexes, GC walk inputs) is updated to match. **The no-children deletion path is unchanged**: deleting a childless repo (fork or not) wipes the full prefix exactly as `Registry.Delete` does today. ## Evidence (current behavior, static read — no live repro) - `internal/wal/registry.go` `Registry.Delete` (~line 287): deletes the manifest as the linearization point, then pages through **every key** under `repos/<owner>/<repo>/` and deletes it — including `wal/<checksum>` pack objects that fork children reference under the parent's prefix. Nothing checks `meta/forks.json`. A parent with children is deleted identically to a childless repo; children lose every pack their manifests reference and every read falls off the end of the chain. - `internal/wal/forkread.go` (`resolveForkChain` / `readForkDoc`): children resolve ancestry through per-ancestor `fork.json` (`Parent` + `Root`), trying the own prefix first and falling back outward on miss. Once the parent prefix is fully wiped, `readForkDoc` on the parent returns `ok=false` and the chain simply ends — the fallback finds nothing. Comment in-file: "a deleted ancestor contributes nothing". - `internal/maintain/forknet.go` (`forkNetworkLive`, `readForkIndex`): the GC walk unions live packs from the parent's `meta/forks.json` and children's manifests; it skips subtrees whose child manifest is 404. A fully-deleted parent prefix means `readForkIndex` on the parent finds nothing — children's packs stop being protected the moment the parent goes, so maintain's `removeSuperseded` may delete packs live children still reference. - `internal/pulls/pulls.go` `ForksKey` (`repos/<o>/<r>/meta/forks.json`, CAS'd, frozen overwritable-key class) and `internal/pulls/forks.go` `ListForks`: the parent-side index is the bookkeeping source of truth. - `cmd/walhub/serve.go` `repoRegistry.Delete` and `internal/api/summary.go` `repoDelete` (DELETE `Sub: ""`, AuthAdmin): today's delete surface passes straight through to `Registry.Delete` with no fork check. Cross-check: audit ticket #449 lists "parent deletion/GC" among its audit areas — this ticket is the *ruling*, delivered ahead of the audit findings, so the audit should mark this item as covered by this ticket rather than re-report it. ## Architecture notes - **Meta repository shape (recommended, planner's call):** the parent's prefix `repos/<o>/<r>/` survives as a storage-only "meta repo": the `wal/` pack set is preserved verbatim, the servable repo state (manifest, refs, checkpoints, issues, pulls, policy, access) is deleted, and a small bookkeeping doc is kept — the preserved `meta/forks.json` (child list) plus a marker file (e.g. `meta/tombstone.json`) recording what happened. The parent id does not change, so children's existing `fork.json` pointers (`parent`, `root`) stay textually correct; "re-pointing" means verifying/repairing those pointers and updating bookkeeping, not renaming ids. If planner prefers deleting the parent id entirely and electing a child (or a synthetic id) as the new root, that changes every child's `fork.json` — pick one and note it. - **Child read path:** `internal/wal/forkread.go` must treat a meta (tombstoned) ancestor as chain-alive: `readForkDoc` currently ends the chain when the ancestor's `fork.json` is gone — decide whether the meta repo keeps a minimal `fork.json` (so existing chain logic works untouched) or the reader learns to read `meta/tombstone.json` as a chain node. Keeping a minimal `fork.json` is the smaller diff; note the choice. - **GC bookkeeping:** `internal/maintain/forknet.go` walks from the parent's `meta/forks.json` and reads child manifests. The preserved index keeps that walk working — verify the walk's parent probe (exact-key GET, no LIST) tolerates the parent manifest being absent while the index is present, and that packs preserved in the meta prefix are unioned as live. Keep it fail-closed (corrupt/absent meta index must never unlock deletion of children-referenced packs). - **Delete surface:** the fork check belongs at the registry (`internal/wal/registry.go` `Registry.Delete`) so every caller (`internal/api` DELETE handler, CLI ops in `cmd/walhub`) inherits it — or in the `api.Repos` adapter if registry-level scope is wrong for some caller; pick one and note it. The API contract (docs/features/03 §8, docs/go/05 §5.1.2 delete) needs the amendment stated explicitly: "delete with children → meta-repo conversion, not a wipe". - **Summary/ETag:** children's `fork_parent` projection (`internal/api/summary.go`, `~f` ETag suffix) must keep resolving through the meta repo; the count and "forked from" line must not regress. `ForkInfo` in `internal/api/env.go` is the seam. - **No-children path unchanged:** a repo with an empty/absent `meta/forks.json` deletes exactly as today — full prefix wipe, same linearization point, same error shape. Acceptance criterion below pins this. - **Reusable primitives:** the frozen overwritable-key class for `meta/forks.json` (`internal/pulls/pulls.go` header), `(repo, kind)` single-flight tasks (`internal/wal/tasks.go`) if the conversion runs as a narrated task, exact-key GETs (never LIST) per law 4, and the `Create`-arbitrates-the-name discipline in `internal/pulls/forkexec.go` (a meta repo must not collide with a racing re-create of the same name — decide: conversion makes the name unusable until tombstone GC, or re-create adopts/absorbs the meta prefix). - **What does not exist yet:** no tombstone/meta-repo marker object anywhere in the tree today; owners are manifest-derived prefixes, so a manifest-less prefix is invisible to `listRepos`/`liveRepos` (`cmd/walhub/serve.go` `liveRepos` requires a manifest HEAD) — that invisibility is probably desirable but must be stated, and explore/repos listings must not surface meta repos. ## Acceptance criteria - [ ] Deleting a parent with ≥1 live fork child preserves the parent's `wal/` pack set and fork bookkeeping; children's clone/push/read paths keep working afterwards (fork read fallback resolves through the meta repo). - [ ] Every fork child's bookkeeping reflects the new shape (`fork.json` chain valid end-to-end; parent-side `meta/forks.json` preserved and consistent with the child list). - [ ] Fork-network GC (`internal/maintain/forknet.go`) still protects packs referenced by live children after a parent deletion; a corrupt/missing meta index fails closed. - [ ] Deleting a repo with no fork children behaves byte-identically to today's `Registry.Delete` (full prefix wipe, manifest-first linearization, same errors). - [ ] Meta repos do not appear in repo listings, explore, or owner repo lists; a re-create of the same name has a defined, tested behavior (absorb or refuse — planner's call, noted). - [ ] Summary projection (`fork_parent`, `forks` count) on children keeps working through the meta repo, ETag suffix intact. - [ ] Docs amended: docs/features/03 §7/§8 (fork GC + fork creation) and docs/go/05 §5.1.2 (delete) state the meta-repo rule; the check lands in the delete path at exactly one layer with tests covering both paths (children present / no children).
crueber added this to the v1 milestone 2026-09-13 13:49:35 +00:00
Author
Owner

Fixed by PR #462 (#462): delete-with-children converts the parent to a storage-only meta repo (packs + forks.json + fork.json + tombstone preserved, servable state swept, no child rewrites); no-children path byte-identical; re-create absorbs per the issue's planner's-call (refusal needed a create-path probe that broke the push fast-path budget — evidence in PR); GC walk unchanged + pinned; e2e with real git (clone/push/reads + absorb) green.

Fixed by PR #462 (https://git.packden.us/crueber/walhub/pulls/462): delete-with-children converts the parent to a storage-only meta repo (packs + forks.json + fork.json + tombstone preserved, servable state swept, no child rewrites); no-children path byte-identical; re-create absorbs per the issue's planner's-call (refusal needed a create-path probe that broke the push fast-path budget — evidence in PR); GC walk unchanged + pinned; e2e with real git (clone/push/reads + absorb) green.
Author
Owner

REVIEW PR #462 (fix/issue-451, fork-deletion safety) — adversarial pass, verified in scratch worktree /tmp/pr462. No browser (no browser-facing change — storage/delete paths only, e2e + unit reasoning as instructed).

VERDICT: ready to merge (one trivial docstring fix pushed as 9da05a7).

(1) WITH-CHILDREN PATH — SOUND. internal/wal/registry.go Delete: liveForkChildren probe runs BEFORE the handle teardown and the manifest-delete linearization point; conversion order is tombstone → manifest delete → sweepPrefix (registry.go:324-345). Preserve set in metarepo.go metaPreserved: wal/ + meta/forks.json + fork.json + meta/tombstone.json — complete: fork children reference only parent wal/ checksums + fork.json chain (fork shares packs by reference, nothing copied); LFS is per-repo uploads with upstream read-through only (no pulls/ LFS refs at all), bundles/ are regenerable derived state, everything else under the prefix is parent-scoped servable state. Manifest gone = Open 404 + refreshList/Exists manifest-gated = invisible to listings (proven by TestDelete451/with-children/meta-conversion asserting Open-fails + not-listed). Children read/push/clone post-delete proven by TestE2E_ForkDeleteKeepsChildrenWorking (real git: clone content, child push lands, summary fork_parent intact) — PASS.

(2) RE-POINTING — SATISFACTORY, zero-child-write holds. Parent id never changes; readForkDoc reads repos//fork.json which is preserved, so resolveForkChain needs zero logic changes (forkread.go diff is comment-only — correct). Chain-through-tombstoned-parent proven by TestDelete451Chain (leaf chain [o/r a/b] + sharedGet of both root.pack and mid.pack after BOTH ancestor deletes) and the absorb case (chain intact + child pack read after re-create). GC-race window also covered: snapshot-taken-pre-delete sweeping post-delete still pins via preserved index (TestForkNetworkGCMetaParent).

(3) ABSORB-vs-REFUSE — ABSORB CORRECT, evidence checks out. Registry.Create is a bare manifest PutCreate (registry.go:235-247), no probe anywhere on the create path — absorb is automatic and zero-trip. Refusal is structurally unimplementable probeless (Open=NotFound + Create=412 cannot coexist on one key — presence is binary), and a tombstone probe would bill HEADs on the push fast path. TestPushFastPathZeroCollabRoundTrips green UNMODIFIED (22 trips/2 pushes). Stale tombstone is informational only (Delete never reads it — liveness re-derives from the index every call, proven by tombstone-GC subtests). Orphan-pack cost (pre-delete generation unowned until childless wipe) is stated in metarepo.go header + both docs — acceptable, disclosed. Tombstone-GC cycle proven both directions (TestDelete451Chain end, TestCreate451AbsorbsMeta end: full wipe incl. tombstone, names free).

(4) NO-CHILDREN PATH BYTE-IDENTICAL — YES. Absent/empty/all-stale index takes the untouched full-wipe code path (registry.go:349+); pinned by no-children/full-wipe (zero keys left, re-create free), stale-index-row/full-wipe, empty-repo-row-skipped.

(5) GC WALK — SOUND. forknet.go forkNetworkLive never probes the parent manifest (reads own index + child manifest.pb exact-GETs only) — docs claim 'the walk never probes the parent manifest' VERIFIED in code. Preserved index keeps in-flight-snapshot passes pinning; corrupt variant aborts with removed=0 (TestForkNetworkGCMetaCorruptIndex) — fail closed. Per-repo GC lists only its own wal/ prefix and no snapshot ever starts from a meta prefix, so preserved packs are structurally safe.

(6) BOOKKEEPING — SOUND. meta/forks.json preserved verbatim = consistent (ids unchanged, no rewrite needed); stale rows self-neutralize (manifest-404 children pin nothing in both liveForkChildren and the GC walk). Parent's own fork.json preserved keeps multi-level chains resolving. Indexes need no mutation — 'updated to match' holds vacuously and correctly.

(7) FAIL-CLOSED — all 9 TestDelete451FailClosed subtests verified: index-GET-error, child-probe-error, tombstone-write-error (all 3 abort pre-linearization, manifest intact asserted), sweep-LIST-error, sweep-DELETE-error, sweep-inner-DELETE-error (abort, packs survive; tombstone+manifest-delete already committed but re-delete via API self-heals since repoDelete calls Repos.Delete with no Open gate — summary.go:334), nil-body-index (doubt=error), empty-repo-row-skipped, tombstone-nil-children. Partial-failure states converge on retry (conversion is idempotent, childless delete absorbs the marker).

(8) HYGIENE — wal 95.1% / maintain 95.5% (gate holds, measured); -race clean on wal+maintain; sim tier green (12s); e2e forkdelete PASS; pulls + api + server + cmd suites green; gofmt/vet clean; go build clean; go.mod/go.sum/package.json untouched (no new deps); docs amended (03 §7+Decisions, 05 §5.1.2+Decisions) and accurate against code (walk claim, coexistence claim both verified). Law 4: conversion uses exact-key probes; LIST only in the non-hot delete sweep. Law 8: metarepo.go mirrors the pulls index shape locally, no upward import. Concurrency subsection present; no locks held across store calls. Covers audit #449 parent-deletion/GC item as claimed.

FIX APPLIED (pushed 9da05a7): e2e/forkdelete_test.go:19 docstring said 'the parent name refuses re-create' while step 8 asserts absorb (PUT→201) — corrected to 'absorbs on re-create'. Comment-only; e2e re-run green after push.

Note: server-suite UI failures seen mid-review were environmental (scratch web/dist unbuilt — only .keep tracked); built web in scratch, suite green. Restored tracked web/dist/.keep after the build wiped it; scratch worktree removed. Main worktree untouched (clean).

No browser used (no browser-facing change). Do NOT merge per instructions — awaiting maintainer.

REVIEW PR #462 (fix/issue-451, fork-deletion safety) — adversarial pass, verified in scratch worktree /tmp/pr462. No browser (no browser-facing change — storage/delete paths only, e2e + unit reasoning as instructed). VERDICT: ready to merge (one trivial docstring fix pushed as 9da05a7). (1) WITH-CHILDREN PATH — SOUND. internal/wal/registry.go Delete: liveForkChildren probe runs BEFORE the handle teardown and the manifest-delete linearization point; conversion order is tombstone → manifest delete → sweepPrefix (registry.go:324-345). Preserve set in metarepo.go metaPreserved: wal/ + meta/forks.json + fork.json + meta/tombstone.json — complete: fork children reference only parent wal/ checksums + fork.json chain (fork shares packs by reference, nothing copied); LFS is per-repo uploads with upstream read-through only (no pulls/ LFS refs at all), bundles/ are regenerable derived state, everything else under the prefix is parent-scoped servable state. Manifest gone = Open 404 + refreshList/Exists manifest-gated = invisible to listings (proven by TestDelete451/with-children/meta-conversion asserting Open-fails + not-listed). Children read/push/clone post-delete proven by TestE2E_ForkDeleteKeepsChildrenWorking (real git: clone content, child push lands, summary fork_parent intact) — PASS. (2) RE-POINTING — SATISFACTORY, zero-child-write holds. Parent id never changes; readForkDoc reads repos/<parent>/fork.json which is preserved, so resolveForkChain needs zero logic changes (forkread.go diff is comment-only — correct). Chain-through-tombstoned-parent proven by TestDelete451Chain (leaf chain [o/r a/b] + sharedGet of both root.pack and mid.pack after BOTH ancestor deletes) and the absorb case (chain intact + child pack read after re-create). GC-race window also covered: snapshot-taken-pre-delete sweeping post-delete still pins via preserved index (TestForkNetworkGCMetaParent). (3) ABSORB-vs-REFUSE — ABSORB CORRECT, evidence checks out. Registry.Create is a bare manifest PutCreate (registry.go:235-247), no probe anywhere on the create path — absorb is automatic and zero-trip. Refusal is structurally unimplementable probeless (Open=NotFound + Create=412 cannot coexist on one key — presence is binary), and a tombstone probe would bill HEADs on the push fast path. TestPushFastPathZeroCollabRoundTrips green UNMODIFIED (22 trips/2 pushes). Stale tombstone is informational only (Delete never reads it — liveness re-derives from the index every call, proven by tombstone-GC subtests). Orphan-pack cost (pre-delete generation unowned until childless wipe) is stated in metarepo.go header + both docs — acceptable, disclosed. Tombstone-GC cycle proven both directions (TestDelete451Chain end, TestCreate451AbsorbsMeta end: full wipe incl. tombstone, names free). (4) NO-CHILDREN PATH BYTE-IDENTICAL — YES. Absent/empty/all-stale index takes the untouched full-wipe code path (registry.go:349+); pinned by no-children/full-wipe (zero keys left, re-create free), stale-index-row/full-wipe, empty-repo-row-skipped. (5) GC WALK — SOUND. forknet.go forkNetworkLive never probes the parent manifest (reads own index + child manifest.pb exact-GETs only) — docs claim 'the walk never probes the parent manifest' VERIFIED in code. Preserved index keeps in-flight-snapshot passes pinning; corrupt variant aborts with removed=0 (TestForkNetworkGCMetaCorruptIndex) — fail closed. Per-repo GC lists only its own wal/ prefix and no snapshot ever starts from a meta prefix, so preserved packs are structurally safe. (6) BOOKKEEPING — SOUND. meta/forks.json preserved verbatim = consistent (ids unchanged, no rewrite needed); stale rows self-neutralize (manifest-404 children pin nothing in both liveForkChildren and the GC walk). Parent's own fork.json preserved keeps multi-level chains resolving. Indexes need no mutation — 'updated to match' holds vacuously and correctly. (7) FAIL-CLOSED — all 9 TestDelete451FailClosed subtests verified: index-GET-error, child-probe-error, tombstone-write-error (all 3 abort pre-linearization, manifest intact asserted), sweep-LIST-error, sweep-DELETE-error, sweep-inner-DELETE-error (abort, packs survive; tombstone+manifest-delete already committed but re-delete via API self-heals since repoDelete calls Repos.Delete with no Open gate — summary.go:334), nil-body-index (doubt=error), empty-repo-row-skipped, tombstone-nil-children. Partial-failure states converge on retry (conversion is idempotent, childless delete absorbs the marker). (8) HYGIENE — wal 95.1% / maintain 95.5% (gate holds, measured); -race clean on wal+maintain; sim tier green (12s); e2e forkdelete PASS; pulls + api + server + cmd suites green; gofmt/vet clean; go build clean; go.mod/go.sum/package.json untouched (no new deps); docs amended (03 §7+Decisions, 05 §5.1.2+Decisions) and accurate against code (walk claim, coexistence claim both verified). Law 4: conversion uses exact-key probes; LIST only in the non-hot delete sweep. Law 8: metarepo.go mirrors the pulls index shape locally, no upward import. Concurrency subsection present; no locks held across store calls. Covers audit #449 parent-deletion/GC item as claimed. FIX APPLIED (pushed 9da05a7): e2e/forkdelete_test.go:19 docstring said 'the parent name refuses re-create' while step 8 asserts absorb (PUT→201) — corrected to 'absorbs on re-create'. Comment-only; e2e re-run green after push. Note: server-suite UI failures seen mid-review were environmental (scratch web/dist unbuilt — only .keep tracked); built web in scratch, suite green. Restored tracked web/dist/.keep after the build wiped it; scratch worktree removed. Main worktree untouched (clean). No browser used (no browser-facing change). Do NOT merge per instructions — awaiting maintainer.
Author
Owner

Fixed by PR #462 (review clean + one docstring fix by reviewer; tombstone path, zero-write re-pointing, absorb, GC all verified; sim + e2e green), merged. Closing.

Fixed by PR #462 (review clean + one docstring fix by reviewer; tombstone path, zero-write re-pointing, absorb, GC all verified; sim + e2e green), merged. Closing.
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
crueber/walhub#451
No description provided.