Evidence / frozen 20 September 2026
Open the
notebook.
Selected source excerpts, with their original line numbers and whole-file digests. Trailing whitespace is omitted in the display. These are evidence you can inspect without access to the development machine.
Revision 3e1b1849413d5168322fcf34c9f74e77d109be72. A source declaration is not a fresh certification. Historical lab records inside this snapshot carry their own revisions and scope. Claude continued work after this snapshot.
Full assessment · Machine-readable manifest · Local check outcomes · Report UI checks
The obligation behind acceptancespecs/storage.md · L33–72
File SHA-256 356d4f8cc42bb3574ffc202a9880f54ec474cba3d29d751cbcb96773e34c98e2
33 STO-002: acceptance publishes one transaction containing the source references, 34 duplicate-history effects, all local group allocations, and any obligations or 35 reservations accepted in that operation. No partially committed cross-post or 36 promised-but-unaccounted retention can become visible. 37 38 ## Commit protocol 39 40 The semantic phases are: 41 42 1. Validate against committed state; reserve resources and stage an identified 43 transaction. The proposal is not yet visible to readers. 44 2. Make all referenced object bytes and their required namespace reachability 45 durable according to the platform contract. 46 3. Make the transaction's complete commit record durable. 47 4. Publish the committed state and generate the protocol/application success. 48 49 STO-003: successful acceptance is emitted only after the corresponding durable 50 commit result. Host completion events name the transaction and generation; stale, 51 duplicate, or unrelated completions cannot publish another transaction. 52 53 Only one shared-state commit is in flight initially. Network input may continue 54 within quotas. A disconnected requester does not cancel an already durable 55 transaction. If it retries, history prevents repeated allocation/effects. 56 57 STO-004: a known abort and an indeterminate I/O result are distinct. After an 58 indeterminate result, fence shared-state mutations and recover before continuing. 59 Do not assume an error means nothing reached disk. It is valid for an unacknowledged 60 transaction to appear after recovery; it is not valid for a modeled acknowledged 61 commit to vanish. Transport/application retries must accommodate that uncertainty. 62 63 ## Recovery 64 65 STO-005: under the [crash model](failures.md), recovery produces an invariant- 66 preserving committed history containing every acknowledged transaction, with no 67 partial transaction. Unacknowledged complete commits may also survive. Recovery 68 checks framing, integrity, dependencies, journal order, and checkpoint linkage. 69 70 The exact rule for choosing a journal/checkpoint generation is still open. Do 71 not implement “pick the newest timestamp.” Incomplete uncommitted tails and 72 detected damage to committed data have different handling. Quarantine/report
Completion is tied to the exact recordbooks/store-node-invariants.lisp · L107–214
File SHA-256 70f391a9ca9132c0f1488c612a4bd3c45809a5e701869354368aaa1654fc3074
107
108 ; Every portable record value is equal to its counterpart in the actual staged
109 ; acceptance/retention transaction, not merely to a sequence/txid reply string.
110 (defthm fn-sn-record-binds-pending-fields
111 (implies (fn-sn-record-bindsp node record)
112 (let ((p (fn-state-pending (fn-node-acceptance node)))
113 (stage (fn-node-stage node)))
114 (and (equal (fn-record-txid record) (fn-pending-txid p))
115 (equal (fn-record-generation record) (fn-pending-generation p))
116 (equal (fn-record-msgid record) (fn-pending-msgid p))
117 (equal (fn-record-payload record) (fn-pending-payload p))
118 (equal (fn-record-groups record) (fn-pending-groups p))
119 (equal (fn-record-obligation-id record) (fn-node-stage-id stage))
120 (equal (fn-record-content-subject record) (fn-node-stage-subject stage))
121 (equal (fn-record-release-evidence record) (fn-node-stage-evidence stage))
122 (equal (fn-record-charge record) (fn-node-stage-charge stage)))))
123 :rule-classes nil)
124
125 (defun fn-sn-committed-recordp (node record)
126 (let* ((article (fn-find-article (fn-record-msgid record)
127 (fn-state-articles (fn-node-acceptance node))))
128 (binding (fn-node-find-binding (fn-record-msgid record)
129 (fn-node-bindings node)))
130 (pin (fn-retain-find-id (fn-record-obligation-id record)
131 (fn-retain-pins (fn-node-retention node)))))
132 (and (consp article)
133 (equal (fn-article-msgid article) (fn-record-msgid record))
134 (equal (fn-article-payload article) (fn-record-payload record))
135 (equal (fn-article-groups article) (fn-record-groups record))
136 (equal (fn-article-pin article) t)
137 (equal binding (fn-node-make-binding
138 (fn-record-msgid record) (fn-record-content-subject record)
139 (fn-record-obligation-id record)))
140 (equal pin (fn-retain-make-obligation
141 (fn-record-obligation-id record)
142 (fn-record-content-subject record) :archive
143 (fn-record-release-evidence record) (fn-record-charge record))))))
144
145 (defthm fn-sn-actual-durable-completion-installs-record
146 (implies (fn-sn-record-bindsp node record)
147 (fn-sn-committed-recordp
148 (fn-node-complete node (fn-record-txid record)
149 (fn-record-generation record) :durable)
150 record))
151 :hints (("Goal" :use fn-sn-record-binds-pending-fields
152 :in-theory (enable fn-node-complete fn-node-statep fn-statep fn-snx-core-definitions))))
153
154 ; -by-definition: the else branch of fn-sn-finish with its test negated.
155 (defthm fn-sn-finish-disabled-is-no-op
156 (implies (not (fn-sn-completion-enabledp s))
157 (equal (fn-sn-finish s) s))
158 :rule-classes nil
159 :hints (("Goal" :in-theory (disable fn-sn-completion-enabledp))))
160
161 (defthm fn-sn-finish-is-actual-durable-completion
162 (implies (fn-sn-completion-enabledp s)
163 (equal (fn-sn-node (fn-sn-finish s))
164 (fn-node-complete (fn-sn-node s)
165 (fn-record-txid (fn-sn-completion-record s))
166 (fn-record-generation (fn-sn-completion-record s))
167 :durable)))
168 :hints (("Goal" :in-theory (disable fn-sn-completion-enabledp
169 fn-sn-completion-record))))
170
171 (defthm fn-sn-finish-installs-exact-article-and-archive-pin
172 (implies (fn-sn-completion-enabledp s)
173 (fn-sn-committed-recordp (fn-sn-node (fn-sn-finish s))
174 (fn-sn-completion-record s)))
175 :hints (("Goal" :use (fn-sn-finish-is-actual-durable-completion
176 (:instance fn-sn-actual-durable-completion-installs-record
177 (node (fn-sn-node s))
178 (record (fn-sn-completion-record s))))
179 :in-theory (disable fn-sn-finish fn-sn-committed-recordp
180 fn-sn-record-bindsp fn-sn-completion-record))))
181
182 (defthm fn-sn-finish-acknowledges-exact-pair
183 (implies (fn-sn-completion-enabledp s)
184 (equal (fn-sf-successes (fn-sn-files (fn-sn-finish s)))
185 (append (fn-sf-successes (fn-sn-files s))
186 (list (fn-sf-completion (fn-sn-files s))))))
187 :hints (("Goal"
188 :use ((:instance fn-sf-core-completion-preserves-state
189 (s (fn-sn-files s))
190 (sequence (fn-record-sequence (fn-sn-completion-record s)))
191 (txid (fn-record-txid (fn-sn-completion-record s)))))
192 :in-theory (e/d (fn-sf-core-completion fn-sf-emit-success)
193 (fn-sn-record-bindsp fn-sn-completion-record)))))
194
195 (defthm fn-sn-new-success-requires-actual-matching-durable-node-completion
196 (implies (not (equal (fn-sf-successes (fn-sn-files (fn-sn-finish s)))
197 (fn-sf-successes (fn-sn-files s))))
198 (and (fn-sn-completion-enabledp s)
199 (fn-sn-record-bindsp (fn-sn-node s) (fn-sn-completion-record s))
200 (equal (fn-sn-node (fn-sn-finish s))
201 (fn-node-complete (fn-sn-node s)
202 (fn-record-txid (fn-sn-completion-record s))
203 (fn-record-generation (fn-sn-completion-record s))
204 :durable))
205 (fn-sn-committed-recordp (fn-sn-node (fn-sn-finish s))
206 (fn-sn-completion-record s))))
207 :hints (("Goal" :use (fn-sn-finish-is-actual-durable-completion
208 fn-sn-finish-installs-exact-article-and-archive-pin
209 fn-sn-finish-disabled-is-no-op)
210 :cases ((fn-sn-completion-enabledp s))
211 :in-theory (disable fn-sn-finish fn-sn-completion-record
212 fn-sn-record-bindsp fn-sn-committed-recordp))))
213
214 ; A matching live proposal has exactly the same durable meaning as a recordThe storage host calls the completion and loaderhost/store-node-host.lisp · L389–421
File SHA-256 8628eb566308f4487a09ebb852c1f0d466cac54b69f378c794218b4fa6f9fda9
389 (not (equal next s)) 390 (equal (fn-sf-phase (fn-sn-files next)) :ready)) 391 (let ((state (f-put-global 'fn-store-sn next state))) 392 (value :aborted)) 393 (value :fault)))) 394 395 (defun fn-store-sn-pending-octets (state) 396 (declare (xargs :stobjs state :mode :program)) 397 (let ((record (fn-sf-record-candidate 398 (fn-sn-files (f-get-global 'fn-store-sn state))))) 399 (value (if record (fn-record-encode record) nil)))) 400 401 (defun fn-store-sn-finish (state) 402 (declare (xargs :stobjs state :mode :program)) 403 (let* ((before (f-get-global 'fn-store-sn state)) 404 (before-files (fn-sn-files before)) 405 (next (fn-sn-finish before)) 406 (after-files (fn-sn-files next))) 407 ; fn-sn-finish is deliberately a no-op away from :completing. The host 408 ; reports success only for the transition that consumes this exact pending 409 ; completion and appends one acknowledgement, never for a stale ready 410 ; state or repeated call. 411 (if (and (equal (fn-sf-phase before-files) :completing) 412 (equal (fn-sf-phase after-files) :ready) 413 (equal (len (fn-sf-successes after-files)) 414 (1+ (len (fn-sf-successes before-files))))) 415 (let ((state (f-put-global 'fn-store-sn next state))) (value :durable)) 416 (value :fault)))) 417 418 (defun fn-store-sn-article-count (state) 419 (declare (xargs :stobjs state :mode :program)) 420 (value (len (fn-state-articles 421 (fn-node-acceptance
Survival under the admissible crash-image hypothesisbooks/store-observed.lisp · L608–644
File SHA-256 7e645aeaf98ea6c8694af34d6b33f497697c975109679666cf1a81a46ae2bde4
608 ; from a related live state (fn-sf-crash-imagep, store-files.lisp), the host's
609 ; reopen entry succeeds and every pair acknowledged before the crash names a
610 ; record of the reopened state. The acknowledgement list itself is nil after
611 ; reopen (fn-sn-open-observed-success-exact-history); the guarantee is carried
612 ; by the records the adapter persists, not by a reconstructed ghost.
613 ; The live-history relation carries the observed configuration predicate
614 ; through fn-sn-statep (fn-snt-relation-implies-structural-state).
615 (defthm fn-snt-relation-implies-observed-configuration
616 (implies (fn-snt-relation s)
617 (fn-sn-observed-configurationp (fn-sn-groups s) (fn-sn-capacity s)))
618 :hints (("Goal"
619 :use fn-snt-relation-implies-structural-state
620 :in-theory (e/d (fn-sn-statep fn-sn-observed-configurationp)
621 (fn-snt-relation fn-sf-statep fn-node-statep
622 fn-snt-relation-implies-structural-state
623 fn-snt-typed-store-components)))))
624
625 (defthm fn-sn-acknowledged-record-survives-observed-reopen
626 (implies (and (fn-snt-relation s)
627 (fn-sf-crash-imagep (fn-sn-files s) frontier records)
628 (member-equal pair (fn-sf-successes (fn-sn-files s))))
629 (and (fn-sn-open-okp
630 (fn-sn-open-observed (fn-sn-groups s) (fn-sn-capacity s)
631 frontier records))
632 (fn-sf-record-has-pairp
633 pair
634 (fn-sf-records
635 (fn-sn-files
636 (fn-sn-open-state
637 (fn-sn-open-observed (fn-sn-groups s) (fn-sn-capacity s)
638 frontier records)))))))
639 :hints (("Goal"
640 :use (fn-snt-relation-implies-observed-configuration
641 fn-snt-admissible-crash-image-is-recoverable
642 (:instance fn-sf-admissible-image-facts (s (fn-sn-files s)))
643 (:instance fn-sn-open-observed-succeeds-on-recoverable-image
644 (groups (fn-sn-groups s)) (capacity (fn-sn-capacity s)))What the crash-image hypothesis permitsbooks/store-files.lisp · L584–608
File SHA-256 c4a5cb1d26871acd9adc2ff96bab33ecdaf0ef4ee47ce9cd1117ff72bcc8111b
584 ; A-DURABILITY and A-WRITE-ISOLATION as a hypothesis, not a constructor. An 585 ; observed post-crash image (frontier, records) is admissible for kernel state 586 ; s when the frontier is the stable value or, only while a replacement may 587 ; have been issued, the candidate; and the records are the stable list or, 588 ; only while a link may have been issued, that list extended by the exact 589 ; data-durable candidate. Theorems about the host's reopen path 590 ; (store-observed.lisp) take this predicate as their premise. fn-sf-crash 591 ; below is one constructor that satisfies it and shows the premise is 592 ; inhabited; the guarantee comes from the predicate, not from the constructor. 593 (defun fn-sf-crash-imagep (s frontier records) 594 (declare (xargs :guard t :verify-guards nil)) 595 (and (fn-sf-statep s) 596 (or (equal frontier (fn-sf-frontier s)) 597 (and (fn-sf-frontier-new-visiblep s) 598 (equal frontier (fn-sf-frontier-candidate s)))) 599 (or (equal records (fn-sf-records s)) 600 (and (fn-sf-record-present-visiblep s) 601 (equal records (append (fn-sf-records s) 602 (list (fn-sf-record-candidate s)))))))) 603 604 (defun fn-sf-crash (s frontier-choice record-choice) 605 (declare (xargs :guard t :verify-guards nil)) 606 (if (and (fn-sf-statep s) 607 (fn-sf-crash-choicep frontier-choice record-choice)) 608 (let* ((frontier
A reachable transaction and a changed-payload counterexampletests/acl2/store-node-teeth-tests.lisp · L20–98
File SHA-256 0837fbd034ce070d67d2502be57fde849c2938e36099f0c59c6f5ebc08718049
20
21 ; -----------------------------------------------------------------------------
22 ; A reachable, non-degenerate witness: the composed store driven from its
23 ; initial state through allocation, staging, publication and finish, with a
24 ; record whose Message-ID, archive identity, content subject and release
25 ; evidence are four different strings across two groups.
26
27 (defconst *snt-groups* '("fn.letters" "fn.test"))
28 (defconst *snt-record*
29 (fn-record-make 0 0 0 "<sn@example>" '(65 66) *snt-groups*
30 "sn-pin" "sn-content" "sn-release" 2))
31
32 (defconst *snt-reserved*
33 (fn-sn-io (fn-sn-io (fn-sn-io (fn-sn-io (fn-sn-initial *snt-groups* 10)
34 :start-frontier nil)
35 :frontier-file :ok)
36 :frontier-replace :ok)
37 :frontier-directory :ok))
38 (defconst *snt-prepared* (fn-sn-prepare *snt-reserved* *snt-record*))
39 (defconst *snt-record-durable* (fn-sn-io *snt-prepared* :record-file :ok))
40 (defconst *snt-record-attempted* (fn-sn-io *snt-record-durable* :record-link :ok))
41 (defconst *snt-completing* (fn-sn-io *snt-record-attempted* :record-directory :ok))
42 (defconst *snt-finished* (fn-sn-finish *snt-completing*))
43
44 (assert-event (fn-sn-statep *snt-prepared*))
45 (assert-event (fn-sn-statep *snt-record-durable*))
46 (assert-event (fn-sn-statep *snt-completing*))
47 (assert-event (fn-sn-statep *snt-finished*))
48 (assert-event (fn-sn-record-bindsp (fn-sn-node *snt-prepared*) *snt-record*))
49 (assert-event (equal (fn-sf-phase (fn-sn-files *snt-record-durable*))
50 :record-data-durable))
51 (assert-event (equal (fn-sf-phase (fn-sn-files *snt-completing*)) :completing))
52 (assert-event (equal (fn-sf-phase (fn-sn-files *snt-finished*)) :ready))
53
54 ; Non-degenerate: the record binds on every field, and the four identities in
55 ; it are distinct, so a relation that compared only one of them would not do.
56 (assert-event (not (equal (fn-record-msgid *snt-record*)
57 (fn-record-content-subject *snt-record*))))
58 (assert-event (not (equal (fn-record-obligation-id *snt-record*)
59 (fn-record-release-evidence *snt-record*))))
60
61 ; The success appears only at finish, and the record is committed by it.
62 (assert-event (null (fn-sf-successes (fn-sn-files *snt-completing*))))
63 (assert-event (equal (fn-sf-successes (fn-sn-files *snt-finished*)) '((0 . 0))))
64 (assert-event (fn-sn-committed-recordp (fn-sn-node *snt-finished*) *snt-record*))
65 (assert-event (equal (fn-sn-finish *snt-finished*) *snt-finished*))
66
67 ; -----------------------------------------------------------------------------
68 ; Teeth for `fn-sn-actual-durable-completion-installs-record'
69 ; (implies (fn-sn-record-bindsp node record)
70 ; (fn-sn-committed-recordp
71 ; (fn-node-complete node (fn-record-txid record)
72 ; (fn-record-generation record) :durable)
73 ; record))
74
75 ; The sole hypothesis dropped. A record that differs from the staged one in a
76 ; single field -- the payload -- shares its sequence, transaction and
77 ; generation, so the completion it names still fires; what it does not do is
78 ; commit the record it claims.
79 (defconst *snt-other-record*
80 (fn-record-make 0 0 0 "<sn@example>" '(99) *snt-groups*
81 "sn-pin" "sn-content" "sn-release" 2))
82 (assert-event
83 (equal (fn-record-txid *snt-other-record*) (fn-record-txid *snt-record*)))
84 (assert-event
85 (equal (fn-record-generation *snt-other-record*)
86 (fn-record-generation *snt-record*)))
87 (assert-event
88 (not (fn-sn-record-bindsp (fn-sn-node *snt-prepared*) *snt-other-record*)))
89
90 (assert-event (with-guard-checking :none (not (fn-sn-committed-recordp
91 (fn-node-complete (fn-sn-node *snt-prepared*)
92 (fn-record-txid *snt-other-record*)
93 (fn-record-generation *snt-other-record*)
94 :durable)
95 *snt-other-record*))))
96
97 ; -----------------------------------------------------------------------------
98 ; Teeth for `fn-sn-new-success-requires-actual-matching-durable-node-completion'The inductive append law and its served bridgebooks/served.lisp · L894–939
File SHA-256 b6e8930785d476ac4bc4e8cc6b5de58540f7375c1fd936b07fdc5bad843fd885
894 (fn-served-make-result
895 (fn-served-result-conn
896 (fn-served-feed (fn-served-result-conn (fn-served-feed conn left))
897 right))
898 (append
899 (fn-served-result-effects (fn-served-feed conn left))
900 (fn-served-result-effects
901 (fn-served-feed (fn-served-result-conn (fn-served-feed conn left))
902 right)))))
903 :hints (("Goal" :induct (fn-served-feed conn left)
904 :in-theory (disable fn-served-dispatch-events fn-wire-feed-byte
905 fn-wire-statep))))
906
907 (local
908 (defthm fn-served-feed-of-closed-wire
909 (implies (fn-served-closed-wirep (fn-served-conn-wire conn))
910 (equal (fn-served-feed conn octets)
911 (fn-served-make-result conn nil)))
912 :hints (("Goal" :expand ((fn-served-feed conn octets))))))
913
914 (defthm fn-served-step-partition-independence
915 (implies (and (fn-served-connp conn)
916 (fn-wire-octet-listp left)
917 (fn-wire-octet-listp right))
918 (equal (fn-served-step conn (append left right))
919 (fn-served-make-result
920 (fn-served-result-conn
921 (fn-served-step
922 (fn-served-result-conn (fn-served-step conn left)) right))
923 (append
924 (fn-served-result-effects (fn-served-step conn left))
925 (fn-served-result-effects
926 (fn-served-step
927 (fn-served-result-conn (fn-served-step conn left))
928 right))))))
929 :hints (("Goal"
930 :do-not-induct t
931 :cases ((fn-served-closed-wirep
932 (fn-served-conn-wire
933 (fn-served-result-conn (fn-served-feed conn left)))))
934 :in-theory (disable fn-served-feed fn-wire-statep fn-served-connp
935 fn-served-feed-preserves-wire-statep
936 fn-served-feed-preserves-connp)
937 :use ((:instance fn-served-feed-preserves-wire-statep
938 (octets left))
939 (:instance fn-served-connp-is-wire-state (c conn))))))The actual host calls the composed readhost/owner-host.lisp · L455–467
File SHA-256 62e5cb2003ae5d060a254b48c1dde2e087309a719303038a61aa0659ca987d00
455 ; One socket read of one connection is one fn-own-read: fn-served-step over 456 ; the connection's wire, session and pinned archive 457 ; (fn-own-read-is-served-step-on-pinned-prefix). The whole chunk is 458 ; consumed; there is no suffix and no loop in Python. 459 (defun fn-owner-chunk (id octets state) 460 (declare (xargs :stobjs state :mode :program)) 461 (let ((owner (f-get-global 'fn-owner state))) 462 (if (not (fn-own-find-conn id (fn-own-conns owner))) 463 (value :unknown) 464 (let* ((result (fn-own-read owner id octets)) 465 (state (f-put-global 'fn-owner (cdr result) state)) 466 (state (fn-owner-install-effects (car result) state))) 467 (value :ok)))))
The repaired owner entry pointbooks/owner.lisp · L589–609
File SHA-256 aa682a1f5833c7a27b8ca1ff49fa9df52cee2cafcc00e00726ce1c186fa7e6ea
589 (defun fn-own-open (o) 590 (declare (xargs :guard t)) 591 (if (< (len (fn-own-conns o)) (nfix (fn-own-max-conns o))) 592 (let* ((view (fn-own-view o)) 593 (archive (fn-own-view-archive view)) 594 (id (fn-own-next-id o)) 595 (opened (fn-served-open archive *fn-nntp-max-initial-line-octets* 596 *fn-own-body-limit* (fn-own-config o) 597 (fn-own-clock o) (fn-own-clock o))) 598 (sconn (fn-served-result-conn opened)) 599 (conn (fn-own-conn-make id (fn-own-view-version view) 600 (fn-own-view-frontier view) 601 (fn-served-conn-wire sconn) 602 (fn-served-conn-session sconn) 603 archive (fn-own-config o) (fn-own-clock o)))) 604 (cons (fn-served-result-effects opened) 605 (fn-own-make (fn-own-store o) view (cons conn (fn-own-conns o)) 606 (1+ (nfix id)) (fn-own-max-conns o) (fn-own-pending o) 607 (fn-own-ledger o) (fn-own-clock o) (fn-own-facts o) (fn-own-config o) (fn-own-queue o) (fn-own-inflight o)))) 608 (cons nil o))) 609
The pinned-prefix correspondence theorembooks/owner-invariants.lisp · L657–690
File SHA-256 f52cef432cac39930be7ab357a140d9e03a997ae747670465c6cee04a86b14dd
657 ; -----------------------------------------------------------------------------
658 ; K1, served port: one socket read of a connection is one fn-served-step over
659 ; the connection's wire and session and the acceptance projection of
660 ; fn-sf-replay-node over the first `version` durable records, advanced to the
661 ; pinned frontier. The effects the host writes are exactly this call's.
662
663 (defthm fn-own-read-is-served-step-on-pinned-prefix
664 (implies (and (fn-own-relation o)
665 (fn-own-find-conn id (fn-own-conns o)))
666 (let* ((conn (fn-own-find-conn id (fn-own-conns o)))
667 (s (fn-own-store o)))
668 (equal (car (fn-own-read o id octets))
669 (fn-served-result-effects
670 (fn-served-step
671 (fn-served-make-conn
672 (fn-own-conn-wire conn)
673 (fn-own-conn-session conn)
674 (fn-node-acceptance
675 (fn-sf-replay-node (fn-sn-groups s) (fn-sn-capacity s)
676 (fn-own-take (fn-own-conn-version conn)
677 (fn-sf-records (fn-sn-files s)))
678 (fn-own-conn-frontier conn)))
679 (fn-own-conn-config conn)
680 (fn-own-conn-observation conn)
681 (fn-own-clock o))
682 octets)))))
683 :hints (("Goal"
684 :use ((:instance fn-own-find-conn-okp
685 (conns (fn-own-conns o))
686 (groups (fn-sn-groups (fn-own-store o)))
687 (capacity (fn-sn-capacity (fn-own-store o)))
688 (records (fn-sf-records (fn-sn-files (fn-own-store o))))))
689 :in-theory (e/d (fn-own-relation)
690 (fn-own-conn-boundedp fn-own-find-conn-okp)))))Named assumptions and the limits of defining thembooks/assumptions.lisp · L1–78
File SHA-256 ba3240ac1892124a6b158f2bcb9b5f8e8c8bb659e9302e4232a271d09949bb3e
1 ; fn's named assumptions as constrained functions. 2 ; 3 ; specs/failures.md names eight assumptions. Until this book existed they were 4 ; prose, which the 2026-09-18 independent review recorded as an assurance 5 ; discipline gap: "There is no `encapsulate' in the tree. A-DURABILITY, 6 ; A-HOST, A-CRYPTO, A-PEER, A-POLICY exist only as prose." A named assumption 7 ; is now an `encapsulate' with a local witness and the minimal constraint the 8 ; theorems that depend on it would need as a hypothesis. 9 ; 10 ; Three things each encapsulate buys, and one it does not: 11 ; 12 ; * The assumption is mechanically listable. `tools/ledger.py' counts 13 ; encapsulates; a grep for `fn-assume-' finds every dependent. 14 ; * Its satisfiability is proved once, by the local witness. An assumption 15 ; no function can satisfy is a proof that the deployment is impossible, and 16 ; ACL2 refuses the encapsulate rather than letting it stand. 17 ; * Functional instantiation is the platform-qualification hook. A 18 ; qualification profile supplies a concrete function and discharges the 19 ; constraints; `:functional-instance' then carries every theorem that took 20 ; the assumption as a hypothesis down to that profile. See 21 ; docs/proofs.md, "Qualifying a platform against A-DURABILITY". 22 ; 23 ; * It does not make any theorem depend on the assumption. Each constraint 24 ; below carries a comment naming the theorems that should eventually take 25 ; it as a hypothesis. Until they do, this book states the assumptions; it 26 ; does not yet discharge or apply them. That is C1-14 and C1-15 work. 27 ; 28 ; This book deliberately includes nothing. It is the bottom of the tree, and 29 ; its constraints are about abstract values, not about any particular book's 30 ; state representation, so that a later refinement can instantiate them 31 ; wherever the corresponding theorem lives. 32 ; 33 ; A-CRYPTO is not here. `books/crypto-seam.lisp' owns the digest and signature 34 ; seam (`fn-digest', `fn-sig-verify'); defining a second constrained crypto 35 ; function would be exactly the twin the review told us to delete. 36 37 (in-package "ACL2") 38 39 ; ----------------------------------------------------------------------------- 40 ; A-DURABILITY. "A completed platform barrier preserves the named bytes and 41 ; necessary namespace updates across a modeled crash." 42 ; 43 ; The crash image is a function of what a completed barrier made stable and 44 ; what was written without one. Two constraints: a crash cannot lose a 45 ; barriered record, and a crash cannot invent one. 46 ; 47 ; Theorems that should take this as a hypothesis, rather than building it into 48 ; `fn-sf-crash' (books/store-files.lisp:463) as the review's D5 records: 49 ; fn-sf-stable-records-prefix-of-crash books/store-files.lisp 50 ; fn-sf-prior-success-has-record-after-one-crash books/store-files.lisp 51 ; fn-sf-crash-preserves-state books/store-files.lisp 52 ; fn-sf-prior-emitted-success-retained-by-run-trace books/store-files-traces.lisp 53 ; fn-snrt-acknowledged-history-retained-through-mixed-trace 54 ; books/store-node-resolution-traces.lisp 55 ; The five-barrier gate `fn-sf-phase-shapep' (books/store-files.lisp:167) is 56 ; what decides which records are in the BARRIERED argument at a crash point. 57 58 (encapsulate 59 (((fn-assume-durability-image * *) => *)) 60 61 (local (defun fn-assume-durability-image (barriered unbarriered) 62 (declare (ignore unbarriered)) 63 barriered)) 64 65 ; A completed barrier is what survives. This is the whole positive content 66 ; of A-DURABILITY; the platform qualification must establish it for the 67 ; selected device, filesystem and barrier call. 68 (defthm fn-assume-durability-retains-barriered 69 (implies (member-equal record barriered) 70 (member-equal record (fn-assume-durability-image barriered unbarriered)))) 71 72 ; A crash invents nothing. Without this, a "surviving" record need not have 73 ; been written at all, and record survival would say nothing about history. 74 (defthm fn-assume-durability-invents-nothing 75 (implies (member-equal record (fn-assume-durability-image barriered unbarriered)) 76 (or (member-equal record barriered) 77 (member-equal record unbarriered))) 78 :rule-classes nil))
Generated support facts and proof disciplinedocs/proof-style.md · L1–25
File SHA-256 4f10a14b8dd325fbe0ea7853b1e7112de8a889e96342816b91c3e2211721591b
1 # Proof style
2
3 These are the conventions every book in `books/` and `tests/acl2/` follows.
4 They were set on the core cluster (acceptance, retention, node, replay,
5 exchange) in the 2026-09-19 realignment; each worked example below is real
6 code, cited by file. The assurance rules in [`AGENTS.md`](../AGENTS.md) say
7 what a claim must be; this document says how a book is written so that green
8 stays green when lanes merge.
9
10 Three of the conventions are now machinery, and the machinery is the
11 convention:
12
13 | what | where | it replaces |
14 | --- | --- | --- |
15 | `fn-defrecord`, `fn-defrecord-export` | `books/defrecord.lisp` | eleven to forty-one hand-written events per record (section 1) |
16 | `fn-deftransition-closed`, `fn-deftransition` | `books/deftransition.lisp` | a floating `(local (in-theory (disable ...)))` and hand-wired branch lemmas ("Never open a recognizer") |
17 | `tools/proof_profile.py` | one command | rediscovering `accumulated-persistence` on every slow form (section 9) |
18
19 `tools/ledger.py`'s fifth lint counts the records still written by hand, so
20 the migration is visible rather than remembered.
21
22 The problem these solve, measured on the tree before the realignment: no
23 typed structures (806 raw-list accessors), 642 `-is-`/`-of-` equalities of
24 which 68 were `:rule-classes nil` and the rest were global rewrite rules on
25 include, whole-state recognizers recomputed on every served operationThe owner reaches the served byte foldbooks/owner.lisp · L674–707
File SHA-256 aa682a1f5833c7a27b8ca1ff49fa9df52cee2cafcc00e00726ce1c186fa7e6ea
674 (defun fn-own-read (o id octets) 675 (declare (xargs :guard t)) 676 (let ((conn (fn-own-find-conn id (fn-own-conns o)))) 677 (if conn 678 (let* ((result (fn-served-step (fn-served-make-conn (fn-own-conn-wire conn) 679 (fn-own-conn-session conn) 680 (fn-own-conn-archive conn) 681 (fn-own-conn-config conn) 682 (fn-own-conn-observation conn) 683 (fn-own-clock o)) 684 octets)) 685 (effects (fn-served-result-effects result)) 686 (sconn (fn-served-result-conn result)) 687 (next (fn-own-conn-make (fn-own-conn-id conn) 688 (fn-own-conn-version conn) 689 (fn-own-conn-frontier conn) 690 (fn-served-conn-wire sconn) 691 (fn-served-conn-session sconn) 692 (fn-own-conn-archive conn) 693 (fn-own-conn-config conn) 694 (fn-own-conn-observation conn))) 695 (decision (fn-served-submission effects))) 696 (cons effects 697 (if (fn-own-conn-boundedp next (fn-sn-groups (fn-own-store o))) 698 (let ((o2 (fn-own-set-conns o (fn-own-replace-conn next (fn-own-conns o))))) 699 (if decision 700 (fn-own-enqueue o2 (fn-own-sub-make id (fn-own-conn-version conn) 701 nil decision)) 702 o2)) 703 (fn-own-set-conns o (fn-own-remove-conn id (fn-own-conns o)))))) 704 (cons nil o)))) 705 706 ; The per-event law under the served port: one framed wire event is one 707 ; fn-served-dispatch (fn-nntp-post-step with the article-mode switch,
Whole-archive validation removed from individual commandsspecs/nntp-audit.md · L53–89
File SHA-256 f3e0956dfcc38a70d014e2936b349b5c6baf52c25a708726a2828e6e019211eb
53 The reader now separates three things. 54 55 **Configuration, decided once.** `fn-nntp-projectionp` is now a configuration 56 recognizer: the acceptance state recognizer, group names that a response can 57 render (nonempty printable US-ASCII, at most 460 octets), watermarks inside RFC 58 3977 §6's range, and an article capacity of at most 2,147,483,647. 59 `fn-nntp-open-session` evaluates it once, when the reader opens a connection 60 (`host/reader-host.lisp` `fn-reader-reset`), and stores the verdict as the 61 session's fourth field. `fn-nntp-step-preserves-carried-projection` states that 62 every step returns a session with the same verdict, and 63 `fn-nntp-finite-trace-preserves-carried-projection` lifts that to finite traces. 64 No command re-runs `fn-nntp-projectionp`. 65 66 **Commands that do not read the archive.** `fn-nntp-command` dispatches 67 CAPABILITIES, HELP, QUIT, an unrecognized keyword, and a syntax error through 68 `fn-nntp-session-command`, which takes no archive argument. 69 `fn-nntp-archive-free-step-ignores-the-archive` states that for any command line 70 whose keyword is not one of the nine archive commands, the step's result is the 71 same for every archive. No committed article and no configuration can deny those 72 commands. When the carried verdict is false, only the nine archive commands 73 answer `503 archive projection unavailable`, which is RFC 3977 §3.2.1's code for 74 a recognized command whose required information the server does not hold. 75 76 **Articles that degrade only themselves.** Per-article checks are split by what 77 a response actually needs. `fn-nntp-article-idp` is the bounded identifier check 78 (a string of at most 250 octets forming a valid `message-id`); STAT, NEXT and 79 LAST need only this. `fn-nntp-article-framedp` is the payload check (CRLF-framed 80 throughout, carrying no NUL, and containing a header/body separator); only 81 ARTICLE, HEAD and BODY need it. 82 83 | Situation | Response | Effect on the group | 84 | --- | --- | --- | 85 | Identifier not renderable | `503 stored article identifier unavailable` to ARTICLE/HEAD/BODY/STAT naming its number | Excluded from the count, the water marks, LISTGROUP, and NEXT/LAST | 86 | Identifier fine, payload not framed | `503 stored article framing unavailable` to ARTICLE/HEAD/BODY; STAT, NEXT and LAST succeed | Included; the number is real and STAT answers for it | 87 | Number outside RFC 3977 §6's range | as "identifier not renderable" | Excluded | 88 89 `fn-nntp-effects-article-response` is now unconditional in the article: the
What the digest attachment provesbooks/crypto-attach.lisp · L48–99
File SHA-256 54a655456b13ce20957d519608c284d3f1ec81c3a364d274373dd632547e945e
48 ; the same predicate, and that is proved here rather than assumed.
49
50 (local
51 (defthm fn-sha256-cbor-octet-listp-is-sha256-octet-listp
52 (equal (fn-cbor-octet-listp xs)
53 (fn-sha256-octet-listp xs))))
54
55 ; -----------------------------------------------------------------------------
56 ; The constraints, discharged for the attachment.
57 ;
58 ; These are exactly the `defthm's inside the two `encapsulate's, with
59 ; `fn-sha256' in place of the constrained function. `defattach' re-generates
60 ; them as proof obligations; proving them here as named events is what makes
61 ; the discharge citable rather than something that happened inside an event.
62
63 (defthm fn-sha256-satisfies-fn-digest-shape
64 ; crypto-seam's `fn-digest-shape'.
65 (fn-digest-octetsp (fn-sha256 m))
66 :hints (("Goal" :in-theory (enable fn-digest-octetsp))))
67
68 (defthm fn-sha256-satisfies-fn-frame-digest-octet-listp
69 ; frame-octets' `fn-frame-digest-octet-listp'.
70 (fn-cbor-octet-listp (fn-sha256 octets)))
71
72 (defthm fn-sha256-satisfies-fn-frame-digest-length
73 ; frame-octets' `fn-frame-digest-length'. *fn-frame-trailer-octets* is 32.
74 (equal (len (fn-sha256 octets)) *fn-frame-trailer-octets*))
75
76 ; No constraint of either `encapsulate' is stronger than 32 octets: the seams
77 ; ask for shape and for nothing else, which is why the discharge is three
78 ; lines and not a research programme. If a later constraint is added that
79 ; SHA-256 does not provide, that is a finding about the seam to be recorded,
80 ; never a constraint to weaken.
81
82 ; -----------------------------------------------------------------------------
83 ; The attachments.
84
85 (defattach fn-digest fn-sha256)
86 (defattach fn-frame-digest fn-sha256)
87
88 ; -----------------------------------------------------------------------------
89 ; The coercion is the identity on the domain fn actually digests.
90 ;
91 ; `fn-sha256' is total because `fn-digest' is constrained over ANY object, so
92 ; it fixes its argument to octets first. Every preimage fn digests is an
93 ; octet list -- the tagged preimages of crypto-seam, the protected prefix of a
94 ; frame, the subject and obligation preimages of books/identity.lisp -- and on
95 ; those the fixer does nothing. Without this theorem, "fn-digest is SHA-256"
96 ; would be a claim about a coerced argument rather than about the octets the
97 ; caller passed.
98
99 (defthm fn-sha256-fix-octets-is-identityThe broader architecture claimdocs/architecture.md · L77–115
File SHA-256 05cea9fe56517a623b51afe2ec433c4fa77ed02475abc55fe7fc48b84b68f0fa
77 ## Trust boundary 78 79 The intended proof subject is the executable core plus its specified codecs, 80 recovery, and storage algorithms. The initial trusted boundary includes ACL2, 81 its host Lisp/runtime, cryptographic primitive implementations, the I/O adapter, 82 and stated platform assumptions. Claims grow only as refinement and integration 83 evidence appear. See [failures](../specs/failures.md) and [proofs](proofs.md). 84 85 **The digest left that boundary on 2026-09-20.** `books/sha256.lisp` defines 86 FIPS 180-4 SHA-256 over octet lists as a total, guard-verified ACL2 function, 87 and `books/crypto-attach.lisp` attaches it to both digest seams — `fn-digest` 88 (`books/crypto-seam.lisp`) and `fn-frame-digest` (`books/frame-octets.lisp`) — 89 after discharging every constraint each `encapsulate` states. So the digest is 90 now **computed in logic by a proved-executable definition**: no host SHA-256 91 stands behind a content identity, an AUTHINFO verifier or a frame the core 92 reasons about, and the identity derivation has one owner. Agreement with the 93 standard is by evaluation against the published vectors 94 (`tests/acl2/sha256-tests.lisp`), which is evidence, not a proof that the 95 definition and the document agree on every input. 96 97 What remains assumed is unchanged and is stated as before: **collision 98 resistance and preimage resistance are A-CRYPTO** (`specs/failures.md`, 99 `books/assumptions.lisp`). `defattach` adds no axiom; it discharges the 100 constraints and makes ground terms evaluate, so every theorem that held of the 101 seam holds now, with the same hypotheses and no more. The seam's own local 102 witness is still the constant zero digest, and 103 `tests/acl2/crypto-seam-tests.lisp` still attaches a colliding toy realiser to 104 keep that visible. Signature verification did not move: `fn-sig-verify` and 105 `fn-anchor-sig-verify` stay constrained, Ed25519 stays a host facility in 106 `tools/crypto_host.py`, and nothing here is evidence about a signature. 107 108 Stored evidence is not automatically authority. An untrusted article cannot 109 change configuration, authorize a new peer, erase another article, or create a 110 retention obligation simply by naming it. The policy version and authorization 111 context of acceptance must be recoverable. 112 113 ## Product boundaries 114 115 The first usable site has configured unmoderated groups, a complete planned
The remaining Python framing digesttools/frame_bridge.py · L149–160
File SHA-256 7345aa0ceca463e9c73b6a0e74fff1271a2792061e16d59533fbb99f14b1792c
149 # -- framing ------------------------------------------------------------ 150 151 def seal(self, prefix: bytes) -> bytes: 152 """Append the integrity trailer over the prefix ACL2 produced.""" 153 return prefix + hashlib.sha256(prefix).digest() 154 155 def digest_of(self, framed: bytes) -> bytes: 156 """The digest a decoder must be given for these stored octets.""" 157 trailer = self.constants["trailer"] 158 if len(framed) < trailer: 159 return b"" 160 return hashlib.sha256(framed[:-trailer]).digest()
The remaining native framing digesthost/native/io.lisp · L754–767
File SHA-256 7a3c6d8882f5681c553be378cc9b59ad1f2f7e723369f7f07b5a2ab5c7f34a64
754 (defun fnn-constant (name) (cdr (assoc name (fnn-constants)))) 755 756 (defun fnn-seal (prefix) 757 (concatenate 'fnn-octets (fnn-octets prefix) (fnn-sha256 prefix))) 758 759 (defun fnn-digest-of (framed) 760 (if (< (length framed) (fnn-constant :trailer)) 761 nil 762 (fnn-octet-list (fnn-sha256 (subseq framed 0 (- (length framed) (fnn-constant :trailer))))))) 763 764 (defun fnn-frame (record) 765 "ACL2 builds the protected prefix; the host appends the integrity trailer." 766 (let ((value (fnn-core 'fn-store-frame-store-protected (fnn-octet-list record)))) 767 (when (or (keywordp value) (not (fnn-octet-list-p value)))
The proposed verifier and its claimed scopebooks/auth-secret.lisp · L1–45
File SHA-256 09599f128c05ec547c9bffcfd83d0838f157a775c973d8009e67456664f77e85
1 ; fn: the AUTHINFO stored-credential scheme (RFC 4643 section 2.3). 2 ; 3 ; Before this book the AUTHINFO credential held its secret IN THE CLEAR and 4 ; the comparison was `equal' on the octets, for a reason recorded in 5 ; books/nntp-auth.lisp and on the deputy board as OB-AUTH-DIGEST: fn's only 6 ; digest was a constrained function with no attachment, so a digest on the 7 ; served path would have made `fn-served-step' non-executable, and deriving 8 ; it in Python is refused by the one-owner rule. `books/crypto-attach.lisp' 9 ; removed that wall. This book is the scheme; `books/nntp-auth.lisp' calls 10 ; `fn-authsec-checkp' where it used to call `equal'. 11 ; 12 ; THE SCHEME, exactly (specs/nntp.md, "The stored AUTHINFO credential"): 13 ; 14 ; salt : exactly 16 octets, per credential, chosen at enrolment 15 ; preimage : salt || secret (the salt's length is fixed, so 16 ; the boundary is unambiguous; 17 ; see -preimage-injective below) 18 ; stored : (:fn-authsec-v1 salt (fn-digest-tagged "fn-authinfo-v1" 19 ; preimage)) 20 ; check : the supplied octets pass iff re-deriving the digest under the 21 ; stored salt yields the stored digest 22 ; 23 ; The tag is the crypto seam's domain separation: an AUTHINFO digest and a 24 ; content identity digest never share a preimage, whatever their messages 25 ; (books/crypto-seam.lisp, `fn-digest-tagged-preimage-injective'). 26 ; 27 ; WHAT IS PROVED AND WHAT IS NOT. Proved here: the enrolled secret always 28 ; checks; the stored verifier is not an octet list at all, so the slot that 29 ; used to carry the secret cannot carry one; the stored digest is 32 octets 30 ; whatever the secret, so it does not even reveal the secret's length; and 31 ; the preimage recovers (salt, secret). NOT proved, and not provable in this 32 ; logic: that a WRONG secret is rejected. That is second-preimage resistance 33 ; of the attached digest -- A-CRYPTO (specs/failures.md) -- and the seam's 34 ; own local witness is a constant digest under which every secret passes. 35 ; The test book exhibits rejection as a witness on concrete octets, which is 36 ; evidence about those octets and nothing more. 37 ; 38 ; This book also does not make AUTHINFO USER/PASS safe on an unprotected 39 ; connection: the secret still crosses the wire in the clear, which is why 40 ; RFC 4643 section 2.3.2 asks for a protected channel and why 41 ; `fn-auth-config-protected-onlyp' answers 483 until STARTTLS has run. What 42 ; changes is what an operator's configuration file, and a stolen copy of it, 43 ; contains. 44 45 (in-package "ACL2")
The actual AUTHINFO comparisonbooks/nntp-auth.lisp · L377–383
File SHA-256 4370ef936827dcba4fcb9d71b02670a4dd28a2f9cce9627ba176505ac3cb2cad
377 (defun fn-auth-checkp (cred secret) 378 ; The whole of the password comparison, in ACL2. See the header: the 379 ; configuration holds the secret, not a digest, because fn's only digest 380 ; cannot be evaluated. 381 (declare (xargs :guard t)) 382 (and (consp cred) (equal (fn-auth-cred-secret cred) secret) t)) 383
Authentication is not yet in the served pathplanning/lanes/HANDOFF-w9-server-polish.md · L57–100
File SHA-256 9d2cc7f21ba3213f95790eae1b14aba4c588eec8c9dbfc52f36695c82a1c7825
57 stop serving. Deriving the digest in Python is refused by the one-owner rule. 58 Next step: a `defattach` for `fn-digest`, or an ACL2 definition of a real hash. 59 Owner: the substrate cluster. Nothing in `books/nntp-auth.lisp` changes. 60 61 **`books/nntp-auth.lisp` is NOT wired into `books/served.lisp`.** The book is 62 a Makefile root of its own and certifies (or fails) standalone; the served fold 63 still calls `fn-peer-step`. The wiring is mechanical and was scoped out when 64 the lane's ACL2 budget went to the XPAT chain. Exactly: 65 - `books/served.lisp`: `(include-book "peer-inbound")` -> `"nntp-auth"`; 66 every `fn-peer-step` -> `fn-auth-step`; every 67 `fn-peer-session-consistentp` -> `fn-auth-session-consistentp`; 68 `fn-peer-open-session-is-consistent` -> `fn-auth-open-session-is-consistent`; 69 `(fn-peer-open-session archive nil nil nil)` -> 70 `(fn-auth-open-session archive nil nil nil (fn-auth-open-config) nil)`; 71 `fn-served-open-peer` gains `acfg` and `tlsp` and passes them through; 72 `fn-served-post-outcome`'s `(fn-peer-session-base (fn-served-conn-session c))` 73 gains an `fn-auth-session-base` inside it; and 74 `fn-served-typed-effect-enumeration-by-definition` (`:rule-classes nil`) 75 gains the `(fn-auth-starttls-effect)` disjunct. 76 - A new `fn-served-open-access` taking `acfg` and `tlsp` keeps `fn-served-open`'s 77 seven call sites (owner.lisp:591, owner-invariants.lisp:399, owner-tests 78 420/452/492, served-tests 67/373) untouched. 79 No served keystone STATEMENT changes; the proofs need `fn-peer-step` renamed in 80 their hints, which is what `books/peer-inbound.lisp` already did to 81 `books/nntp-post.lisp` when it was inserted. 82 83 **The owner bridge does not carry the STARTTLS effect.** `fn-owner-chunk` 84 publishes `fn-owner-closep` and `fn-owner-submittedp`; it needs a third, 85 `fn-owner-starttlsp`, set from `(fn-served-submission ...)`-style inspection of 86 the effect list in `host/owner-host.lisp` (`:program` mode, no proof), and 87 `tools/run_owner.py` then calls `sock = context.wrap_socket(sock, server_side=True)` 88 after flushing the 382. Until then the only TLS on the wire is the implicit 89 listener, which is what `tests/test_owner.py` exercises. **AUTHINFO is 90 therefore not reachable over a socket yet**: the credentials also need to reach 91 `fn-served-open-access` through the same bridge. 92 93 **`fn-served-feed` has one stopping condition.** RFC 4642 §2.2 makes STARTTLS 94 un-pipelineable and the handshake begin at the first octet after the 382's 95 CRLF, so octets after the command line in the same read are TLS bytes. The fold 96 would frame them as NNTP. `tools/run_owner.py` discards the remainder, which 97 §2.2 permits, but that is a host decision about octets. The fix is a second 98 stopping condition carried in the connection exactly as the closed wire is; the 99 effect to key on already exists. Recorded in `specs/nntp.md`. 100
The statement CLI loads toy cryptotools/stx.py · L1–54
File SHA-256 c0e15150c6127169c79085ccfecdd5bcc9722569cc4cefac5a2487895612f2b4
1 """The host side of the FN-Statement carrier. 2 3 Nothing here computes what ACL2 computes. `books/stx-carrier.lisp` owns the 4 detached encoding, the base64 layer, the field value, the payload projection 5 and the bounds; `books/stx-verify.lisp` owns the verdict and its rendering. 6 This module starts an ACL2 session over those books, marshals octets in and 7 out of it, and prints what the session decided. There is no second encoder 8 in Python, and no path here turns an exception into a verdict. 9 10 The crypto seam is constrained (`books/crypto-seam.lisp`): a digest and a 11 signature scheme with shape constraints and one correctness property, and no 12 deployed realiser. So the session attaches the TOY realisers of 13 `tests/acl2/crypto-seam-tests.lisp` -- a polynomial mix digest and a 14 sign-by-public-key scheme, neither cryptographic -- and every verdict this 15 tool prints is a statement about the codec and the composition, never about 16 unforgeability. `tools/crypto_host.py` is the host's one Ed25519 entry point 17 and is used by `sign --ed25519` to produce a real signature over the signing 18 preimage ACL2 computed, for the deployed profile; wiring that signature back 19 into an ACL2 verdict needs a digest realiser ACL2 can execute, which is D09's 20 open question (specs/substrate-transport.md, "What this design does not 21 decide"). Until it is answered, `verify` under the deployed profile is the 22 recipient's own business and this tool reports the toy-realiser verdict only, 23 saying so on every line. 24 25 Exit codes keep the three outcomes distinct (D13): 26 0 verified 27 3 unverified 28 4 absent 29 2 the tool could not reach a verdict at all (never an outcome) 30 """ 31 32 from __future__ import annotations 33 34 import argparse 35 import os 36 import subprocess 37 import sys 38 39 ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) 40 sys.path.insert(0, ROOT) 41 42 from tools.run_store import ( # noqa: E402 43 PROMPT, StoreError, acl2_result, decimal_list, read_prompt, 44 ) 45 46 EXIT_VERIFIED = 0 47 EXIT_UNVERIFIED = 3 48 EXIT_ABSENT = 4 49 EXIT_NO_VERDICT = 2 50 51 _BOOKS = ( 52 '(include-book "tests/acl2/crypto-seam-tests")', 53 '(include-book "books/stx-invariants")', 54 )
The Ed25519 signature is emitted separatelytools/stx.py · L131–148
File SHA-256 c0e15150c6127169c79085ccfecdd5bcc9722569cc4cefac5a2487895612f2b4
131 ":" + args.kind, octet_form(payload))
132 value = session.octets("(fn-stx-header-value {})".format(statement))
133 if not value:
134 raise SystemExit("ACL2 refused the statement: no field value")
135 preimage = session.octets(
136 "(fn-stmt-signing-preimage (fn-stmt-header {}))".format(statement))
137 finally:
138 session.close()
139 sys.stdout.write("FN-Statement: " + value.decode("ascii") + "\r\n")
140 sys.stderr.write("creator {}\n".format(creator.hex()))
141 if args.ed25519:
142 from tools import crypto_host
143 signature = crypto_host.sign(seed, preimage)
144 sys.stderr.write("ed25519 signature over the ACL2 signing preimage: {}\n"
145 .format(signature.hex()))
146 sys.stderr.write("not attached: the deployed digest realiser is open (D09)\n")
147 return 0
148Peering integration and recorded certification boundaryplanning/lanes/HANDOFF-w9-peering-e2e.md · L32–83
File SHA-256 3fde221cf12c38d26646b463e9205d483f86bb151907e9be07c81ba7a2a4bf69
32 `(fn-sched-eligiblep (fn-sched-find w (fn-sched-queue ss)) wf)` in its 33 *statement* and `fn-sched-aging-bound` (KEYSTONE) reaches the same test 34 through `fn-sched-eligible-runp`. Both are peer-blind. Filter selection by 35 the open contact's peer and a promoted work for another peer is neither 36 selected nor moved closer to the head of the promotion queue: the 37 conclusions fail on a reachable state, and the only repair is an extra 38 argument to a function named inside a keystone. `books/scheduler-peers.lisp` 39 keys one scheduler per peer instead; the aging bound is then per contact, 40 which is what it always meant. 41 42 ## Certification 43 44 | Root | State | Evidence | 45 | --- | --- | --- | 46 | `books/scheduler-peers` | certified | persvati `run-20260920T180716Z-da35`, `build/acl2/certify-20260920T180719Z-1531308` | 47 | `tests/acl2/scheduler-peers-tests` | certified | same run | 48 | `books/owner`, `books/owner-invariants` | **uncertified, blocked on two other lanes' open forms** | persvati `run-20260920T180927Z-a8a4`, evidence `build/acl2/certify-20260920T180931Z-1553858`: 29 books published, and exactly six failed. Two are root causes and neither is this lane's: `books/peer-config` fails at `DEFTHM FN-CFG-SET-PEER-DELTA-IS-ADMISSIBLE` (the form w6/peering-inbound-2 has already closed on its own branch, commit `3901d55`, not yet on dev) and `books/nntp-effects` has no certificate on dev: in THIS run it was cut by the runner's 1800 s per-invocation cap (its log stops inside the XOVER block), and in w5/owner-followups' 5400 s run it reached a real verdict and FAILED at `fn-nntp-hdr-labelled-line-is-block-text`. So the owner is not one long run away: `books/nntp-effects` is genuinely open and `books/peer-inbound` includes `nntp-post` which includes it. The other four are cascades: `peer-inbound` and `nntp-post` include them, `served` includes `peer-inbound`, `owner` includes `served`. **Nothing in this lane's owner edits has been refuted or confirmed**; the books were never reached. | 49 | everything else touched | host and Python only | `make check` green; `tests/test_twonode_gate.py` 19 tests pass | 50 51 `tests/test_inn_lab.py` has one error on this branch and the SAME error on 52 dev (`inn_start`, the nnrpd pid parse, `IndexError`): pre-existing, verified 53 by running it in the dev checkout, not caused by this lane. 54 55 ## What is open, with the obligation each needs 56 57 1. **Certify the owner, after merging the two open forms.** Merge 58 `w6/peering-inbound-2` (its `fn-cfg-set-peer-delta-is-admissible` fix) and 59 whatever closes `books/nntp-effects`, then 60 `python3 tools/farm.py submit persvati --jobs 6 --remote-root 61 /home/ember/fn-lanes/w9-peering-e2e --closure books/owner 62 books/owner-invariants tests/acl2/owner-tests`. Until those two land, no 63 run of this lane's owner books can reach a verdict: they are six levels 64 above the failing forms. Use `--timeout-seconds 5400` when you do: 65 `books/nntp-effects` needs about 2600 s and is cut silently at the default 66 1800 s, which reads as "no certificate" rather than as a failure. 67 2. **Teeth for the owner's three transit theorems.** `tests/acl2/owner-tests.lisp` 68 needs a transit submission witness (a `(:transit peer kind msgid octets)` 69 at the head of the queue) and one violating value per hypothesis. Not 70 written in this lane. 71 3. **Milestone 3, the feed driven by the owner.** `books/peer-feed.lisp` had 72 not landed on dev at this HEAD. When it does: `fn-own-tick` runs 73 `fn-sched-table-tick` for each configured peer and `fn-feed-tick-step` 74 under that peer's contact, with the FNFD journal durable before the offer, 75 and `tools/run_feed.py` folded into the owner's loop (one process, one 76 store lock). 77 4. **Milestones 4 and 5's outbound halves.** K5's crash-replay evidence and 78 the gate's A-feeds-B scenario both need the feed; today the gate's 79 offering side is its own socket client, which is stated in the evidence 80 and in the spec's status table. 81 5. **K6 and K7.** K6 restates `books/ideal.lisp`'s robustness theorems over 82 the peer event kinds (`(:open id peer)`, `:feed-octets`, `:tick`); K7 is 83 `fn-cfg-peer-delta-preserves-the-node-and-changes-only-decisions` at the
A counterexample discovered in the crash modelplanning/lanes/HANDOFF-w9-storage.md · L17–52
File SHA-256 5f0e3ba957a7384c8e38ae874f734642f4388ec2c33ed745c407b41c622cd20d
17 ## What landed 18 19 ### 1. `fn-bs-splice-composition`, and the model defect it found 20 21 The obligation recorded against the open `fn-bs-view-is-an-admissible-image` 22 was the splice-composition lemma. It is proved: 23 24 ```lisp 25 (defthm fn-bs-splice-composition 26 (implies (and (natp offset) (true-listp a)) 27 (equal (fn-bs-splice (fn-bs-splice old offset a) (+ offset (len a)) b) 28 (fn-bs-splice old offset (append a b))))) 29 ``` 30 31 with the nothing-lost choice list `fn-bs-view-choices` and its admissibility 32 (`fn-bs-view-choices-are-choices`), which is the witness the view theorem's 33 `fn-bs-crash-imagep-suff` needs. 34 35 **The view theorem was false as stated.** A pending `(:write ino offset NIL)` 36 has `fn-bs-unit-count` 0, so a crash tears it into no pieces at all, while 37 `fn-bs-apply-op` splices it and zero-extends the inode when the offset is 38 past the end. The view of `(:byte-store 4 ((0)) NIL ((:write 0 5 NIL)) 1)` 39 is five zero octets and no crash image of that state has them. `write(2)` 40 of zero octets changes nothing on POSIX and `fn-bs-write` issues no 41 operation for it, so `fn-bs-statep` now carries `fn-bs-writes-nonemptyp`, 42 with the eight preservation facts beside the `fn-bs-writes-knownp` ones. 43 44 `fn-bs-view-is-an-admissible-image` is **still open**, not weakened, with a 45 smaller obligation stated at its place in `byte-store-invariants` and in 46 `specs/crash-model-v2.md` §7: two index facts (A1, A2) relating the i-th 47 unit piece to the octets pieces 0..i-1 covered. The induction on `i` is 48 right and its step is the composition lemma; it exhausts a 2,000,000 step 49 limit re-deriving A1 and A2 inside every branch of `fn-bs-tear-write`. State 50 them as `:linear` rules over a named `fn-bs-piece-start` and open the tear 51 by `:expand`. 52
Model-backed campaign and exact limitsplanning/evidence/campaign-model-images-2026-09-20.md · L1–69
File SHA-256 5383ed44dfd58d1e5d6e16a727ed0f5a75bf67033f0d1e13b4ec83385d8499ce
1 # Campaign against the byte model: first run 2 3 The fault campaign has always compared a recovered store with a *reference 4 run*. This run is the first in which the **byte-level crash model** says 5 what the recovered store is allowed to be, per cut, and the campaign checks 6 it. Every number below is a wall time on one host under its load at that 7 moment; none of it is a proof. 8 9 ## What ran 10 11 | fact | value | 12 | --- | --- | 13 | lane | `w9/storage`, branch `w9/storage` from `dev` at `52eb0db` | 14 | host | hbox (the coordinator moved proof and campaign work there: persvati was at load 9 with five queued runners and zero ACL2 processes) | 15 | tree | `/tank/fn/lanes/w9-storage` | 16 | acl2 | `/tank/fn/acl2-8.7/saved_acl2`, ACL2 8.7 | 17 | books | `books/byte-store`, `books/byte-store-invariants`, `books/byte-store-programs` certified: `build/acl2/certify-20260920T182231Z-1039695` | 18 | command | `FN_ACL2=... swarm-build python3 tests/campaign/campaign.py --quick --scenario cross-post --model-images --json build/probe/campaign-model.json` | 19 | result | `pairs=7 failures=0 seconds=97.4` | 20 | output | `/tank/fn/lanes/w9-storage/build/probe/campaign.out` | 21 22 ## What the model said, per cut 23 24 The model state at a cut is `fn-bs-run` over the program constant of 25 `books/byte-store-programs.lisp` applied to the imported template store; the 26 admissible set is the record counts over the whole-or-lost choices of that 27 state's pending list. 28 29 | cut | pending at the cut | model admits | host recovered | 30 | --- | --- | --- | --- | 31 | `store:frontier-staged-durable` | `(:SET-ENTRY :STAGING ".allocation-campaign" 8361628)` | `(1)` | 1 | 32 | `store:frontier-replaced` | the staging name, `(:SET-ENTRY :ROOT "allocation-frontier.json" ...)`, `(:DEL-ENTRY :STAGING ...)` | `(1)` | 1 | 33 | `store:record-staged-durable` | `(:SET-ENTRY :STAGING ".stage-campaign" 8361628)` | `(1)` | 1 | 34 | `store:record-linked` | the staging name and `(:SET-ENTRY :TRANSACTIONS "campaign.txn" ...)` | `(1, 2)` | 2 | 35 | `store:record-attempted` | the same two | `(1, 2)` | 2 | 36 | `store:recover-barrier` | `NIL` | `(1)` | 1 | 37 38 `store:finish-durable` is the seventh pair and is not model-checked: 39 `Store.finish` issues no syscall, so `PROGRAM_OF` maps no program to it. 40 41 **The check is not vacuous.** Four of the six sets have exactly one element, 42 so at those cuts the model pins the recovered record count rather than 43 permitting it: had the host published a record at `record-staged-durable`, 44 where `os.link` has not run, the run would have failed. The two two-element 45 sets are precisely the crash window the cut table describes in prose -- 46 `final-link` issued and its reply not yet observed -- now computed from the 47 pending `:set-entry` on `:transactions` instead of asserted. 48 49 ## What this run does NOT establish 50 51 * **It is the namespace envelope, not §5.1's check.** The recovered record 52 *octets* are not compared with what the killed process staged: that needs 53 `fn-bs-scan-store`, which is K1 and open. What is compared is the record 54 count, which is what `fn-sf-crash-imagep` constrains. 55 * **It enumerates rather than decides.** `fn-bs-image-admissiblep` and 56 `fn-bs-image-admissiblep-iff-crash-imagep` (§1.5) are open, so the harness 57 enumerates the whole-or-lost choices of the pending list. Torn *selections* 58 within a write are not enumerated; §5.2's variant injector is the packet 59 that produces them on disk, and it was not written. 60 * **No known-bad host was run.** §5.5's `FN_CAMPAIGN_UNSAFE=link-before-fsync` 61 demonstration -- the campaign-level tooth for K1 -- is not implemented. 62 The singleton sets above are an argument that the check constrains, not a 63 demonstration that it fails when the host is wrong. 64 * **Seven pairs, one scenario, `--quick`.** `bp-receive`, `bp-retry`, 65 `capacity-refusal` and `sender-enqueue` were not run with this flag, and 66 their journal and inbox cuts have model programs now but no `PROGRAM_OF` 67 entry, because a journal has no `fn-sf` kernel and the count the check 68 compares is the store's. 69 * **Process death only.** No power-loss claim, on this or any run.
Native TCPCL is not yet the native BP nodeplanning/lanes/HANDOFF-w9-dtn-e2e.md · L51–100
File SHA-256 d8b1266187d6f322e97e4e41a3075db836195775f82badebeb57a0960c64f423
51 - **Still open, with the exact obligation**: the *outbound* suffix. See 52 `specs/tcpcl.md` §6 — `fn-tcl-outboundp` measures `remaining`, so a send is 53 still quadratic, and closing it needs guard-total `fn-tcl-take`/`fn-tcl-drop` 54 or a carried `(len remaining)`. Named, not attempted. 55 56 `tools/tcpcl_lab.py` gains a sixth scenario, `profile`: the same transfer at 57 64 KiB and 256 KiB, and the verdict is the *ratio* (a quadratic guard gives 58 about 16x for a 4x transfer; the bar is 8x), so a loaded box moves both 59 measurements together. `tools/twonode_gate.py`'s `tcpcl` scenario runs it. 60 61 ## 2. What did not land, and what it needs 62 63 **The BP node over TCPCL did not land.** Saying why precisely, because the 64 gap is not where the brief assumed: 65 66 - `books/bp-node.lisp` does not exist. `fn-bpn-receive`, `fn-bpn-step` and the 67 FNBS record family are [specs/bp-design.md](../../specs/bp-design.md) §1.3–1.5 68 — a design with exact statements, explicitly "not a claim". So is 69 `books/bp-bundle.lisp` (§1.4): **nothing in this tree encodes or decodes a 70 BPv7 bundle**, only its primary block (`books/bp-primary-cbor`). The 71 convergence layer therefore carries opaque octets, and `:bundle-received` 72 is a file in a spool directory, not a bundle record. 73 - The receiver entry that does exist is `host/bp-receipt-host.lisp` 74 (`fn-bpr-host-accept`, `-prepare-receipt`, `-commit-receipt`, 75 `-receipt-adu`) over `books/bp-receipt`, and it is driven today by 76 `tools/run_bp_receive.py` with an FNRJ journal — Python, not the image. 77 Wiring it into the image means the receiver state and its journal move into 78 the native host, which is the packet `host/bp-receive-host.lisp` + 79 `tools/receipt_journal.py` currently stand in for. 80 - `books/scheduler`, `books/bp-release` and `books/relay` are certified and 81 have the keystones the brief wants cited, but none of them is in the image's 82 build list ([host/native/build.lisp](../../host/native/build.lisp)). 83 84 So `bp send` / `bp receive` / `bp status` are not verbs on the image. Adding 85 them honestly is three packets, in this order: (a) `books/bp-bundle` per 86 bp-design §1.4, so that a transfer's octets are a bundle and not a blob; 87 (b) the FNBS record family in `books/frame` and the receiver state in the 88 image; (c) the scheduler's contact window as the gate on `fn-tcl-host-send`. 89 Doing (c) alone — a contact check around the existing `tcpcl send` — would 90 have been a verb that looks like a BP node and is not one. 91 92 ## 3. Evidence: the image builds and the layer runs 93 94 [`planning/evidence/tcpcl-dtn-w9-2026-09-20.md`](../evidence/tcpcl-dtn-w9-2026-09-20.md). 95 w8's record had to say "the image did not build, so none of the five 96 scenarios ran". On hbox, under `swarm-build`: 97 98 - `books/tcpcl-session` certifies with this change in **156.17 s**. 99 - **`build/fn-host-dtn` builds** — `host/native/build-dtn.lisp`, the DTN-only 100 variant of the build list (no `books/served`, no `books/nntp-effects`, no
Native TCPCL and dtn7 laboratory evidenceplanning/evidence/tcpcl-dtn-w9-2026-09-20.md · L1–210
File SHA-256 5ab3800843321ed847962ba27ab8d8f10efccb722be4be30cbbd32e03d4b6f54
1 # Evidence: TCPCLv4 on the native image, and dtn7-rs interop (w9/dtn-e2e)
2
3 Date 2026-09-20. Branch `w9/dtn-e2e` at `6024fe9` (dev merged to `d50c392`).
4 Box **hbox**, everything under `swarm-build`. ACL2 8.7
5 (`/tank/fn/acl2-8.7/saved_acl2`, "This is SBCL 2.6.8"); the system SBCL that
6 built the image is `SBCL 2.2.9.debian`. Remote root
7 `/tank/fn/lanes/w9-dtn-e2e`, certificate cache `/tank/fn/certcache`.
8
9 **Headline, against w8's:** the native image builds and the convergence layer
10 runs. `planning/evidence/tcpcl-9cbf301-2026-09-20.md` had to report "the image
11 did not build, so none of the five scenarios ran". Six scenarios now run and
12 pass, and fn holds a TCPCLv4 session with dtn7-rs.
13
14 ## 1. What certified
15
16 | Book | Result | Wall | Evidence |
17 | --- | --- | --- | --- |
18 | `books/tcpcl-session` (this lane's change) | certified | 156.17 s | `build/acl2/certify-20260920T180207Z-1003414/books--tcpcl-session.certify.log` |
19 | `books/anchor`, `-record`, `-invariants` + closure | certified | one run | `build/acl2/certify-20260920T181319Z-1024059` |
20 | `books/tcpcl-invariants`, `tests/acl2/tcpcl-tests` | see §6 | | `build/farm/w9inv.log` |
21
22 Command: `FN_ACL2=/tank/fn/acl2-8.7/saved_acl2 FN_ACL2_TIMEOUT_SECONDS=3600
23 FN_CERT_CACHE=/tank/fn/certcache swarm-build python3 tools/certify_books.py
24 --jobs N <roots>`; the farm submit that installed the cache was
25 `run-20260920T180202Z-01c4` (`installed 180, kept 0, uncached 57`).
26
27 ## 2. The image
28
29 `build/fn-host-dtn`, 261,809,336-octet core, built by
30
31 ```sh
32 FN_ACL2=/tank/fn/acl2-8.7/saved_acl2 \
33 FN_NATIVE_BUILD=host/native/build-dtn.lisp \
34 FN_NATIVE_IMAGE=build/fn-host-dtn \
35 FN_NATIVE_LOG=build/native-host-build-dtn.log \
36 swarm-build sh tools/build_native_host.sh
37 ```
38
39 **This is the DTN-only image, not the deployment image**, and the difference
40 is stated because it bounds every claim below. `host/native/build-dtn.lisp`
41 omits `books/served`, `books/nntp-effects`, `host/reader-host.lisp` and
42 `host/native/reader-model-host.lisp`; the `reader` and `model` verbs are
43 therefore absent and fault at `fnn-call` with "ACL2 executable counterpart
44 missing". Every book the convergence layer needs is present and certified.
45 The two reader books were not red on this tree -- the `w6/nntp-effects` fix
46 is in -- they were queued behind a saturated slot pool on a box carrying five
47 other lanes' runs, and the DTN path does not read news.
48
49 Its first attempt failed honestly: `books/anchor-invariants` and
50 `books/anchor-record` were uncertified in this directory (`host/anchor-host.lisp`
51 includes them and they were in no submit list), and
52 `tools/build_native_host.sh` refused the image on the `Uncertified` marker
53 rather than building one.
54
55 ## 3. The lab: six scenarios, all passing
56
57 `swarm-build python3 tools/tcpcl_lab.py --image build/fn-host-dtn --work
58 /tank/fn/lanes/w9-dtn-e2e/build/tcpcl-lab3`, one JSON row per scenario:
59
60 ```json
61 {"a_holds_b_bundle": true, "acks_from_a": 2, "acks_from_b": 2, "active_events": 8, "active_outcomes": [["accepted", "xfer=0 path=/tank/fn/lanes/w9-dtn-e2e/build/tcpcl-lab3/exchange/active-spool/active-0.bundle"], ["accepted", "outbound xfer=0"]], "b_holds_a_bundle": true, "contact": 1, "listener_rc": 0, "ok": true, "passive_events": 10, "passive_outcomes": [["accepted", "xfer=0 path=/tank/fn/lanes/w9-dtn-e2e/build/tcpcl-lab3/exchange/passive-spool/passive-0.bundle"], ["accepted", "outbound xfer=0"]], "scenario": "exchange", "segments_to_a": 2, "segments_to_b": 2, "sender_rc": 0, "sess_init": 1, "sess_term": 1, "trace_bytes": 1691}
62 {"ok": true, "outcomes": [["refused", "outbound reason=exceeds-transfer-mtu"]], "scenario": "refused", "segments": 0, "sender_rc": 1, "staged": []}
63 {"active_keepalives": 3, "active_terms": 0, "ok": true, "passive_keepalives": 3, "passive_terms": 0, "scenario": "keepalive", "still_up": true}
64 {"acks_before_kill": 24, "durable_after": true, "durable_before": true, "first_rc": 0, "interrupted_absent": true, "no_partials": true, "ok": true, "reconnect_rc": 0, "scenario": "crash", "staged": ["passive-0.bundle"]}
65 {"large": {"intact": true, "rc": 0}, "large_octets": 262144, "large_seconds": 0.146, "ok": true, "ratio": 1.38, "scenario": "profile", "size_ratio": 4, "small": {"intact": true, "rc": 0}, "small_octets": 65536, "small_seconds": 0.106}
66 {"equal": true, "loop_events": 9, "model_events": 9, "ok": true, "rc": 0, "received": ["passive-0.bundle"], "scenario": "replay", "sender_rc": 0}
67 {"failed": [], "ok": true, "passed": ["exchange", "refused", "keepalive", "crash", "profile", "replay"], "scenario": "summary"}
68 ```
69
70 Read against the brief's list: **a bundle each way over loopback** with
71 contact, SESS_INIT, a two-segment transfer and two acks in each direction and
72 a clean SESS_TERM (`exchange`, and both spools hold the peer's octets byte for
73 byte); **a refused transfer** at the peer's Transfer MRU, with zero segments
74 sent, nothing staged and exit 1, distinct from accepted and from uncertain
75 (`refused`); **`kill -9` of the receiver mid-transfer, then reconnect**, with
76 the interrupted transfer absent, no partial staging name left behind, the
77 earlier acknowledged transfer still durable and a new transfer landing after
78 the restart (`crash`, the kill after 24 acknowledged segments); keepalives on
79 the negotiated interval with no spurious termination (`keepalive`); and the
80 model differential (`replay`, §5).
81
82 **A receipt round trip is NOT in this list.** `exchange`'s reverse transfer is
83 a second bundle, not a receipt: no receipt is generated, because
84 `books/bp-receipt` is not in this image and `:bundle-received` is a file in a
85 spool, not an ADU handed to `fn-bpr-host-accept`. Saying "a bundle each way"
86 is what the evidence supports; see HANDOFF-w9-dtn-e2e §2 for what a receipt
87 round trip needs.
88
89 ## 4. The profile: the served-path guard is no longer quadratic
90
91 `profile` sends the same shape of transfer at 64 KiB and at 256 KiB and
92 reports the ratio, because a loaded box moves both measurements together:
93
94 - 65,536 octets: **0.106 s**
95 - 262,144 octets: **0.146 s**
96 - ratio **1.38** for a 4x transfer (bar: below 8x)
97
98 The guard `fn-tcl-drive` names is checked once per socket chunk by its ACL2
99 executable counterpart. While it was `fn-tcl-sessionp`, that check walked
100 every octet staged so far, so a 4x transfer meant 4x the chunks *and* 4x the
101 walk -- about 16x the guard work. 1.38x is that cost gone; the residue below
102 4x is the per-run process startup, which both measurements pay once. The
103 `crash` scenario's 120,000-octet transfer, which w8 had to keep small for this
104 reason, now takes 24 acknowledged segments without difficulty.
105
106 The covered scope: this is one box, one loopback, one segment MRU (4096) and
107 one pair of sizes. It is a refutation of a quadratic guard, not a throughput
108 number, and the send direction is still quadratic (`specs/tcpcl.md` §6).
109
110 ## 5. A finding: the model differential is only sound for a receive-only session
111
112 The first run of the lab -- the first run there has ever been -- failed
113 `replay` at
114
115 ```
116 '(:OUTBOUND-SENT 0 "passive")' != '(:SEND :MSG-REJECT 3)'
117 ```
118
119 `tcpcl replay` folds `fn-tcl-drive` over the trace and calls nothing else, so
120 it models a node that consumes octets and never originates a transfer. A node
121 that also sends reaches `fn-tcl-send` and `fn-tcl-pump` from the *host*, not
122 from the wire; the replay's state has no outbound transfer when the peer's
123 XFER_ACK arrives, and the machine is right to answer MSG_REJECT Unexpected
124 where the loop said `:outbound-sent`. `exchange`'s listener carries a reply
125 bundle, so its trace was never a valid subject for this differential.
126
127 The trace was wrong, not the machine. `scenario_replay` now drives its own
128 receive-only listener and the differential holds exactly (9 loop events, 9
129 model events, equal). Extending it to a sending node means putting the host's
130 aux calls into the trace; that is not done and is recorded open.
131
132 ## 6. dtn7-rs interop: one bundle each way
133
134 dtn7-rs **0.21.0** at revision `4daf02d7ea927e9293753b2a5c4497457f6e5a40` --
135 the revision `tests/bp-dtn7/pin.json` names -- cloned to `/tank/fn/dtn7/repo`
136 on hbox and built with `cargo build --release --locked` under `swarm-build`
137 (cargo 1.98.0-nightly). Its `tcp` convergence layer is RFC 9174:
138 `core/dtn7/src/cla/tcp/proto.rs` carries the Table 2 message types and the
139 CAN_TLS contact-header flag.
140
141 `python3 tests/bp-dtn7/run_fn_tcpcl_interop.py --image build/fn-host-dtn
142 --dtn7-repo /tank/fn/dtn7/repo`, `ok: True`.
143
144 **Leg 1, dtn7 to fn.** `dtnsend -r dtn://fn-b/incoming` gave
145 `Bundle-Id: dtn://dtn7x/-843243606819-0`, `Result: Sent 109 bytes`.
146 dtn7 dialled fn's listener; fn's own event digests, computed in ACL2 by
147 `fn-tcl-host-event-digests`:
148
149 ```
150 passive event (:SEND :CONTACT 6)
151 passive event (:SEND :SESS-INIT 36)
152 passive event (:SESSION-UP 10 64000 64000 NIL)
153 passive event (:SEND :XFER-ACK 18)
154 passive event (:BUNDLE-RECEIVED 1 126)
155 passive event (:SEND :SESS-TERM 3)
156 passive event (:PEER-TERMINATING 1)
157 passive event (:CLOSE)
158 ```
159
160 fn negotiated a 64,000-octet segment and transfer MTU with TLS false,
161 acknowledged the transfer, and staged **126 octets** as `passive-1.bundle`. Exit 0.
162
163 **Leg 2, fn to dtn7**, returning the same octets:
164
165 ```
166 active aux (:SEND :CONTACT 6)
167 active event (:SEND :SESS-INIT 36)
168 active event (:SESSION-UP 10 64000 64000 NIL)
169 active aux (:SEND :XFER-SEGMENT 148)
170 active event (:OUTBOUND-SENT 0 "active")
171 active aux (:SEND :SESS-TERM 3)
172 active event (:PEER-TERMINATING 0)
173 active event (:CLOSE)
174 ```
175
176 dtn7's side of leg 2, verbatim:
177
178 ```
179 Bundle dtn://dtn7x/-843243606819-0 already in store, updating it!
180 Dispatching bundle: dtn://dtn7x/-843243606819-0
181 Bundle contains an hop count block: dtn://dtn7x/-843243606819-0 32 2
182 Previous Node Block was updated: dtn://dtn7x/ dtn://dtn7x/
183 Transmission time: 29.337us for 1 bundles in 126 bytes to 127.0.0.1:54298
184 Session terminated for dtn://fn-a/ (127.0.0.1:54298)
185 ```
186
187 So dtn7 completed the contact exchange and SESS_INIT with fn, accepted fn's
188 XFER_SEGMENT, decoded the 126 octets as a BPv7 bundle, recognised it as its
189 own, and dispatched it. fn's `:INBOUND-REFUSED 1 6` on that last line is
190 correct and not a failure: dtn7's epidemic router immediately tried to forward
191 the bundle back, and fn had already sent SESS_TERM, so the machine refused
192 with Table 6 reason 6, Session Terminating.
193
194 **What this does and does not show.** It shows two independent RFC 9174
195 implementations holding a session and moving a bundle in each direction, with
196 fn's every protocol value computed in ACL2. It does **not** show fn
197 originating a bundle: nothing in this tree encodes BPv7
198 (`specs/bp-design.md` §1.4 is a design), so **dtn7 authored the bundle in both
199 directions** and fn carried it back as a convergence-layer peer. A bundle fn
200 composed would fail dtn7's parser today. That is the honest boundary of this
201 interop and the reason the harness is written the way it is.
202
203 ## 7. Limits
204
205 - The image is the DTN-only variant (§2), so no NNTP claim follows from it.
206 - `books/tcpcl-invariants` and `tests/acl2/tcpcl-tests` were still queued
207 against this text when this record was written; the keystones C1 to C4 are
208 unchanged in statement and `books/tcpcl-session` certifies, but the
209 invariants book has no verdict on this tree until `build/farm/w9inv.log`
210 has one. Recorded as open, not as passed.ION carries fn over LTP, with a destructive receive seamtests/evidence/2026-09-19-ltp-feasibility.md · L1–85
File SHA-256 4d8421157077b78c8cf90797ebd45d589ae633c071ce9b17e9f9f89494e6c939
1 # LTP feasibility with a real second BPA — 2026-09-19 2 3 A pinned [ION-DTN](https://github.com/nasa-jpl/ION-DTN) build carried an actual 4 fn request ADU across an LTP convergence layer, and fn's own receiver accepted 5 it. The [machine-readable record](2026-09-19-ltp-feasibility.json) holds the 6 revision, commands, parameters and digests; every configuration file is in 7 [`tests/ltp/`](../ltp/README.md) with its [pin](../ltp/pin.json). 8 9 ION was built from tag `ion-open-source-4.2.1-a.1`, commit 10 `4912bf82de7d03a9a11bf5d68614cc002f9ac911`, with `autoreconf -fi`, 11 `./configure --prefix=…`, `make -j8`, `make install` on Linux/gcc 14.2.0. Two 12 nodes `ipn:1` and `ipn:2` ran in separate working directories with distinct 13 shared-memory keys and SDRs, joined by one LTP span each way over UDP on IPv4 14 loopback with `ltpclock`, `ltpmeter`, `ltpdeliv`, `ltpcli` and `ltpclo`. 15 `bpsource`/`bpsink` confirmed BP-over-LTP before any fn code ran. 16 17 ## What ran 18 19 The fn sender work is the same ACL2 path as the pinned TCP laboratory: an 20 article is published, work is enqueued, attempt intent is persisted, and ACL2 21 projects the 449-octet request ADU. That ADU crossed the LTP link, was durably 22 staged, and arrived byte-identical 23 (`781f7ff3…4016` both sides). `tools/run_bp_receive.py`'s 24 `receive_bpa_request` then accepted it: outcome `accepted`, one record, one 25 article, one pin, exact article bytes, receipt `f2cbfa70…ae3e`, and the staged 26 copy deleted only after the receipt decision was durable. 27 28 Interruption: with a 60,000-octet ADU in flight and a 600-second lifetime, the 29 receiving node's LTP/UDP datagrams were dropped for 20 seconds with one named 30 `iptables` rule. Nothing was staged while the link was cut. After the rule was 31 deleted, LTP resumed the session, repaired the lost red-part segments, and the 32 ADU arrived byte-identical to its source. 33 34 Expiry: a 4,096-octet ADU with a 20-second bundle lifetime was submitted into a 35 60-second outage. Forty-five seconds after the link returned, nothing had been 36 staged. The bundle's lifetime had elapsed and ION deleted it. The fn job and its 37 archive obligation are untouched by that transport event. 38 39 ## What did not run, and why 40 41 No receipt was returned over BP. ION gives the sending application no bundle 42 identifier — `bpsendfile` prints none and `bp_send()` returns only an in-process 43 `SdrObject` address — so fn's durable attempt record has no observed transport 44 handle to bind. The return leg is blocked behind that seam rather than faked. 45 46 ## The receive seam 47 48 **ION has no non-destructive receive.** `bp_receive()` deletes the endpoint's 49 delivery-queue element, zeroes `bundle.payload.content`, destroys the bundle and 50 commits the SDR transaction *before it returns*. Afterwards nothing redelivers 51 the ADU, and `bp_release_delivery(&dlv, 0)` only leaks the ZCO without making it 52 reachable through any endpoint API. `bprecvfile` compounds this: it writes 53 `testfile<N>` with no `fsync`, no rename into place, and a per-process counter 54 that restarts at 1, so a restarted receiver silently overwrites earlier files. 55 Neither is restart-safe in fn's sense. 56 57 `tests/ltp/fn_ltp_stage.c` is the app-level staging copy this forces: take 58 delivery, write the exact ADU to a temporary file, `fsync`, rename into the 59 staging directory under the RFC 9171 bundle id, `fsync` the directory, then 60 release. `IonStagingInbox` serves fn's `inventory`/`download`/`delete` from that 61 copy, so `delete` removes fn's own staged file, never an ION-retained bundle. 62 The window between `bp_receive()`'s commit and the staging `fsync` cannot be 63 closed with ION's public API; it is covered at the fn layer by durable sender 64 work, retry and receipt idempotence, not by the BPA. 65 66 ## Delegated behavior and its limits 67 68 ION owns LTP session establishment, 1,200-octet red-part segmentation, 69 checkpoints, reception reports, retransmission, bundle lifetime expiry, and BP 70 forwarding over the contact/range plan. Its repair is budgeted: `maxTimeouts 5` 71 and `maxRepairRounds 8` per session. An outage longer than that budget cancels 72 the session, and BPv7 has no custody transfer to recover it, so recovery falls 73 back to fn's own durable retry. `sessionInactivityLimit` was 0 and one-way light 74 time was 0 in this profile. 75 76 ## What this is not 77 78 Not interoperability qualification: one implementation, one host, one profile, 79 no cross-implementation vectors and no errata audit. Not a mission profile: IPv4 80 loopback UDP, zero light time, no contact plan, no asymmetry, no rate limiting. 81 No BPSec, no authenticated peer, no author signature; the A_POLICY premise is an 82 explicitly trusted local test input. No power-loss or physical-media 83 qualification. No relay topology, backpressure campaign or staging-quota 84 exhaustion. LTP reliability is not fn application acceptance, and none of these 85 results closes `REP-006` or `SCN-010/017`.
Restart and receipt evidence over mock BPA transporttests/evidence/2026-09-20-four-node-lab.md · L1–24
File SHA-256 ab265ea9e953265fdef8e01a496d668b030a894c1afe2d7e95e62123831a28cd
1 # Four-node carried-media lab, 2026-09-20 2 3 A complete run. `tests/evidence/2026-09-20-four-node-lab.json` is the machine 4 record the lab wrote; this file says what it means. It replaces the incomplete 5 run w3/media-lab recorded on the same date, whose stop is described and closed 6 in `planning/lanes/HANDOFF-w6-workflow-restart.md`. 7 8 - Command: `python3 tests/bp-dtn7/run_four_node_lab.py`, mock BPA transport, 9 Python 3.14.7, ACL2 through `tools/run_store.py` against the certificates 10 installed in this worktree. Revision is in the JSON. 40.5 s. 11 - Status: **passed**. All twenty-two named assertions were checked and every 12 one held, including the eleven the earlier run never reached: the carried 13 media hop with no contact window, the lost-and-regenerated receipt, the 14 expired attempt and its retry under the next window, the reordered pair with 15 a duplicate, and each node's local-number frontier. 16 - Two defects were fixed to get here, neither in the workflow model. The lab 17 submitted an attempt whose `bp-lifetime` was not the configured one, which 18 `fn-bp-record-contextp` refuses; and `tests/bp-dtn7/mock_bpa.py` spooled its 19 queue under a digest of the BID, so the queue was not first in, first out and 20 the reordered-pair assertion read the wrong order. Nothing in the ACL2 21 acceptance path was relaxed. 22 - `relay_a_onward_obligation_recoverable_after_kill` is back and holds: after 23 the mid-forward SIGKILL of relay-a and its recovery, `work:a1:relay-a` reads 24 `outstanding`, and `absent` now means only that the image holds no such work.
Statement authority: certified parts and open joinsplanning/lanes/HANDOFF-w9-substrate.md · L1–94
File SHA-256 c7a2a17f1bd9d26e2cdcab7ba947dc077384fcbf66647900aa32b551a9ba70b7
1 # w9/substrate — the lace projected, the index, policy on transit, epochs 2 3 Branch `w9/substrate`, worktree `build/lanes/w9-substrate`, from `dev` 4 `52eb0db` with `w7/substrate-s1-2`'s three stx fixes cherry-picked (that 5 lane's work is not merged to `dev` yet; this lane builds on its names and 6 its certificates, and its registry commit `79cb367` is left to it). 7 8 Packets S2 to S5 of [substrate transport](../../specs/substrate-transport.md); 9 SUB-002 to SUB-005; PRF-021 to PRF-025. The status table is 10 [section 10 of the spec](../../specs/substrate-transport.md#10-status-packets-s2-to-s5-lane-w9substrate) 11 and it is where every open obligation is written with its exact shape. 12 Certification evidence: `tests/evidence/2026-09-20-substrate-transit.md`. 13 14 ## Certification, hbox, ACL2 8.7 15 16 `books/stx-lace` and `books/stx-policy` are **certified** 17 (`run-20260920T181308Z-0b1b` and `run-20260920T182629Z-333c`); so is the 18 whole dependency closure, cached on hbox at `/tank/fn/certcache`. 19 `books/stx-index` is **open** at `fn-stx-index-equivocators-agree`, 20 `books/stx-epochs` is **open** at `fn-stx-commit-decode-is-a-commit`, and 21 `books/stx-authority` and `tests/acl2/stx-transit-tests` are cascades of 22 those two. **The teeth have therefore not run**: they are written and 23 committed and nothing here may lean on them until the test book certifies. 24 PRF-019 to PRF-022 and PRF-024 are `in-progress` citing only certified 25 roots; PRF-023, PRF-025 and PRF-026 stay `planned`. 26 27 The first submission of this lane went to persvati before the coordinator 28 moved the lane to hbox; everything after it is hbox's. 29 30 ## What landed 31 32 - **`books/stx-lace.lisp`** — the lace as the store projected. `fn-stx-parse` 33 (the syntax article behind stored octets), `fn-stx-verifiedp` with 34 `fn-stx-verifiedp-is-the-verified-verdict` tying it to the verdict the 35 evidence slot records, `fn-stx-delta` (a singleton exactly when the 36 article's own octets verify under this node's keyring, nil otherwise), 37 `fn-stx-store`, `fn-stx-lace` as the oldest-first projection, and the 38 bridge `fn-stx-lace-of-accept-is-merge`. S3-1 and S3-2 follow as labelled 39 corollaries, including the two membership conjuncts that say neither fork 40 is dropped, and `fn-stx-transit-equivocation-survives-later-merges`. 41 - **`books/stx-index.lisp`** — the incremental twin: three alists, at most one 42 cons per accepted article, with `fn-stx-index-agrees-with-lace` (lookup and 43 the equivocator predicate) and `fn-stx-index-invariant-preserved-by-accept`. 44 The `(:equivocation creator incarnation sequence id-held id-new)` records 45 are the third list and `fn-stx-recorded-equivocation-agrees-with-lace` is 46 what stops them becoming a second authority. The cost shadow is 47 `fn-stx-index-lookup-cost-is-index-bounded` and 48 `fn-stx-index-grows-by-at-most-one-binding`. 49 - **`books/stx-policy.lisp`** — `fn-stx-transit-authority-ok`, the batch 50 vocabulary (`fn-stx-accept-batch`, `fn-stx-batch-delta`, 51 `fn-stx-lace-of-accept-batch`), the append forms of the three confinement 52 lemmas, `fn-stx-peer-batch-cannot-change-policy`, 53 `fn-stx-peer-batch-cannot-change-admission`, the constructive 54 `fn-stx-batch-policy-change-needs-authority-signature`, 55 `fn-stx-transit-admission-is-grounded`, and 56 `fn-stx-policy-statement-propagates`. The two typed authority outcomes are 57 `*fn-stx-authority-outcomes*`, deliberately not in `*fn-peer-reasons*`. 58 - **`books/stx-epochs.lisp`** — the commit codec over the `fn-stmt-` item 59 vocabulary (five items, bounded before any is parsed), 60 `fn-stx-commits-of-batch`, the carrier obligation 61 `fn-stx-commits-of-batch-are-verified-and-well-formed`, and the three 62 membership-epoch corollaries, each labelled with the keystone it restates. 63 - **`tests/acl2/stx-transit-tests.lisp`** — the witnesses and teeth. 64 - **`bin/fn`, `tools/stx.py`** — `fn statement sign|attach|show|verify` and 65 `fn principal new|list`; `tools/stx.py` gained `show` and `principal-new`. 66 Every value is computed by an ACL2 session; the CLI derives nothing. 67 68 ## Three departures, each recorded in the spec's status table 69 70 1. The two typed reasons do **not** join `*fn-peer-reasons*`: a member of 71 that enumeration is a reason to refuse bytes, and an equivocating article 72 must be accepted. 73 2. The bridge lemma carries a freshness hypothesis, because two Message-IDs 74 can carry one statement. 75 3. `fn-inj-prefix` is not edited: a node holds no principal's signing key. 76 77 ## Open, recorded rather than weakened 78 79 - **The host-line equations** (`fn-stx-transit-verdict-is-fn-stx-verdict`, 80 and the same obligation standing behind S4-1's renamed 81 `-by-definition` row). `fn-peer-transfer` records provenance as a string, 82 not as a structured value, so there is nowhere for `(:statement ...)` to 83 go. Owner: this lane with the peering cluster; the interface change is a 84 structured evidence value carried beside the node, as the index is. 85 - **`fn-stx-acceptedp` is an observation**, not derived from 86 `fn-node-complete`. 87 - **S6-1 / PRF-026 is not wired**, and deliberately not half-wired: there is 88 no slot in which acceptance records a verdict, so `fn-nntp-hdr-content` 89 cannot reach one. The three steps to close it are in the status table; the 90 nntp chain is independently red at `books/nntp-effects.lisp:971`. 91 - **`fn-stx-reissue-detected-after-peering`** needs a two-node system model 92 (`fn-sys-run`) that does not exist on this tree. 93 - **SCN-019 was not run.** No scenario was added to `tools/twonode_gate.py`. 94 - **Independent verification with no fn code** is blocked on D09.
Historical gate: owner fallback and unavailable peeringplanning/evidence/twonode-dfd8758-2026-09-20.md · L1–32
File SHA-256 e03c50b9665d439130cf18c7e1e2e4f6e2cfff988370b3636a88bc0b7ab4f3b9
1 # Two-node gate: dfd8758 on persvati 2 3 Two fn nodes from one commit, on one host, each with its own store, its own 4 served groups and its own listener, running at the same time. This records 5 what ran. It establishes nothing about the books, and where the tree has no 6 transit surface yet it says so in place of the scenario rather than passing 7 a weaker one under the same name. 8 9 ## What ran 10 11 | fact | value | 12 | --- | --- | 13 | commit | `dfd8758` (dfd87580d781ce2f3e4e957853e57d77e68232b9) | 14 | tree | `dev` | 15 | host | `persvati` | 16 | started | 2026-09-20T03:51:37Z | 17 | wall time | 103.0 s | 18 | gate tool | `tools/twonode_gate.py` | 19 | os | Ubuntu 25.10 kernel=Linux 6.17.0-40-generic arch=x86_64 cores=24 | 20 | python3 | Python 3.13.7 | 21 | acl2version | + ACL2 Version 8.7 + | 22 | certificates | neighbour /home/ember/fn-gates/dev-bdd59d2 -> matched=213 mismatched=0 absent=0 | 23 | node a | reader on port 38573 (main), store $HOME/fn-deploy/dfd8758/a/store | 24 | node b | reader on port 38351 (after-recovery), store $HOME/fn-deploy/dfd8758/b/store | 25 | peer records | none | 26 | three outcomes a | accepted=0 refused=1 uncertain=3 (expected (0, 1, 3)) | 27 | three outcomes b | accepted=0 refused=1 uncertain=3 (expected (0, 1, 3)) | 28 | transit | IHAVE -> '500 command not recognized'; CAPABILITIES lists IHAVE: False | 29 | feed | not exercised | 30 | kill | mode=post ConnectionResetError: [Errno 104] Connection reset by peer | 31 | host clients | expect=ABSENT, inews=ABSENT, nn=ABSENT, script=/usr/bin/script, slrn=ABSENT, tin=ABSENT, trn=ABSENT | 32
Boundary tests still omit the bundle inputtests/test_host_boundary.py · L110–136
File SHA-256 b39efd8a303056bcd1879e465ca08182d2a5c749f541442c0ac595f03fbeb0a2
110 """A durable acceptance awaiting a delete keeps the accepted code."""
111 pending = run_bp_ingress.BpDeletePending(
112 "durably accepted ADU awaits BPA delete", "accepted", "bid-1", Path("/staged"))
113 output = io.StringIO()
114 arguments = ["--store", "s", "--journal", "j", "--workflow-journal", "w",
115 "--bid", "bid-1", "--inventory", "i", "--adu", "a",
116 "--source-eid", "dtn://sender/"]
117 with mock.patch.object(run_bp_ingress, "ingest_bpa_adu", side_effect=pending), \
118 contextlib.redirect_stdout(output):
119 self.assertEqual(run_bp_ingress.main(arguments), run_store.EXIT_OK)
120 self.assertIn("accepted bid=bid-1", output.getvalue())
121 self.assertIn("bpa-delete=pending", output.getvalue())
122
123 def test_ingress_refusal_and_fault_stay_distinct(self):
124 arguments = ["--store", "s", "--journal", "j", "--workflow-journal", "w",
125 "--bid", "bid-1", "--inventory", "i", "--adu", "a",
126 "--source-eid", "dtn://sender/"]
127 cases = ((run_bp_ingress.BpIngressError("capacity"), run_store.EXIT_REFUSED),
128 (run_store.StoreIndeterminate("uncertain"), run_store.EXIT_UNCERTAIN),
129 (run_store.StoreFault("invalid"), run_store.EXIT_FAULT))
130 for error, expected in cases:
131 with self.subTest(error=type(error).__name__):
132 with mock.patch.object(run_bp_ingress, "ingest_bpa_adu", side_effect=error), \
133 contextlib.redirect_stderr(io.StringIO()):
134 self.assertEqual(run_bp_ingress.main(arguments), expected)
135
136The current ingress API requires a bundletools/run_bp_ingress.py · L297–315
File SHA-256 1d5f80873291bd07f1f252f6b5ebea7b3f0bbd6f63118eab5d8152614895c9e8
297 def main(argv=None):
298 parser = run_store.UsageParser(description=__doc__)
299 parser.add_argument("--store", required=True, type=Path)
300 parser.add_argument("--journal", required=True, type=Path)
301 parser.add_argument("--workflow-journal", required=True, type=Path,
302 help="Sol workflow_journal.py; it is not copied into this bridge")
303 parser.add_argument("--bid", required=True)
304 parser.add_argument("--inventory", required=True, type=Path,
305 help="test/lab newline BID inventory snapshot")
306 parser.add_argument("--adu", required=True, type=Path,
307 help="test/lab non-destructive downloaded ADU for --bid")
308 parser.add_argument("--bundle", required=True, type=Path,
309 help="the raw BP bundle octets the BPA holds for --bid; "
310 "ACL2 derives the identity and expiry from these")
311 parser.add_argument("--source-eid", required=True)
312 parser.add_argument("--destination", default=DEFAULT_DESTINATION)
313 parser.add_argument("--lifetime", type=int, default=DEFAULT_LIFETIME)
314 args = parser.parse_args(argv)
315 try:Assurance grows with the implemented surfacedocs/proofs.md · L1–73
File SHA-256 3994fae14a84a64867d8024debe4e6d76896083d1224fabd7caac46d287640e9
1 # Proof strategy and evidence 2 3 Status: component theorems are being certified as implementation advances. The 4 [proof registry](../planning/proofs.json) is the authoritative ledger for the 5 larger targets, which remain open until their complete statements are supported. 6 See [implementation status](implementation.md) for executable scope. Partial 7 lemmas do not close an entire subsystem proof target. 8 9 ## Assurance grows with the implemented surface 10 11 Each new reachable command, persistent record, transition, codec or adapter 12 operation adds assurance obligations. A proof about an older component does not 13 automatically cover a new caller or the composition between components. Keep 14 three things distinct in the [closure inventory](../planning/assurance-closure.md): 15 evidence for implemented behavior, missing evidence for implemented behavior, 16 and requirements for features that have not been implemented. 17 18 For each behavior-changing batch, record the affected stable requirement/proof/ 19 scenario IDs and the following change in scope: 20 21 | Changed surface | Evidence to add or explicitly leave open | 22 | --- | --- | 23 | State or transition | Meaningful initial/reachable invariant, rejection behavior, preservation of prior accepted facts, and finite traces | 24 | Caller or composition | Relation to the actual callee's state and hypotheses, including pending work; shape validity alone is insufficient | 25 | Durable record or side effect | Byte/record correspondence, barriers and uncertainty, live-versus-replay agreement, restart and lost-completion cuts | 26 | External bytes or raw execution | Decoder domain/canonicality, limits and work, guard status of the executed call graph | 27 | Authority or release | Exact evidence/context binding, durable decision, explicit policy/crypto/peer premises, preservation of independent obligations | 28 | Transport or scheduling | Duplicate/reorder/expiry/contact behavior; safety and conditional progress stated separately | 29 30 A feature checkpoint may remain experimental with an exact open list. It does 31 not count as assurance closure until its applicable claims have evidence. 32 Changing a profile or a trust assumption to avoid an obligation changes the 33 contract and must be recorded as such. Keep critical missing composition/recovery 34 evidence in a bounded closure batch before building dependent behavior on it. 35 36 No count appears in this document. Theorem, root, guard and test counts live 37 in [the generated ledger](../planning/ledger.md) and nowhere else, because a 38 number that is typed is a number that drifts. Prose carries the property, its 39 hypotheses and its covered scope in one sentence; it does not carry counts. 40 Report the property, supported domain, source digest and assumptions. A growing 41 count does not demonstrate that the assurance gap is shrinking. Review the 42 frozen batch once, fix concrete defects, and keep independent work moving; this 43 discipline adds no approval ceremony. 44 45 ## Refinement ladder 46 47 1. **Abstract executable state:** finite maps/records and event transitions. 48 Prove recognizer preservation, identity/allocation invariants, and phase rules. 49 2. **Bounded bytes:** refine external parsing/encoding to the accepted object and 50 NNTP domains. Prove decoding, deterministic preimages, and framing properties. 51 3. **Crash storage model:** relate logical acceptance to records, writes, barriers, 52 recovery, and checkpoint selection under named platform assumptions. 53 4. **Concrete execution:** use guard verification and explicit correspondence 54 between logical maps and efficient representations, including abstract stobjs 55 where useful. Include range-query completeness in index obligations. 56 5. **Multiple nodes:** reason about validated fact sets, obligation transitions, 57 duplicate/reordered exchange, and explicitly conditional progress properties. 58 59 The program used by the host must be these definitions or a justified refinement. 60 An abstract model plus an independently written server is insufficient evidence 61 for claims about the running server. 62 63 ## Shape of the claims 64 65 State preservation has the schematic form 66 `inv(s) and permitted-event(s,e) => inv(next(s,e))`. Prefer total, guarded 67 interfaces that explicitly reject malformed events rather than assume away 68 untrusted wire inputs. An invariant must include meaningful initial states and 69 reachable examples, so a theorem is not satisfied by an empty state domain. 70 71 Trace induction lifts one-step safety properties to finite executions. Eventual 72 delivery additionally needs a scheduling/contact/resource model and A-FAIRNESS. 73 Do not describe a safety theorem as proving inevitable delivery.
Content obligations outlive individual carriersspecs/bp-design.md · L1–52
File SHA-256 76043f41d7dd909a46ad863cf5a1a9044be69c2888f7bda98f0a271bdffec9b2
1 # fn as a BPv7 node: design
2
3 Status: design with exact statements, wave 4. Nothing here is certified; every
4 `defun` and `defthm` below is a commitment for the packets in "Migration",
5 not a claim. The books it builds on are certified today: `bp-primary-cbor`,
6 `bp-primary`, `bp-fragment`, `clock`, `bp-adu`, `bp-workflow`, `bp-receipt`,
7 `bp-release`, `relay`, and the wave-3 `scheduler` book
8 (`build/lanes/w3-scheduler/books/scheduler.lisp`, uncommitted at the time of
9 writing; its contact observation is `(fn-sched-contact peer start end)` and its
10 events are `admit`, `open`, `close`, `tick`, `expiry`, `transport`, `restart`).
11
12 ## Why
13
14 What fn calls its BP integration today is an application polling a co-located
15 dtn7-rs daemon over HTTP on loopback: three verbs (`/status/bundles`,
16 `/download?BID`, `/delete?BID`) in `tools/bpa_dtn7.py`, an opaque BID that
17 "is a key into one agent's table" ([bp-primary](bp-primary.md)), a Rust
18 binary using the *upstream* decoder to extract the ADU, and an insert API
19 whose reply "follows an asynchronously spawned store/transmit operation"
20 ([bp-workflow](bp-workflow.md)). The certified books that model the primary
21 block, fragmentation and expiry have no caller. That is theater: the proofs
22 are about functions the host never runs, and the host's actual protocol
23 surface is an HTTP client to a process fn does not control, cannot reason
24 about, and whose retained-copy semantics fn had to discover by experiment
25 ("a pinned BPA restart window in which stored inventory does not resume
26 forwarding", [tests/bp-dtn7/README](../tests/bp-dtn7/README.md)).
27
28 This design makes fn a BPv7 node in its own right. The bundle protocol agent
29 is fn's core; the bundle store is fn's store; the convergence layer is fn's,
30 written against RFC 9174 and run by the native host on its own sockets. The
31 pinned dtn7-rs build and ION stop being fn's agent and become fn's *peers*.
32
33 ## 1. fn as a BPv7 node
34
35 ### 1.1 Vocabulary, and custody
36
37 | RFC 9171 term | fn realisation |
38 | --- | --- |
39 | Bundle Protocol Agent (§3.1) | the `fn-bpn-*` machine in `books/bp-node.lisp` (this design), executed by the native host |
40 | Application agent, administrative element | the sender workflow `fn-bp` (bp-workflow) and the receiver `fn-bpr` (bp-receipt) are the application agent; status reports are generated and parsed by `fn-bpn` itself and are the administrative element |
41 | Node ID, endpoint | `fn-bpp-eidp` values; fn's node ID is a `dtn` node ID; its registration is one singleton endpoint `dtn://<node>/fn` in the Active state |
42 | Bundle store | the fn Store journal, with a new record family (§1.3). A bundle is a durable record; its payload is an object the Store already keeps |
43 | Retention constraint (§3.1, §5.4, §5.6, §5.7) | `fn-bpn-constraintp`: `:dispatch-pending`, `:forward-pending`, `:reassembly-pending`; a per-bundle set in the bundle record |
44 | Custody | not in BPv7. RFC 9171 Appendix A removes RFC 5050 custody transfer and fn imports nothing of it |
45 | Convergence layer adapter (§7) | `fn-tcl-*` (§2), sockets in the native host |
46 | Contact plan, routing (§5.4 step 2) | the wave-3 scheduler: `fn-sched-contact-holdsp` decides whether a peer is reachable now; `fn-sched-selection` decides which work goes |
47
48 **Custody in fn's sense.** fn has two obligations that live longer than any
49 bundle: the archive pin an accepted article holds (RET-001, `fn-retain`), and
50 the `:forward` pin an outstanding work holds (`fn-bprl-undertake`). Both are
51 released only by `fn-retain-release` under checked application evidence
52 (`fn-bprl-release-decision`, a receipt). These are what "custody" means inExisting transfer-work semantics are the starting point, not agent executionspecs/bp-workflow.md · L1–38 and L99–116
File SHA-256 d24a9a0549c2446cf004fe96ecdfb6ce19584b9d88a7e41779ddfe82227ac9a0
1 # Provisional BPv7 workflow journal 2 3 This specification defines the first executable fn workflow for disconnected 4 BPv7 transfer. It is a local laboratory profile. It does not define the 5 portable D01 exchange grammar, choose D09 signing, prove a BPA durable, or turn 6 a BP status report into fn application acceptance. 7 8 The executable decisions are in `books/bp-workflow.lisp`. They start from an 9 already committed `fn-node` article/archive binding. Local posting therefore 10 continues to acknowledge the local archive guarantee only. Creating onward 11 work is a separate journal transaction and receives its own enqueue 12 acknowledgment after that transaction is durable. 13 14 ## State and identity 15 16 A fixed schema-1 configuration records the local and peer EIDs, local policy 17 identifier, receipt authority, BP lifetime, node incarnation, and authorization 18 context. Work records copy the peer, policy, incarnation, authorization 19 context, and terms identifier that governed their creation. A work record is 20 bound to the immutable subject and archive obligation obtained from the actual 21 committed node binding; callers cannot supply replacements for those fields. 22 Local NNTP article numbers and BPA bundle identifiers are never part of portable 23 work identity. 24 25 Each storage intent uses a natural-number `(transaction-id, 26 transaction-generation)` pair. A successful prepare reserves that pair before 27 its outcome is known. The reservation remains in durable workflow history 28 through success, known abort, uncertainty, recovery, and restart. A later 29 prepare can never reuse it. This prevents an old completion for an aborted or 30 completed operation from authorizing a different enqueue, submission, or 31 receipt transaction. 32 33 The host journal must persist the prepare intent itself before invoking the ACL2 34 prepare transition. An outcome is a separate append-only record: 35 36 - ordinary `durable` and `aborted` outcomes feed `fn-bp-complete`; 37 - recovery `committed` and `absent` outcomes reconstruct an indeterminate fence 38 and then feed `fn-bp-recover`;
99 ## Application receipt 100 101 Transport status never releases work. A receipt is separate application 102 evidence with nine fields: receipt ID, work ID, immutable subject, issuer EID, 103 peer EID, policy ID, node incarnation, authorization context, and terms ID. 104 `fn-bp-prepare-receipt` requires an exact match to the durable work and fixed 105 configuration plus an explicit true A-POLICY authorization input. The boolean 106 is an environmental premise at this layer; it is not a signature proof or a 107 hidden trust claim. The checked receipt then follows the same durable 108 intent/outcome/fence/recovery protocol before it closes the work and enters 109 receipt history. 110 111 Arbitrary, stale, wrong-context, or policy-unauthorized receipts are no-ops. 112 Receipt recovery emits its acknowledgment only after the checked receipt record 113 is known committed. A future D09 decision must bind real verification and key 114 handling to the explicit authority, incarnation, authorization-context, and 115 terms fields; this experiment makes no cryptographic assurance claim. 116