parse-reader: splice multi-line def := continuations (eigentrust pitfall #15) - #12
Conversation
2ebdb15 to
5331e70
Compare
Adds pvec-nth-int / pvec-length-int / pvec-take-int / pvec-drop-int to prologos::core::pvec, mirroring the existing nth-int / length-int / take-int / drop-int quartet on List. These let an algorithm that already carries Int counters (e.g. for an int-le budget termination check) index into a PVec without maintaining a parallel Nat counter. Implementation routes through pvec-to-list and the existing List Int helpers (and back via pvec-from-list for take/drop), avoiding a freshly-introduced Int->Nat conversion primitive. An earlier draft defined a private int-to-nat-clamp helper and called pvec-slice directly, but pvec-slice's whnf rule needs nat-value to succeed on its lo/hi args — and a defn-defined int-to-nat-clamp does not reduce far enough inside the slice's whnf to satisfy nat-value, leaving the slice stuck. Routing through List sidesteps this entirely and matches the List versions' semantics exactly: negative indices are out-of-range, out-of-bounds nth returns none, take/drop clamp to [0, length]. Cost: O(n) for nth-int/take-int/drop-int (matching the List counterparts). pvec-length-int is O(log32 n). Did NOT add a top-level Int -> Nat helper. The pitfall noted that from-int : Int -> Nat does not exist, but `from-int` is already a parser keyword (Int -> Rat), and the List helpers themselves avoid the conversion via pure recursion — so a public Int -> Nat would just be a foot-gun that doesn't unblock anything the new helpers don't already address. Tests: tests/test-pvec-int-helpers.rkt covers length on empty/1/2, nth at 0/1/last/negative/out-of-bounds/empty, take and drop at 1/0/negative/larger-than-length, and a take+drop length round-trip. A standalone smoke test (run separately) exercises all 16 cases via process-string + pvec-to-list/pvec-length-int and confirms PASS on both Racket 8.10 (with current-parallel-executor #f) and Racket 9.1. The test file uses the standard test-support.rkt + rackunit pattern; the pre-existing (thread #:pool 'own) blocker on Racket 8.10 prevents running it via `raco test` here, but it is ready for the standard test runner. https: //claude.ai/code/session_01MbncYJnrvjzhbVWw4xGi5x Co-authored-by: kumavis <1474978+kumavis@users.noreply.github.com>
5331e70 to
1f67a47
Compare
Adds pvec-nth-int / pvec-length-int / pvec-take-int / pvec-drop-int to prologos::core::pvec, mirroring the existing nth-int / length-int / take-int / drop-int quartet on List. These let an algorithm that already carries Int counters (e.g. for an int-le budget termination check) index into a PVec without maintaining a parallel Nat counter. Implementation routes through pvec-to-list and the existing List Int helpers (and back via pvec-from-list for take/drop), avoiding a freshly-introduced Int->Nat conversion primitive. An earlier draft defined a private int-to-nat-clamp helper and called pvec-slice directly, but pvec-slice's whnf rule needs nat-value to succeed on its lo/hi args — and a defn-defined int-to-nat-clamp does not reduce far enough inside the slice's whnf to satisfy nat-value, leaving the slice stuck. Routing through List sidesteps this entirely and matches the List versions' semantics exactly: negative indices are out-of-range, out-of-bounds nth returns none, take/drop clamp to [0, length]. Cost: O(n) for nth-int/take-int/drop-int (matching the List counterparts). pvec-length-int is O(log32 n). Did NOT add a top-level Int -> Nat helper. The pitfall noted that from-int : Int -> Nat does not exist, but `from-int` is already a parser keyword (Int -> Rat), and the List helpers themselves avoid the conversion via pure recursion — so a public Int -> Nat would just be a foot-gun that doesn't unblock anything the new helpers don't already address. Tests: tests/test-pvec-int-helpers.rkt covers length on empty/1/2, nth at 0/1/last/negative/out-of-bounds/empty, take and drop at 1/0/negative/larger-than-length, and a take+drop length round-trip. A standalone smoke test (run separately) exercises all 16 cases via process-string + pvec-to-list/pvec-length-int and confirms PASS on both Racket 8.10 (with current-parallel-executor #f) and Racket 9.1. The test file uses the standard test-support.rkt + rackunit pattern; the pre-existing (thread #:pool 'own) blocker on Racket 8.10 prevents running it via `raco test` here, but it is ready for the standard test runner. https: //claude.ai/code/session_01MbncYJnrvjzhbVWw4xGi5x Co-authored-by: kumavis <1474978+kumavis@users.noreply.github.com>
Adds pvec-nth-int / pvec-length-int / pvec-take-int / pvec-drop-int to prologos::core::pvec, mirroring the existing nth-int / length-int / take-int / drop-int quartet on List. These let an algorithm that already carries Int counters (e.g. for an int-le budget termination check) index into a PVec without maintaining a parallel Nat counter. Implementation routes through pvec-to-list and the existing List Int helpers (and back via pvec-from-list for take/drop), avoiding a freshly-introduced Int->Nat conversion primitive. An earlier draft defined a private int-to-nat-clamp helper and called pvec-slice directly, but pvec-slice's whnf rule needs nat-value to succeed on its lo/hi args — and a defn-defined int-to-nat-clamp does not reduce far enough inside the slice's whnf to satisfy nat-value, leaving the slice stuck. Routing through List sidesteps this entirely and matches the List versions' semantics exactly: negative indices are out-of-range, out-of-bounds nth returns none, take/drop clamp to [0, length]. Cost: O(n) for nth-int/take-int/drop-int (matching the List counterparts). pvec-length-int is O(log32 n). Did NOT add a top-level Int -> Nat helper. The pitfall noted that from-int : Int -> Nat does not exist, but `from-int` is already a parser keyword (Int -> Rat), and the List helpers themselves avoid the conversion via pure recursion — so a public Int -> Nat would just be a foot-gun that doesn't unblock anything the new helpers don't already address. Tests: tests/test-pvec-int-helpers.rkt covers length on empty/1/2, nth at 0/1/last/negative/out-of-bounds/empty, take and drop at 1/0/negative/larger-than-length, and a take+drop length round-trip. A standalone smoke test (run separately) exercises all 16 cases via process-string + pvec-to-list/pvec-length-int and confirms PASS on both Racket 8.10 (with current-parallel-executor #f) and Racket 9.1. The test file uses the standard test-support.rkt + rackunit pattern; the pre-existing (thread #:pool 'own) blocker on Racket 8.10 prevents running it via `raco test` here, but it is ready for the standard test runner. https: //claude.ai/code/session_01MbncYJnrvjzhbVWw4xGi5x Co-authored-by: kumavis <1474978+kumavis@users.noreply.github.com>
hierophantos
left a comment
There was a problem hiding this comment.
LGTM. Surgical scope mirrors #4 (preparser splice based on first-token check), with a deliberately narrower rule: only :=-headed continuations splice; bare bodies stay wrapped — preserving def c\n + 1 2 → (def c (+ 1 2)) semantics for the existing bare-body idiom. End-to-end test coverage (using run-ns-ws-last) pins the silent-suppression case, which is the worst-quality bug shape (no error, just no work — was reporting reduce_ms = 0 in benchmarks).
Audit-passed against our codebase: 354 single-line def := body exist, all unaffected; zero multi-line def usage today, so the change is purely additive.
Now that #4 has landed (3d7ccb19), this PR is showing CONFLICTING — per your own note in the PR body, needs a rebase to drop the duplicate spec-form-node? / flatten-with-boundaries/spec / continuation-starts-with-keyword? (now on main) and add the def-form-node? branch on top of the existing spec-form-node? cond clause.
Approving — ready to merge once the rebase lands.
1f67a47 to
b51900d
Compare
b51900d to
d1c92a5
Compare
Wires the OCapN port to a real Racket toolchain and fixes the issues
the test run surfaced.
Library fixes
- vat.prologos: rename `spawn` -> `vat-spawn` (collision with the
reserved surface form recognised in macros.rkt:`'spawn`),
`spawn-actor` -> `vat-spawn-actor`. Same in core.prologos and the
acceptance file.
- vat.prologos: drop the `Sigma Vat Nat` return shape for spawn /
fresh-promise / send. Replace with a named `Allocated` struct +
`alloc-vat` / `alloc-id` accessors. The Sigma form ran into
"could not infer" elaborator errors when the body destructured
via `match | pair a b -> ...` and then re-constructed a Sigma;
`[fst p]` / `[snd p]` reused on the same `p` tripped QTT
multiplicity. The named struct sidesteps both.
- vat.prologos: reorder `resolve-promise` / `break-promise` BEFORE
`apply-effect` (forward-reference rule — module elaboration is
single-pass top-to-bottom). Also reorder `step-after-act` before
`deliver-msg` and `list-length-helper` before `queue-length`.
- vat.prologos: drop the queued-pipeline-flush in resolve-promise /
break-promise. PromiseState's queue is `List SyrupValue` (wire
repr); the vat queue is `List VatMsg` (decoded); flushing across
the boundary would need re-encoding. Phase 1.
Test-fixture fix (load-bearing)
- All 8 OCapN test files were updated to capture and restore
`current-ctor-registry` and `current-type-meta` across the setup-
-> run boundary. The standard fixture pattern from
`test-hashable-01.rkt` does NOT preserve these — fine for tests
that only declare traits, but breaks once a preamble's imports
declare new `data` types (every `data` in our 8 modules). Without
it, the reducer sees a stale ctor-registry and refuses to fire
pattern arms over user constructors; results print as un-reduced
`[reduce ... | vat x y z a -> x] : Nat` strings.
Documented as goblin-pitfall #12; the canonical fixture in
test-support.rkt should grow this for every future test.
Compat fence
- driver.rkt: guard
`(current-parallel-executor (make-parallel-thread-fire-all))` with
a feature-detection try/catch on `thread #:pool 'own`. Racket 9
ships parallel threads; Racket 8 does not. Fence preserves the
Racket-9 fast path and falls back to sequential firing on 8.
Acceptance
- examples/2026-04-27-ocapn-acceptance.prologos updated to match
the new vat-spawn/Allocated API and verified to run clean via
process-file.
Pitfalls catalogue (docs/tracking/2026-04-27_GOBLIN_PITFALLS.md)
- #0 (sandbox/no-Racket): closed.
- +#11 — Racket-8 vs Racket-9 `thread #:pool` compat
- +#12 — test fixture loses ctor-registry/type-meta across calls
[highest-impact; canonical fixture pattern needs update]
- +#13 — `spawn` is a reserved surface keyword; collides silently
- +#14 — `match | pair a b ->` on Sigma + Sigma reconstruction =>
"could not infer"
- +#15 — QTT multiplicity on `[fst p]`/`[snd p]` reused thrice
- +#16 — single-pass module elaboration: forward references error
- +#17 — promise-queue (Syrup) vs vat-queue (VatMsg) type clash
on flush — design pitfall, scope cut
Test results
refr 6/6 syrup 22/22 promise 16/16 message 19/19
behavior 13/13 vat 21/21 pipeline 5/5 captp 7/7
e2e 8/8 total 117/117 PASS
Per user direction: - Replace the body of every DELETED entry with a single-sentence explanation. Numbers reserved per prior instruction. - Delete #15 (QTT multiplicity on fst/snd thrice). I re-tested with a real Racket — `pair [snd p] [fst p]` then a third use of `fst p` works fine; no multiplicity error. The failure I had conflated this with was actually #14's "match-and-reconstruct Sigma" issue. Result: pitfalls doc shrinks from 765 to 534 lines. Remaining real claims: #1, #4, #5, #11, #12, #13, #14, #16, #17, #18, #19, #20 (the user has reviewed only #0-10 so far; #11-20 still pending their review).
…alls #15) A two-line def with type annotation — def c : [List Int] := '[1 2 3] — silently suppressed evaluation: PHASE-TIMINGS reported reduce_ms=0 and the bound name elaborated but never reduced. The same def collapsed to one line worked normally. The silent-no-work shape was particularly bad for benchmarks (reported zero reduce time regardless of workload). Add a def-form-node? branch to tree-node->stx-elements and a flatten-with-boundaries/def variant that splices ONLY continuations whose first token is :=. All other continuations (bare body, bracketed application chain, etc.) stay wrapped, preserving today's def c + 1 2 → (def c (+ 1 2)) semantics for the bare-body idiom. Narrower than the spec splice rule (#4 / #6) on purpose: spec needs to expose -> from anywhere in a multi-line type signature, def only needs to expose := which is always the head of a natural line break. Test coverage in test-def-multiline-ws.rkt covers: - Datum equivalence between one-line and two-line shapes (12 cases). - Bare-body wrapping is preserved. - End-to-end via run-ns-ws-last (silent-suppression regression pin). - Multi-line BODY after :=, := alone on a line, and definitions spanning lines as bracketed groups. Three-level WS validation: - Level 1 (sexp): the test file uses run-ns-last where applicable. - Level 2 (WS string): ws-read / run-ns-ws-last cover the dispatch and elaboration paths. - Level 3 (WS file): process-file on a .prologos with the two-line form reports reduce_ms=5 / reduce_steps=39, matching the one-line shape exactly (was reduce_ms=0 / reduce_steps=0 before). https://claude.ai/code/session_01MbncYJnrvjzhbVWw4xGi5x Co-authored-by: kumavis <1474978+kumavis@users.noreply.github.com>
d1c92a5 to
274cd2a
Compare
Adds pvec-nth-int / pvec-length-int / pvec-take-int / pvec-drop-int to prologos::core::pvec, mirroring the existing nth-int / length-int / take-int / drop-int quartet on List. These let an algorithm that already carries Int counters (e.g. for an int-le budget termination check) index into a PVec without maintaining a parallel Nat counter. Implementation routes through pvec-to-list and the existing List Int helpers (and back via pvec-from-list for take/drop), avoiding a freshly-introduced Int->Nat conversion primitive. An earlier draft defined a private int-to-nat-clamp helper and called pvec-slice directly, but pvec-slice's whnf rule needs nat-value to succeed on its lo/hi args — and a defn-defined int-to-nat-clamp does not reduce far enough inside the slice's whnf to satisfy nat-value, leaving the slice stuck. Routing through List sidesteps this entirely and matches the List versions' semantics exactly: negative indices are out-of-range, out-of-bounds nth returns none, take/drop clamp to [0, length]. Cost: O(n) for nth-int/take-int/drop-int (matching the List counterparts). pvec-length-int is O(log32 n). Did NOT add a top-level Int -> Nat helper. The pitfall noted that from-int : Int -> Nat does not exist, but `from-int` is already a parser keyword (Int -> Rat), and the List helpers themselves avoid the conversion via pure recursion — so a public Int -> Nat would just be a foot-gun that doesn't unblock anything the new helpers don't already address. Tests: tests/test-pvec-int-helpers.rkt covers length on empty/1/2, nth at 0/1/last/negative/out-of-bounds/empty, take and drop at 1/0/negative/larger-than-length, and a take+drop length round-trip. A standalone smoke test (run separately) exercises all 16 cases via process-string + pvec-to-list/pvec-length-int and confirms PASS on both Racket 8.10 (with current-parallel-executor #f) and Racket 9.1. The test file uses the standard test-support.rkt + rackunit pattern; the pre-existing (thread #:pool 'own) blocker on Racket 8.10 prevents running it via `raco test` here, but it is ready for the standard test runner. https: //claude.ai/code/session_01MbncYJnrvjzhbVWw4xGi5x Co-authored-by: kumavis <1474978+kumavis@users.noreply.github.com>
Wires the OCapN port to a real Racket toolchain and fixes the issues
the test run surfaced.
Library fixes
- vat.prologos: rename `spawn` -> `vat-spawn` (collision with the
reserved surface form recognised in macros.rkt:`'spawn`),
`spawn-actor` -> `vat-spawn-actor`. Same in core.prologos and the
acceptance file.
- vat.prologos: drop the `Sigma Vat Nat` return shape for spawn /
fresh-promise / send. Replace with a named `Allocated` struct +
`alloc-vat` / `alloc-id` accessors. The Sigma form ran into
"could not infer" elaborator errors when the body destructured
via `match | pair a b -> ...` and then re-constructed a Sigma;
`[fst p]` / `[snd p]` reused on the same `p` tripped QTT
multiplicity. The named struct sidesteps both.
- vat.prologos: reorder `resolve-promise` / `break-promise` BEFORE
`apply-effect` (forward-reference rule — module elaboration is
single-pass top-to-bottom). Also reorder `step-after-act` before
`deliver-msg` and `list-length-helper` before `queue-length`.
- vat.prologos: drop the queued-pipeline-flush in resolve-promise /
break-promise. PromiseState's queue is `List SyrupValue` (wire
repr); the vat queue is `List VatMsg` (decoded); flushing across
the boundary would need re-encoding. Phase 1.
Test-fixture fix (load-bearing)
- All 8 OCapN test files were updated to capture and restore
`current-ctor-registry` and `current-type-meta` across the setup-
-> run boundary. The standard fixture pattern from
`test-hashable-01.rkt` does NOT preserve these — fine for tests
that only declare traits, but breaks once a preamble's imports
declare new `data` types (every `data` in our 8 modules). Without
it, the reducer sees a stale ctor-registry and refuses to fire
pattern arms over user constructors; results print as un-reduced
`[reduce ... | vat x y z a -> x] : Nat` strings.
Documented as goblin-pitfall #12; the canonical fixture in
test-support.rkt should grow this for every future test.
Compat fence
- driver.rkt: guard
`(current-parallel-executor (make-parallel-thread-fire-all))` with
a feature-detection try/catch on `thread #:pool 'own`. Racket 9
ships parallel threads; Racket 8 does not. Fence preserves the
Racket-9 fast path and falls back to sequential firing on 8.
Acceptance
- examples/2026-04-27-ocapn-acceptance.prologos updated to match
the new vat-spawn/Allocated API and verified to run clean via
process-file.
Pitfalls catalogue (docs/tracking/2026-04-27_GOBLIN_PITFALLS.md)
- #0 (sandbox/no-Racket): closed.
- +#11 — Racket-8 vs Racket-9 `thread #:pool` compat
- +#12 — test fixture loses ctor-registry/type-meta across calls
[highest-impact; canonical fixture pattern needs update]
- +#13 — `spawn` is a reserved surface keyword; collides silently
- +#14 — `match | pair a b ->` on Sigma + Sigma reconstruction =>
"could not infer"
- +#15 — QTT multiplicity on `[fst p]`/`[snd p]` reused thrice
- +#16 — single-pass module elaboration: forward references error
- +#17 — promise-queue (Syrup) vs vat-queue (VatMsg) type clash
on flush — design pitfall, scope cut
Test results
refr 6/6 syrup 22/22 promise 16/16 message 19/19
behavior 13/13 vat 21/21 pipeline 5/5 captp 7/7
e2e 8/8 total 117/117 PASS
Per user direction: - Replace the body of every DELETED entry with a single-sentence explanation. Numbers reserved per prior instruction. - Delete #15 (QTT multiplicity on fst/snd thrice). I re-tested with a real Racket — `pair [snd p] [fst p]` then a third use of `fst p` works fine; no multiplicity error. The failure I had conflated this with was actually #14's "match-and-reconstruct Sigma" issue. Result: pitfalls doc shrinks from 765 to 534 lines. Remaining real claims: #1, #4, #5, #11, #12, #13, #14, #16, #17, #18, #19, #20 (the user has reviewed only #0-10 so far; #11-20 still pending their review).
…2-pv12 pvec: add Int-indexed helpers (eigentrust pitfall #12)
|
Post-rebase verified clean: dispatch in |
Merges origin/main (branch point 68f4564, 2026-05-02 -> tip 9bff07f, 2026-07-22) into the OCapN branch and re-audits the pitfalls log against the merged toolchain on Racket 9.0. Merge: 4 trivial conflicts (3 docs + the nat.pnet cache, which main now gitignores). Compiler builds clean. Audit findings: - main fixed NONE of the 11 load-bearing pitfalls. Its quarter went to typing/records (CIU T6), propagator internals (PPN 4C), Numerics and Rel T1; the WS reader, match-lowering, data-inference and reducer perf were untouched. PReduce is docs-only (Tracks 1-9 not started). - #27/#31 (decode perf) are STALE as written: fixed on our own branch by 4f6b3f0 (looseBVarRange short-circuit on shift), two days after #31 was logged. Re-measured: the #31 repro decodes in ~1s, not 150s. Residual is a ~100x constant factor, not a wall. This is also the recommended fix for still-open upstream issue #58. - NEW: the .pnet module cache is unsound. Same script, cold cache -> correct String value; warm cache -> stuck [reduce ...] term, no error. Silently non-reducing definitions; highest-severity new item. - NEW: transitive-import mis-elaboration pinned to a mechanism - step-behavior's BehaviorTag parameter elaborates as String, so no beh-* arm matches. Unifies #12 and #33. - NEW: the merge breaks our harness. main retired current-prelude-env (9d166ce) and swept its own tests; our 37 OCapN test files did not get swept, and the shared-fixture pattern has no supported replacement. Also records the measured cost of the workarounds (153 helper defns, 92 explicit type-arg sites, 38 hand-written 11-ctor matches) and an honest blocker taxonomy separating architectural / silent-wrong / expressiveness / throughput. No code changes; docs + merge only. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01YM6gc3cMNH2Ymor4jdZY8u
…ator hints are a map Three more entries. 9615 unit tests, conformance 24/24. §1.4 #6 — `op:listen`'s third field was required-but-DISCARDED, so a peer asking for partial resolutions (a promise resolving to another promise) was indistinguishable from one that was not. `op-listen` carries it now. A non-boolean in that slot fails the decode rather than defaulting to false: the field is not optional, and coercing a malformed one is the same silent class as the answer position two functions away. §1.6 M8 — a frame we cannot decode now gets an `op:abort` instead of "", which the peer could not tell apart from "decoded fine, nothing to say". Every decode failure is fatal, deliberately: the decoder's gates are arity, descriptor label and slot well-formedness, so a frame that fails one is a frame whose meaning we do not know, and continuing is how a desync becomes a wrong answer several frames later. This one was blocked for a reason worth recording: the SPEC's plural GC ops did not decode, so aborting would have fired on ordinary conforming traffic. Fixing them removed the blocker, and this entry was reachable only after. §1.10 #12 — locator hints are a MAP (flat alternating key/value, the shape syrup-dict uses) rather than two fixed scalar fields. Upstream ships an onion netlayer whose hints are different keys entirely and had no representation at all. `loc-host`/`loc-port` survive as lookups, so the one consumer and the tests are unaffected; `loc-port` parses its hint and answers zero when absent or non-numeric, because a locator with no port is a loopback locator, not an error. The string→Nat parse the gaps doc named as the blocker lives here for exactly that reason -- a general one in the stdlib could not choose to be total. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01YM6gc3cMNH2Ymor4jdZY8u
…wn premises 9616 unit tests, conformance 24/24. Seven entries were open; five are closed and two turned out to rest on premises that no longer hold. §1.9 #4 IS A LANGUAGE GAP, NOT AN OCapN ONE Annotating an FFI binding `:requires (CryptoCap)` makes the whole module unimportable from a `.prologos` file: the requirement propagates to the caller and nothing can introduce a root capability. I tried wrapping the bindings in same-module functions the way `tcp-testing` does, and importing the capability type alongside. Neither helps. Then the actual discovery: `prologos::ocapn::tcp-testing`, which this document cites as proof the convention is live, CANNOT BE IMPORTED EITHER. Nothing in the tree imports it, and its test loads it through the sexp `imports` path, which does not hit the check — so a module that no consumer can use has been sitting there with a green test asserting "just load the module". It now carries a characterization test that pins the WS-mode failure. That test asserts behaviour that is WRONG on purpose: when the language grows a root capability form it fails, and that is the signal to thread the capability for real. A gap with a witness beats a gap with a comment. §1.8 M2 HAS LOST ITS CONSEQUENCE The stated failure was that a process-wide keypair collapses session-ids and the handoff replay guard, keyed on session, then refuses a peer's legitimate second handoff. That guard became per-connection when the gift namespace was split, so two connections no longer share a replay set. What is left is a conformance deviation with no failing input — which by this document's own standard is not a finding. Minting per connection also costs a `process-string` per accept, which the suite's wall-clock budget would notice. CLOSED - §1.1 #5 / §1.10 #3 / §1.10 #11 — sets and floats (previous commit) - §1.4 #6 — op:listen carries wants-partial (previous commit) - §1.6 M8 — undecodable frames abort (previous commit) - §1.10 #10 — the plain-value reason names its value (previous commit) - §1.10 #12 — locator hints are a map (previous commit) WHAT IS ACTUALLY LEFT Five entries, and four of them are one problem: the gifter and receiver roles live in Racket because a Prologos behaviour cannot open a socket. §1.7 M8 (double processing), §1.7 M7 (unregistered enliven slots) and §1.10 #8 (unsigned Node peers) are all downstream of that. Both of §1.7 M7's live consequences are gone; what remains is that the slot reservation is by convention rather than construction. The fifth, §1.2 M3, is independent and has a known shape: allocate our own answer position for a forwarded send and resolve the queued deliver's from it. The pieces exist — `bs-add-question` already maps a peer answer position to a local promise and the pump answers from it — but wiring them means threading BridgeState through a loop that returns bytes only. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01YM6gc3cMNH2Ymor4jdZY8u
Fixes pitfall #15 from
docs/tracking/2026-04-23_eigentrust_pitfalls.md.Summary
silently suppressed evaluation of downstream top-level expressions. The reducer never fired,
PHASE-TIMINGSreportedreduce_ms = 0, and the computed value was never printed. The samedefcollapsed to one line worked normally (reduce_ms = 38_855for a 3-iter benchmark).Load-bearing for benchmark validity — silent suppression was the worst of both worlds (no error, but no work).
Reproducer (before/after)
def)d)Unbound variable: $list-literalUnbound variable: d(reduce_ms=0)d : [List Rat] defined'[1/2 1/2 1/2] : [List Rat]Identical to the one-line form.
Fix
WS reader splices the
:= BODYcontinuation into the def's parent token stream. Narrower than the spec splice rule from PR #4: only continuations whose first token is:=are spliced. Bare bodies,+ 1 2applications, bracketed groups stay wrapped, preserving today'sdef c\n + 1 2→(def c (+ 1 2))semantics.Implementation: added
def-form-node?,flatten-with-boundaries/def,continuation-starts-with-assign?toparse-reader.rkt, dispatched intree-node->stx-elements.Coexistence note with PR #4 (pitfall #6)
PR #4 (pitfall #6) is not yet merged to main, but uses the same dispatch site in
tree-node->stx-elements. This PR introduces theflatten-with-boundaries/spectriple from PR #4's commit6c42aa9alongside the new def helpers because both fixes share the dispatch site. Whichever lands second will need a small dedup offlatten-with-boundaries/spec,spec-form-node?, andcontinuation-starts-with-keyword?.Test plan
tests/test-def-multiline-ws.rkt— 12 cases: datum-level equivalence between one-line and two-line forms, end-to-end evaluation regression,:=on its own line, multi-line BODYprocess-ws-01,parse-reader,let-arrow-syntax,spec*,defmacro,cond-01,bound-args-01,int-patterns-01,first-rest-01,hashable-01) all passlattices.prologos,foreign.prologos,2026-04-02-ppn-track3.prologosbenchmarks/micro/info.rkt)Commits
e7dbc0f— primary fix inparse-reader.rkt+ new test file2ebdb15— CI fix (skip stale bench file)https://claude.ai/code/session_01MbncYJnrvjzhbVWw4xGi5x
Generated by Claude Code