F3b: a root-wide operation must PROVE its population complete
Pre-review reach finding, closed before the reviewer sees the tree: the referrer scan iterates discovery's CANDIDATES, so anything discovery could not walk -- an unreadable directory, a contested candidate, a symlinked directory (deliberately unwalked, and the F3a containment escape is the standing reason to distrust them) -- could hold referrers no pass would ever see, and a rename or delete would proceed around them: silent knowledge loss through the census's blind spot.
population-guard refuses every root-wide destructive operation (rename, delete, cross-file move) while any such location exists, naming each; skips for 'not-a-source-format / 'not-a-regular-file are accepted BY DESIGN (a .txt or a socket is outside the document population the ruled independent pass covers). Probe arms: a symlinked directory in the root refuses both rename and delete with 'population-unprovable; battery 12/12.
src/folio/doc/structural.sgl | 42 ++++++++++++++++++++++++++++++++++++++++--
test/f3b-probe.sgl | 19 ++++++++++++++++++-
2 files changed, 58 insertions(+), 3 deletions(-)src/folio/doc/structural.sglmodified
;; its population from discovery's census -- with the denominator kept, ;; never a filtered list -- and reads every candidate INSIDE the lock. ;; ------------------------------------------------------------------ ;; A root-wide destructive operation may only trust a population it can ;; PROVE complete. Discovery reports what it could not walk, and three ;; of its classes mean referrers could exist UNSEEN: unreadable ;; directories, contested candidates (collisions), and symlinked ;; directories (deliberately unwalked -- and the F3a containment escape ;; is the standing reason to distrust them). Any of those REFUSES the ;; operation; skips for reason 'not-a-source-format or ;; 'not-a-regular-file are accepted BY DESIGN -- a .txt or a socket is ;; outside the document population the ruled independent pass covers. ;; Returns #f when the census is trustworthy, or a refusal naming what ;; is not. (define (population-guard census) (let ((unwalkable (append (map (lambda (d) (list d 'unreadable-directory)) (census-unreadable-dirs census)) (map (lambda (c) (list (candidate-path c) 'contested-candidate)) (census-collisions census)) (let loop ((rest (census-skipped census)) (out '())) (cond ((null? rest) (reverse out)) ((eq? (skip-reason (car rest)) 'symlinked-directory) (loop (cdr rest) (cons (list (skip-path (car rest)) 'symlinked-directory) out))) (else (loop (cdr rest) out))))))) (if (null? unwalkable) #f (%txn-refuse 'population-unprovable (format "a root-wide operation cannot prove its referrer scan complete: ~a location(s) could not be walked: ~a -- refusing rather than renaming or deleting around unseen referrers" (length unwalkable) unwalkable) retryable: #t)))) (define (root-population coord) ;; Returns (census . entries) where each entry is ;; (list rel-path abs-path format bytes-or-#f). Bytes are read for (let* ((doomed (cons node-id (%txn-subtree-declared-ids node))) (doomed (filter string? doomed)) (population (root-population coord)) (guard-refusal (population-guard (car population))) (found (find-referrers coord (cdr population) doomed)) (semantic (list-ref found 0)) (raw-only (list-ref found 1)) (troubles (list-ref found 2))) (cond (guard-refusal guard-refusal) ((pair? troubles) (%txn-refuse 'referrer-uninspectable (format "cannot prove ~s unreferenced: ~a file(s) carrying its id could not be inspected: ~a" (define (rename-under-lock coord old-id new-id expected-revision) (let* ((population (root-population coord)) (census (car population)) (entries (cdr population))) (entries (cdr population)) (guard-refusal (population-guard census))) (if guard-refusal guard-refusal ;; 1. The NEW id must be completely fresh -- no boundary occurrence ;; anywhere, prose included. Conservative by design. (let ((taken old-id (length owners) (map car owners)))) (else (rename-with-owner coord old-id new-id expected-revision entries (car owners))))))))) entries (car owners)))))))))) (define (rename-with-owner coord old-id new-id expected-revision entries owner) (let* ((owner-rel (list-ref owner 0)) ;; discipline as rename. (let* ((root (coordinator-root coord)) (population (root-population coord)) (guard-refusal (population-guard (car population))) (entries (cdr population)) (found (find-referrers coord entries moved-ids)) (semantic (list-ref found 0)) (raw-only (list-ref found 1)) (troubles (list-ref found 2))) (cond (guard-refusal guard-refusal) ((pair? troubles) (%txn-refuse 'referrer-uninspectable (format "cannot account for every occurrence of ~a: ~a" moved-ids troubles)test/f3b-probe.sglmodified
(assert-refusal "unknown id refuses" (folio-rename-id! coord "t-nonesuch" "t-fresh" (revision-of-file (rooted "owner.sgd"))) 'unknown-node)))) 'unknown-node) ;; A location the scan could not WALK is a population it cannot prove ;; complete: a symlinked directory inside the root (discovery skips ;; those deliberately) must refuse every root-wide destructive op. (make-symlink (path-dirname (rooted "owner.sgd")) (rooted "esc-dir")) (assert-refusal "an unwalkable location refuses the rename" (folio-rename-id! coord "t-lonely" "t-fresh" (revision-of-file (rooted "owner.sgd"))) 'population-unprovable) (delete-file (rooted "esc-dir")) (assert-refusal "delete refuses over the same unprovable population" (begin (make-symlink (path-dirname (rooted "owner.sgd")) (rooted "esc-dir2")) (let ((r (folio-delete-node! coord (rooted "owner.sgd") "t-lonely" (revision-of-file (rooted "owner.sgd"))))) (delete-file (rooted "esc-dir2")) r)) 'population-unprovable))));; ---------------------------------------------------------------------------;; b7-delete -- the referrer discipline, then the clean path.