Skip to content

pvec: add Int-indexed helpers (eigentrust pitfall #12)#15

Merged
hierophantos merged 1 commit into
mainfrom
claude/fix-eigentrust-pitfall-12-pv12
May 5, 2026
Merged

pvec: add Int-indexed helpers (eigentrust pitfall #12)#15
hierophantos merged 1 commit into
mainfrom
claude/fix-eigentrust-pitfall-12-pv12

Conversation

@kumavis

@kumavis kumavis commented Apr 25, 2026

Copy link
Copy Markdown
Contributor

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-int quartet. Without these, an algorithm with Int counters (e.g. int-le budget 0 termination checks) that wants to index a PVec had to carry a parallel Nat counter — doubling the arity of every inner helper.

Files changed

  • racket/prologos/lib/prologos/core/pvec.prologos (+49) — added pvec-length-int, pvec-nth-int, pvec-take-int, pvec-drop-int; require addition for nth-int/take-int/drop-int from prologos::data::list
  • racket/prologos/lib/prologos/book/PRELUDE (+4) — added new names to the explicit pvec :refer
  • racket/prologos/namespace.rkt (+4) — kept in sync with PRELUDE
  • racket/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-trip

from-int : Int → Nat decision

Skipped. The pitfall framing was slightly off — from-int already exists as a parser keyword (Int → Rat), and the List nth-int family avoids the Int → Nat conversion entirely via pure recursion. These new helpers do the same by routing through pvec-to-list. A standalone Int → Nat would just be a foot-gun (panic on negative? what semantics?).

Implementation note worth review

The first draft defined a private int-to-nat-clamp helper and called pvec-slice directly. pvec-slice's whnf rule needs nat-value to succeed on its lo/hi args, but a defn-defined int-to-nat-clamp doesn't reduce far enough inside the slice's whnf to satisfy nat-value, leaving the slice stuck. Routing through pvec-to-list + List helpers + pvec-from-list sidesteps this. Same O(n) complexity as the List counterparts — acceptable parity. Worth noting in case the team wants a primitive int-to-nat-clamp later for O(log32 n) PVec indexing.

Test plan

  • New tests/test-pvec-int-helpers.rkt (18 cases)
  • Standalone smoke test (16 cases) passes on both Racket 8.10 and Racket 9.1
  • Direct elaboration check: all 4 helpers get correctly-typed Pi types
  • Direct evaluation via process-string with prelude active — works
  • raco test of the rackunit file fails to launch on Racket 8.10 due to the pre-existing (thread #:pool 'own) blocker (test-pvec.rkt fails identically). CI on this PR uses Racket 8.14 — should run cleanly
  • CI fix included (benchmarks/micro/info.rkt skip; redundant once PR Migrate bench-bsp-le-track2.rkt to current ATMS API (CI unblock) #10's actual migration lands)

Commits

  1. f925496 — primary fix (helpers + tests + PRELUDE/namespace sync)
  2. 0678170 — CI fix (skip stale bench file)

https://claude.ai/code/session_01MbncYJnrvjzhbVWw4xGi5x


Generated by Claude Code

kumavis added a commit that referenced this pull request Apr 26, 2026
…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>
@kumavis kumavis force-pushed the claude/fix-eigentrust-pitfall-12-pv12 branch from 0678170 to 922dcb0 Compare April 26, 2026 01:03

@kumavis kumavis left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hiero approved but acknowledges this will all be inducted into a later collection heneralization

Comment thread racket/prologos/tests/test-pvec-int-helpers.rkt Outdated
kumavis added a commit that referenced this pull request Apr 26, 2026
…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>
@kumavis kumavis force-pushed the claude/fix-eigentrust-pitfall-12-pv12 branch 2 times, most recently from 9d23f19 to fac9fb2 Compare April 26, 2026 03:37

@hierophantos hierophantos left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 → Nat skipped 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 diagnosing whnf stuck-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.

kumavis added a commit that referenced this pull request Apr 27, 2026
…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>
kumavis added a commit that referenced this pull request Apr 27, 2026
…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>
kumavis pushed a commit that referenced this pull request Apr 27, 2026
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
kumavis pushed a commit that referenced this pull request Apr 27, 2026
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).
kumavis added a commit that referenced this pull request Apr 28, 2026
…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>
kumavis pushed a commit that referenced this pull request Apr 29, 2026
…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>
@kumavis kumavis force-pushed the claude/fix-eigentrust-pitfall-12-pv12 branch from fac9fb2 to cb8da5f Compare April 30, 2026 21:42
hierophantos added a commit that referenced this pull request Apr 30, 2026
…5-mldef

parse-reader: splice multi-line def := continuations (eigentrust pitfall #15)
kumavis pushed a commit that referenced this pull request May 4, 2026
…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
kumavis pushed a commit that referenced this pull request May 4, 2026
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
kumavis pushed a commit that referenced this pull request May 4, 2026
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).
kumavis pushed a commit that referenced this pull request May 4, 2026
…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
@hierophantos

Copy link
Copy Markdown
Contributor

Rebase looks clean — the require-line conflict with #5 resolved as expected (combined into one :refer [List nil cons zip-with nth-int take-int drop-int]). Same scope as original approval. Merging.

@hierophantos hierophantos merged commit aba5c9b into main May 5, 2026
1 check passed
@hierophantos hierophantos deleted the claude/fix-eigentrust-pitfall-12-pv12 branch May 5, 2026 08:48
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants