pvec: add Int-indexed helpers (eigentrust pitfall #12)#15
Conversation
…dentically (eigentrust pitfall #15) EigenTrust pitfall #15 reported that a `def` with a type annotation split across two lines — def c-asym-3 : [List [List Rat]] := '['[rz 1/2 1/2] '[rz rz ro] '[ro rz rz]] — silently misparses. The WS reader's indent grouping wraps the `:= BODY` continuation as a sub-list, so the def datum becomes (def c : (List ...) (:= ($list-literal ...))) rather than the one-line equivalent (def c : (List ...) := ($list-literal ...)). `expand-def-assign` (and every other `:=` dispatcher in macros.rkt) only scans the TOP LEVEL of the def body via `memq`, so the buried `:=` is invisible. The 4-element fallback in `parse-def` then treats `(:= ($list-literal ...))` as the BODY of `def`, which on elaboration tries to apply `:=` to `($list-literal ...)` — reporting `Unbound variable: $list-literal`. Worse, every downstream reference to the (never-bound) name reports its own spurious unbound-variable error, and benchmarks measuring `reduce_ms` report 0 because the intended computation never ran. Fix shape (a in the pitfall memo): `tree-node->stx-elements` now recognises `def`/`def-` form nodes and SPLICES any indent-grouped continuation line whose first token is `:=` directly into the parent token stream. All other continuations (bare-body expressions, bracketed application chains) remain wrapped, preserving today's semantics for forms like `def c\n + 1 2` → `(def c (+ 1 2))`. This is intentionally narrower than the spec-form splice rule (which splices everything except `:`-keyword metadata): def only needs `:=` exposed, and over-splicing would break the body grouping that `def NAME\n + 1 2` relies on. Reproducer (before fix): `def d : [List Rat]\n := '[1/2 1/2 1/2]` followed by `d` reports `Unbound variable: $list-literal` for the def and `Unbound variable: d` for the reference. After fix: both forms evaluate identically to the one-line equivalent. Test coverage in `tests/test-def-multiline-ws.rkt` covers the eigentrust reproducer, the assignment-marker-on-its-own-line variant, multi-line bracketed bodies after `:=`, the private `def-` keyword, and a non-regression check that `def c\n + 1 2` still groups its continuation as `(+ 1 2)` (i.e. the splice is gated on `:=`-headed continuations only). Twelve cases, all green. Co-authored-by: kumavis <1474978+kumavis@users.noreply.github.com>
0678170 to
922dcb0
Compare
kumavis
left a comment
There was a problem hiding this comment.
hiero approved but acknowledges this will all be inducted into a later collection heneralization
…dentically (eigentrust pitfall #15) EigenTrust pitfall #15 reported that a `def` with a type annotation split across two lines — def c-asym-3 : [List [List Rat]] := '['[rz 1/2 1/2] '[rz rz ro] '[ro rz rz]] — silently misparses. The WS reader's indent grouping wraps the `:= BODY` continuation as a sub-list, so the def datum becomes (def c : (List ...) (:= ($list-literal ...))) rather than the one-line equivalent (def c : (List ...) := ($list-literal ...)). `expand-def-assign` (and every other `:=` dispatcher in macros.rkt) only scans the TOP LEVEL of the def body via `memq`, so the buried `:=` is invisible. The 4-element fallback in `parse-def` then treats `(:= ($list-literal ...))` as the BODY of `def`, which on elaboration tries to apply `:=` to `($list-literal ...)` — reporting `Unbound variable: $list-literal`. Worse, every downstream reference to the (never-bound) name reports its own spurious unbound-variable error, and benchmarks measuring `reduce_ms` report 0 because the intended computation never ran. Fix shape (a in the pitfall memo): `tree-node->stx-elements` now recognises `def`/`def-` form nodes and SPLICES any indent-grouped continuation line whose first token is `:=` directly into the parent token stream. All other continuations (bare-body expressions, bracketed application chains) remain wrapped, preserving today's semantics for forms like `def c\n + 1 2` → `(def c (+ 1 2))`. This is intentionally narrower than the spec-form splice rule (which splices everything except `:`-keyword metadata): def only needs `:=` exposed, and over-splicing would break the body grouping that `def NAME\n + 1 2` relies on. Reproducer (before fix): `def d : [List Rat]\n := '[1/2 1/2 1/2]` followed by `d` reports `Unbound variable: $list-literal` for the def and `Unbound variable: d` for the reference. After fix: both forms evaluate identically to the one-line equivalent. Test coverage in `tests/test-def-multiline-ws.rkt` covers the eigentrust reproducer, the assignment-marker-on-its-own-line variant, multi-line bracketed bodies after `:=`, the private `def-` keyword, and a non-regression check that `def c\n + 1 2` still groups its continuation as `(+ 1 2)` (i.e. the splice is gated on `:=`-headed continuations only). Twelve cases, all green. Co-authored-by: kumavis <1474978+kumavis@users.noreply.github.com>
9d23f19 to
fac9fb2
Compare
hierophantos
left a comment
There was a problem hiding this comment.
LGTM. Adds the Int-indexed quartet (pvec-length-int / pvec-nth-int / pvec-take-int / pvec-drop-int) mirroring the List counterparts, with prelude integration via PRELUDE + namespace.rkt — addressing the symmetric question I raised on #5. Implementation routes through pvec-to-list / list helpers / pvec-from-list, with honest scope discipline:
from-int : Int → Natskipped with rationale (existing parser keyword for Int→Rat; foot-gun semantics for Int→Nat)- First-draft approach (private
int-to-nat-clamp+pvec-slice) abandoned after diagnosingwhnfstuck-ness; routed through list helpers instead. Same O(n) parity with List; future O(log32 n) primitive flagged for the team if someone wants to add it later.
Test coverage: 18 cases including negative-index / OOB / empty / round-trip (take + drop = original length).
CONFLICTING with main — the require line in pvec.prologos where both #5 (added zip-with) and this PR (added nth-int take-int drop-int) modify the same line. Mechanical resolution: combine both additions into one require list. The body sections at the end should merge cleanly.
Approving — ready to merge once the rebase lands.
…dentically (eigentrust pitfall #15) EigenTrust pitfall #15 reported that a `def` with a type annotation split across two lines — def c-asym-3 : [List [List Rat]] := '['[rz 1/2 1/2] '[rz rz ro] '[ro rz rz]] — silently misparses. The WS reader's indent grouping wraps the `:= BODY` continuation as a sub-list, so the def datum becomes (def c : (List ...) (:= ($list-literal ...))) rather than the one-line equivalent (def c : (List ...) := ($list-literal ...)). `expand-def-assign` (and every other `:=` dispatcher in macros.rkt) only scans the TOP LEVEL of the def body via `memq`, so the buried `:=` is invisible. The 4-element fallback in `parse-def` then treats `(:= ($list-literal ...))` as the BODY of `def`, which on elaboration tries to apply `:=` to `($list-literal ...)` — reporting `Unbound variable: $list-literal`. Worse, every downstream reference to the (never-bound) name reports its own spurious unbound-variable error, and benchmarks measuring `reduce_ms` report 0 because the intended computation never ran. Fix shape (a in the pitfall memo): `tree-node->stx-elements` now recognises `def`/`def-` form nodes and SPLICES any indent-grouped continuation line whose first token is `:=` directly into the parent token stream. All other continuations (bare-body expressions, bracketed application chains) remain wrapped, preserving today's semantics for forms like `def c\n + 1 2` → `(def c (+ 1 2))`. This is intentionally narrower than the spec-form splice rule (which splices everything except `:`-keyword metadata): def only needs `:=` exposed, and over-splicing would break the body grouping that `def NAME\n + 1 2` relies on. Reproducer (before fix): `def d : [List Rat]\n := '[1/2 1/2 1/2]` followed by `d` reports `Unbound variable: $list-literal` for the def and `Unbound variable: d` for the reference. After fix: both forms evaluate identically to the one-line equivalent. Test coverage in `tests/test-def-multiline-ws.rkt` covers the eigentrust reproducer, the assignment-marker-on-its-own-line variant, multi-line bracketed bodies after `:=`, the private `def-` keyword, and a non-regression check that `def c\n + 1 2` still groups its continuation as `(+ 1 2)` (i.e. the splice is gated on `:=`-headed continuations only). Twelve cases, all green. Co-authored-by: kumavis <1474978+kumavis@users.noreply.github.com>
…dentically (eigentrust pitfall #15) EigenTrust pitfall #15 reported that a `def` with a type annotation split across two lines — def c-asym-3 : [List [List Rat]] := '['[rz 1/2 1/2] '[rz rz ro] '[ro rz rz]] — silently misparses. The WS reader's indent grouping wraps the `:= BODY` continuation as a sub-list, so the def datum becomes (def c : (List ...) (:= ($list-literal ...))) rather than the one-line equivalent (def c : (List ...) := ($list-literal ...)). `expand-def-assign` (and every other `:=` dispatcher in macros.rkt) only scans the TOP LEVEL of the def body via `memq`, so the buried `:=` is invisible. The 4-element fallback in `parse-def` then treats `(:= ($list-literal ...))` as the BODY of `def`, which on elaboration tries to apply `:=` to `($list-literal ...)` — reporting `Unbound variable: $list-literal`. Worse, every downstream reference to the (never-bound) name reports its own spurious unbound-variable error, and benchmarks measuring `reduce_ms` report 0 because the intended computation never ran. Fix shape (a in the pitfall memo): `tree-node->stx-elements` now recognises `def`/`def-` form nodes and SPLICES any indent-grouped continuation line whose first token is `:=` directly into the parent token stream. All other continuations (bare-body expressions, bracketed application chains) remain wrapped, preserving today's semantics for forms like `def c\n + 1 2` → `(def c (+ 1 2))`. This is intentionally narrower than the spec-form splice rule (which splices everything except `:`-keyword metadata): def only needs `:=` exposed, and over-splicing would break the body grouping that `def NAME\n + 1 2` relies on. Reproducer (before fix): `def d : [List Rat]\n := '[1/2 1/2 1/2]` followed by `d` reports `Unbound variable: $list-literal` for the def and `Unbound variable: d` for the reference. After fix: both forms evaluate identically to the one-line equivalent. Test coverage in `tests/test-def-multiline-ws.rkt` covers the eigentrust reproducer, the assignment-marker-on-its-own-line variant, multi-line bracketed bodies after `:=`, the private `def-` keyword, and a non-regression check that `def c\n + 1 2` still groups its continuation as `(+ 1 2)` (i.e. the splice is gated on `:=`-headed continuations only). Twelve cases, all green. 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).
…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>
…x multi-line def bug The original four comparative benchmarks all converged in one step — uniform-matrix with uniform pre-trust is a fixed point, so W1 and W2 shortcut after the first diff check. reduce_ms reflected only the trivial converging workloads and primitive ops. Added W3: eigentrust c-asym-3 p-uniform-3 alpha=3/10 eps=0 budget=3. The asymmetric 3x3 matrix c-asym-3 (row-stochastic but not column- stochastic, so uniform is not a fixed point) plus eps=0 (never converges) guarantees all 3 iterations run. At alpha=3/10 the EigenTrust convention is 70% network / 30% anchor, giving an interesting mid-convergence trajectory. While measuring, uncovered a serious latent Prologos bug (documented as pitfall #15): def c-asym-3 : [List [List Rat]] := '['[rz 1/2 1/2] '[rz rz ro] '[ro rz rz]] This two-line def form silently suppresses evaluation of downstream top-level expressions. `PHASE-TIMINGS` reports reduce_ms=0 and the computed value is never printed. Collapsing the def to one physical line fixes it. All four benchmark files had used the two-line form, so the reduce_ms numbers in the previous commit were artefacts — real reduce_ms on the corrected W3 workload is 22-35 seconds, not 250-1144 ms. Real phase-timing results (bench-phases.rkt, 2-run median): list+rat : reduce_ms=33699 list+posit32 : reduce_ms=34874 (~3% vs list+rat) pvec+rat : reduce_ms=22260 (34% faster than list+rat) pvec+posit32 : reduce_ms=22428 (34% faster) The story inverts from the earlier reading: PVec beats List by ~35% on genuinely-iterating workloads because the index-based pvec-push accumulator shapes the reducer's term-tree work better than cons-chain recursion. Posit32 vs Rat is within noise at these denominators. Docs updated: - docs/tracking/2026-04-23_eigentrust_pitfalls.md: new section 15 "Multi-line def X : T := body silently suppresses evaluation". - docs/tracking/2026-04-23_eigentrust_comparison.md: replaced performance section with the corrected numbers + disclaimer + observations on what the phase breakdown actually shows. https://claude.ai/code/session_01UrB1yXsd8hzjyXwj8PFiVp
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>
fac9fb2 to
cb8da5f
Compare
…5-mldef parse-reader: splice multi-line def := continuations (eigentrust pitfall #15)
…end)
Adds the first vat-level support for sending messages to
unresolved promises (the foundation of OCapN promise pipelining).
New module: lib/prologos/ocapn/pipelining.prologos
pipeline-deliver target args v
- target is an actor → send-only (current vat-deliver)
- target is an unresolved promise → enqueue in promise's pending list
- target is a settled promise OR missing → drop
Helpers:
set-promise-state pid pst v ;; replace one promise's state
deliver-to-promise pid args pst v
deliver-to-promise-or-drop target args v
promise-queue-length pid v
list-length xs
Implementation note: the helpers are factored out (not inlined)
to dodge the recurring "import-time elaborator error from nested
match + let" pattern (pitfalls #15 history). 3-deep nested match
in one defn body fails to elaborate at IMPORT time despite
loading via `racket driver.rkt`. Helper-factoring works around it.
Also documented goblin-pitfalls #18 reminder: leading 0-arity
ctor in multi-arity defn (`| nil -> zero`) hits the dispatch
bug; using `defn f [xs] match xs` form sidesteps it. (list-length
specifically uses the match form for this reason.)
What's NOT included (deferred):
- Queue flush on resolution: when a pipelined promise resolves,
the queued messages should be drained back to the vat queue
with target rewritten to the resolution value. The
promise-state's `enqueue`/`take-queue` machinery is in place;
`resolve-promise` doesn't flush yet (pitfall #1, deferred
from Phase 0). Phase 21 lays storage groundwork only.
- PIPE actually running through bridge — Phase 14/15 don't yet
call pipeline-deliver; only direct test usage exercises it.
Test additions (4 in tests/test-ocapn-pipelining.rkt):
- pipeline-deliver to unresolved promise → promise-queue-length=1
- pipeline-deliver to actor → vat queue-length=1 (normal)
- pipeline-deliver to missing target → no-op
- pipeline-deliver to already-fulfilled promise → drops (no queue
growth)
Tests: 4/4 green on Racket 9.1.
https://claude.ai/code/session_01YM6gc3cMNH2Ymor4jdZY8u
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).
…end)
Adds the first vat-level support for sending messages to
unresolved promises (the foundation of OCapN promise pipelining).
New module: lib/prologos/ocapn/pipelining.prologos
pipeline-deliver target args v
- target is an actor → send-only (current vat-deliver)
- target is an unresolved promise → enqueue in promise's pending list
- target is a settled promise OR missing → drop
Helpers:
set-promise-state pid pst v ;; replace one promise's state
deliver-to-promise pid args pst v
deliver-to-promise-or-drop target args v
promise-queue-length pid v
list-length xs
Implementation note: the helpers are factored out (not inlined)
to dodge the recurring "import-time elaborator error from nested
match + let" pattern (pitfalls #15 history). 3-deep nested match
in one defn body fails to elaborate at IMPORT time despite
loading via `racket driver.rkt`. Helper-factoring works around it.
Also documented goblin-pitfalls #18 reminder: leading 0-arity
ctor in multi-arity defn (`| nil -> zero`) hits the dispatch
bug; using `defn f [xs] match xs` form sidesteps it. (list-length
specifically uses the match form for this reason.)
What's NOT included (deferred):
- Queue flush on resolution: when a pipelined promise resolves,
the queued messages should be drained back to the vat queue
with target rewritten to the resolution value. The
promise-state's `enqueue`/`take-queue` machinery is in place;
`resolve-promise` doesn't flush yet (pitfall #1, deferred
from Phase 0). Phase 21 lays storage groundwork only.
- PIPE actually running through bridge — Phase 14/15 don't yet
call pipeline-deliver; only direct test usage exercises it.
Test additions (4 in tests/test-ocapn-pipelining.rkt):
- pipeline-deliver to unresolved promise → promise-queue-length=1
- pipeline-deliver to actor → vat queue-length=1 (normal)
- pipeline-deliver to missing target → no-op
- pipeline-deliver to already-fulfilled promise → drops (no queue
growth)
Tests: 4/4 green on Racket 9.1.
https://claude.ai/code/session_01YM6gc3cMNH2Ymor4jdZY8u
|
Rebase looks clean — the require-line conflict with #5 resolved as expected (combined into one |
Fixes pitfall #12 from
docs/tracking/2026-04-23_eigentrust_pitfalls.md.Summary
Adds Int-indexed analogues for PVec to mirror the List
nth-int/length-int/take-int/drop-intquartet. Without these, an algorithm withIntcounters (e.g.int-le budget 0termination checks) that wants to index a PVec had to carry a parallelNatcounter — doubling the arity of every inner helper.Files changed
racket/prologos/lib/prologos/core/pvec.prologos(+49) — addedpvec-length-int,pvec-nth-int,pvec-take-int,pvec-drop-int; require addition fornth-int/take-int/drop-intfromprologos::data::listracket/prologos/lib/prologos/book/PRELUDE(+4) — added new names to the explicit pvec:referracket/prologos/namespace.rkt(+4) — kept in sync withPRELUDEracket/prologos/tests/test-pvec-int-helpers.rkt(new, 124 lines) — 18 rackunit cases: length, nth (positive/negative/OOB/empty), take, drop, take+drop length round-tripfrom-int : Int → NatdecisionSkipped. The pitfall framing was slightly off —
from-intalready exists as a parser keyword (Int → Rat), and the Listnth-intfamily avoids theInt → Natconversion entirely via pure recursion. These new helpers do the same by routing throughpvec-to-list. A standaloneInt → Natwould just be a foot-gun (panic on negative? what semantics?).Implementation note worth review
The first draft defined a private
int-to-nat-clamphelper and calledpvec-slicedirectly.pvec-slice'swhnfrule needsnat-valueto succeed on its lo/hi args, but adefn-definedint-to-nat-clampdoesn't reduce far enough inside the slice'swhnfto satisfynat-value, leaving the slice stuck. Routing throughpvec-to-list+ List helpers +pvec-from-listsidesteps this. Same O(n) complexity as the List counterparts — acceptable parity. Worth noting in case the team wants a primitiveint-to-nat-clamplater for O(log32 n) PVec indexing.Test plan
tests/test-pvec-int-helpers.rkt(18 cases)process-stringwith prelude active — worksraco testof the rackunit file fails to launch on Racket 8.10 due to the pre-existing(thread #:pool 'own)blocker (test-pvec.rktfails identically). CI on this PR uses Racket 8.14 — should run cleanlybenchmarks/micro/info.rktskip; redundant once PR Migrate bench-bsp-le-track2.rkt to current ATMS API (CI unblock) #10's actual migration lands)Commits
f925496— primary fix (helpers + tests + PRELUDE/namespace sync)0678170— CI fix (skip stale bench file)https://claude.ai/code/session_01MbncYJnrvjzhbVWw4xGi5x
Generated by Claude Code