Skip to content

unify: lenient {m0, mw} compatibility (eigentrust pitfall #2)#17

Draft
kumavis wants to merge 1 commit into
mainfrom
claude/fix-eigentrust-pitfall-2-closmult
Draft

unify: lenient {m0, mw} compatibility (eigentrust pitfall #2)#17
kumavis wants to merge 1 commit into
mainfrom
claude/fix-eigentrust-pitfall-2-closmult

Conversation

@kumavis
Copy link
Copy Markdown
Contributor

@kumavis kumavis commented Apr 25, 2026

Fixes pitfall #2 from docs/tracking/2026-04-23_eigentrust_pitfalls.md.

Summary

spec scale-vec Rat [List Rat] -> [List Rat]
defn scale-vec [s xs]
  [map (fn [x : Rat] [rat* s x]) xs]

failed with a QTT multiplicity error. The pitfall report described it as "closure capture of s at multiplicity 1 vs many uses." Investigation showed that diagnosis is wrong — the closure is fine. The actual root cause is at the kind level.

Root cause (the surprise)

Tracing checkQ-fb revealed:

  • List is registered with kind Pi(m0, Type, Type)
  • map's {C : Type → Type} elaborates to Pi(mw, Type, Type) (the inner defaults to mw via surf-arrow)
  • Implicit type-arg unification then tries to unify m0 and mw and fails

The "closure captures linearly" message was misleading — the failure was in implicit-type-arg unification at the kind level, not in the value-level closure mult system.

Approach (a) — surgical {m0, mw} lenience in unify-mult

In unify.rkt's classify-mult-problem: extend the compatibility table so {m0, mw} (and {mw, m0}) unify successfully without solving either to the other.

Why principled: m0 ("zero runtime uses") trivially satisfies mw ("any number, including zero"). Treating these as compatible is sound. {m1, *} pairs stay incompatible — linearity is a real value-level constraint.

Latent bug exposed: three solve-mult-meta! call sites in typing-core.rkt re-solve already-solved metas, which the new lenience path makes observable. Guarded with mult-meta-solved? checks.

Deferred to a future PR

Approach (c) — full type-driven closure-capture mult inference from the callee's spec — is the principled long-term fix for cases the {m0, mw} lenience doesn't cover (e.g., spec-position threading into closure elaboration). Out of scope here.

Files changed

  • racket/prologos/unify.rkt (~12 LoC) — classify-mult-problem extended with {m0, mw} compatibility case
  • racket/prologos/typing-core.rkt — three solve-mult-meta! calls guarded with mult-meta-solved? (latent bug exposed by the unify change)
  • racket/prologos/tests/test-unify.rkt — replaced one stale assertion with two new tests covering both directions of the new lenience
  • racket/prologos/tests/test-closure-mult.rkt (new, 10 cases)

Test plan

  • Eigentrust scale-vec example now produces '[1/4 1/8 1/16] : [List Rat]
  • All 105 mult/qtt-related tests pass
  • All 51 unify tests pass
  • All 141 capability/qtt-pipeline tests pass
  • Full suite: 7818 tests / 545s — 9 failures, all pre-existing environmental (prologos/propagator and rackcheck collections unavailable in the agent's Racket install). None are caused by this change.
  • 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. 5fb3930 — primary fix in unify.rkt + typing-core.rkt guards + tests
  2. 691addb — CI fix (skip stale bench)

https://claude.ai/code/session_01MbncYJnrvjzhbVWw4xGi5x


Generated by Claude Code

@kumavis kumavis force-pushed the claude/fix-eigentrust-pitfall-2-closmult branch from 691addb to b4560bf Compare April 26, 2026 01:03
@kumavis
Copy link
Copy Markdown
Contributor Author

kumavis commented Apr 26, 2026

this should wait hiero's elaboration refactor

@kumavis kumavis force-pushed the claude/fix-eigentrust-pitfall-2-closmult branch from b4560bf to 2ff728d Compare April 26, 2026 03:01
…uctor args

Eigentrust pitfall memo (2026-04-23) #2 reports that

    spec scale-vec Rat [List Rat] -> [List Rat]
    defn scale-vec [s xs] [map (fn [x : Rat] [rat* s x]) xs]

fails QTT with "Multiplicity violation," even though no parameter is
annotated linear. The user's workaround was to inline the recursion.

ROOT CAUSE (diagnosed via tracing checkQ-fb / inferQ-app): a kind-level
Pi multiplicity mismatch. Type constructors like List are registered
with kind `Pi(m0, Type, Type)` (the type arg is erased — `data List {A}`
uses `:0` mult on its implicit binder). Generic combinators' specs like
`(spec map {A B : Type} {C : Type -> Type} ...)` elaborate the inner
`Type -> Type` via surf-arrow, which defaults `->` to `mw`. So map's `C`
parameter expects `Pi(mw, Type, Type)`. When List flows into that slot
via implicit insertion, QTT's `unify` rejects the mult mismatch even
though typing-core succeeds (typing-core's check uses Pi-domain only,
not mult). The user-visible error wrongly accuses the closure body of
the violation; the actual conflict is the kind unification.

FIX (approach (a) — targeted lattice lenience): in
`classify-mult-problem` (unify.rkt), treat the {m0, mw} pair as
compatible. Rationale: m0 = "zero runtime uses" trivially satisfies
mw = "any number of uses (including zero)." {m1, m0} and {m1, mw} stay
incompatible — linear (m1) IS a real value-level usage constraint.

SIDE EFFECT FOLLOW-UP: making more unify paths succeed exposed a
pre-existing latent bug in typing-core.rkt's check/Pi case
(lines 2143, 2151, 2503): three `solve-mult-meta!` calls were
unguarded, so re-entry on already-solved metas crashed with
`solve-mult-meta!: mult-meta already solved`. Guarded each call with
`mult-meta-solved?` (matches the existing QTT-side guards in
qtt.rkt:2106-2108). This bug existed before but was masked because the
m0/mw mismatch caused unification to fail earlier and short-circuit.

APPROACH SELECTION:
  (a) chosen — targeted unify-mult lenience for {m0, mw}. Surgical,
      covers the kind-level case, doesn't change the rest of QTT.
  (b) rejected — defaulting all closure captures to mw would break
      linearity guarantees in code that relies on the inference.
  (c) deferred — type-driven inference of closure captures from the
      callee's spec is the principled fix but requires touching
      elaborator.rkt + typing-core.rkt + qtt.rkt across multiple
      argument-position detection sites. Approach (c) remains the
      future ideal once the spec-position information flows
      consistently into the closure elaboration site.
  (d) not needed — (a) handles the actual eigentrust case cleanly.

Tests:
- Updated test-unify.rkt: replaced the {m0, mw} reject test with two
  new tests: (i) {m0, m1} still rejects, (ii) {m0, mw} now succeeds.
- Added test-closure-mult.rkt with 10 tests covering: unit tests for
  unify-mult lenience, eigentrust scale-vec reproducer (full
  invocation), foldr/scale-and-shift defns capturing scalars,
  explicit -1> linear annotation still rejected on multi-use, and
  the dict-param mw heuristic regression check.
- Full suite: 7818 tests in 545s, 9 failures all pre-existing
  environmental (collection not found for prologos/propagator +
  rackcheck — same modules unavailable in this Racket install).
- All 105 mult/qtt-related tests pass; all 51 unify tests pass; all
  141 capability + qtt-pipeline tests pass.

Co-authored-by: kumavis <1474978+kumavis@users.noreply.github.com>
Copy link
Copy Markdown
Contributor

@hierophantos hierophantos left a comment

Choose a reason for hiding this comment

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

Substantive review with disposition pending. Let me explain.

Strong PR. Surgical fix (~12 LoC core), self-corrected diagnosis (the original pitfall report's closure-capture framing was wrong; you traced it to the actual kind-level mult mismatch when type constructor Pi(m0, Type, Type) flows into the combinator's Pi(mw, Type, Type) slot), well-tested with both lenience directions and m1 incompatibility regression. The technical fix is sound under QTT's multiplicity semiring partial order (m0 ≤ mw).

We're holding to consider a structural alternative — see #23 — and decide disposition after we audit our prior subtyping infrastructure.

The reasoning, briefly:

The lenience extends unify's semantics from structural-equality to "{m0, mw} compatible." This is a real subtype relation (m0 <: mw in QTT's semiring partial order), and the principled treatment is an explicit subtype? predicate composing with — not extending — unify. Per workflow.md adversarial framing rule, lenience-as-scaffolding risks sticking around even after the structural fix lands. We'd rather pay the architectural cost upfront if it's tractable.

Verified the bug reproduces on current main (post-S2.e). Your fix is correct for the case it covers; the question is whether to land it tactically while the structural fix is in flight, or hold for the structural fix and accept eigentrust's scale-vec blocked in the meantime.

Specifically, our concerns from the review thread (now superseded by the issue):

  1. Lenience-under-unify vs explicit subtyping — addressed by #23's subtype?-as-separate-predicate proposal
  2. Defensive mult-meta-solved? guards as symptom treatment — likely obviated when re-entry semantics are clarified under the structural fix
  3. Coordination with PPN 4C mult-domain universe migration (S2.c-iv, S2.e-iv) — the structural fix can be designed to compose with the universe model from the start

Three observations for if you'd like to engage with #23:

  • The auditing work on unify-mult call sites and the solve-mult-meta! re-entry paths in your investigation is directly reusable in scoping the structural fix
  • The 10 test cases you wrote (especially the m1 regression and the eigentrust reproducer) become acceptance criteria for #23's implementation
  • If you want to author the structural fix, very welcome — help wanted may go on #23 after our prior-art audit. Your familiarity with classify-mult-problem is an advantage

Disposition decision pending the prior-art audit (how much existing subtyping infrastructure we can compose with, refining the LoC estimate). Will update here once we have a clearer picture. Thanks for the depth of the investigation — the kind-level diagnosis is the load-bearing piece for the structural design.

Copy link
Copy Markdown
Contributor

@hierophantos hierophantos left a comment

Choose a reason for hiding this comment

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

Updating to formal decline (request-changes) following the prior-art audit summarized in #23 comment. Short version: the structural fix is more tractable than initially estimated.

Audit found ~80% of the subtyping infrastructure already exists (subtype-predicate.rkt, subtype-lattice-merge, variance-aware structural-subtype-ground?, mult-leq partial order, mult ctor-desc with component-lattice). The PPN Track 4 design (2026-04-04) explicitly specifies "multiplicities merge via mult-lattice (max)" — the partial-order join is the named target.

The fix is literally "do for mults what's already done for types": add mult-subtype-merge (parallels subtype-lattice-merge), register it with the SRE domain at the 'subtype relation, change Pi's mult variance from = to +, handle mult-meta interaction. ~220-360 LoC, 2-3 days. The lenience-under-unify scaffolding doesn't pay for itself when the structural fix is this small.

What this means for your work:

  1. Closing this PR is what we're requesting — the diff itself is correct work, but landing it would create scaffolding our charter explicitly retires (workflow.md adversarial framing rule)
  2. Your investigation contributions land in #23 — the kind-level diagnosis (type constructor Pi(m0, Type, Type) vs combinator Pi(mw, Type, Type)) is load-bearing for the structural fix's design
  3. Your tests become acceptance criteria — the eigentrust reproducer + m1 incompatibility regression are exactly what #23's implementation must satisfy
  4. help wanted may go on #23 — we'll audit our scheduling in coordination with PPN 4C tropical addendum work; if you'd like to author the structural fix, very welcome. Your classify-mult-problem familiarity is an advantage

Practical implication: eigentrust's scale-vec use case stays blocked until #23 lands. We accept that — 2-3 days work for the proper architecture is better than landing scaffolding that risks sticking around.

Apologies for the disposition reversal between my earlier comment and this one. The prior-art audit changed our cost estimate substantially (initial fear was multi-week; reality is multi-day given existing infrastructure), which moves the structural fix from "ideal-but-expensive" to "ideal-and-tractable." Your initial PR did its job: forced us to look at the architectural seam.

Closing recommendation. Will reopen if our scheduling shifts and we want to land #17 as bridge — but that's not the current plan.

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 pushed a commit that referenced this pull request Apr 27, 2026
All 10 inline comments were legitimate. Three were correctness
issues, three doc/code mismatches, two unbounded-growth bugs in the
assoc-list tables, one mutate-while-iterating bug in the FFI cleanup
helper, and one CI-flakiness fix.

Real bugs
  - vat.prologos:actor-table-set + promise-table-set: replace-or-
    insert instead of unconditional cons. Each delivery turn was
    growing the table without bound and slowing lookups linearly.
    (#28#discussion_r3150426596 + #28#discussion_r3150426729)
  - vat.prologos:deliver-msg: when the target actor doesn't exist,
    BREAK any associated answer-promise instead of dropping the
    message silently. Previously `ask`-against-missing-actor would
    hang on the result-promise forever.
    (#28#discussion_r3150426741)
  - behavior.prologos:step-greeter: append the trailing "!" the
    docstring promised. Implementation now matches "{g}, {n}!".
    (#28#discussion_r3150426776)
  - behavior.prologos:step-counter: add the explicit "get" branch
    the docstring advertised — previously every non-"inc" tag fell
    into the same no-op pile, including "get".
    (#28#discussion_r3150426679)
  - tcp-ffi.rkt:tcp-table-clear!: snapshot keys via hash-keys before
    iterating + closing. The previous in-hash + hash-remove! shape
    can raise an iteration error in Racket.
    (#28#discussion_r3150426813)

Test/flakiness
  - test-ocapn-tcp-testing.rkt: replace fixed port 18763 with a
    listen-on-random-port helper that retries on collisions. CI
    parallelism / port reuse made the fixed-port choice flaky.
    (#28#discussion_r3150426716)

Doc-vs-code mismatches
  - promise.prologos:enqueue / take-queue: clarify LIFO storage
    (cons-onto-head) and that take-queue does NOT update the
    PromiseState. Comments now match the function signatures.
    (#28#discussion_r3150426657 + #28#discussion_r3150426758)
  - core.prologos top docstring: drop the "Promise pipelining
    (send to a promise; flushes on resolution)" claim. Phase 0
    explicitly does NOT flush; only the in-actor FullFiller pattern
    works for pipelining today. Cross-references goblin-pitfall #17.
    (#28#discussion_r3150426694)

Verification: all 149 OCapN tests still pass after the changes
(behavior 13, captp 7, e2e 8, locator 13, message 19, netlayer 14,
pipeline 5, promise 16, refr 6, syrup 22, tcp-testing 5, vat 21).

https://claude.ai/code/session_01YM6gc3cMNH2Ymor4jdZY8u
@kumavis kumavis marked this pull request as draft April 28, 2026 21:06
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
All 10 inline comments were legitimate. Three were correctness
issues, three doc/code mismatches, two unbounded-growth bugs in the
assoc-list tables, one mutate-while-iterating bug in the FFI cleanup
helper, and one CI-flakiness fix.

Real bugs
  - vat.prologos:actor-table-set + promise-table-set: replace-or-
    insert instead of unconditional cons. Each delivery turn was
    growing the table without bound and slowing lookups linearly.
    (#28#discussion_r3150426596 + #28#discussion_r3150426729)
  - vat.prologos:deliver-msg: when the target actor doesn't exist,
    BREAK any associated answer-promise instead of dropping the
    message silently. Previously `ask`-against-missing-actor would
    hang on the result-promise forever.
    (#28#discussion_r3150426741)
  - behavior.prologos:step-greeter: append the trailing "!" the
    docstring promised. Implementation now matches "{g}, {n}!".
    (#28#discussion_r3150426776)
  - behavior.prologos:step-counter: add the explicit "get" branch
    the docstring advertised — previously every non-"inc" tag fell
    into the same no-op pile, including "get".
    (#28#discussion_r3150426679)
  - tcp-ffi.rkt:tcp-table-clear!: snapshot keys via hash-keys before
    iterating + closing. The previous in-hash + hash-remove! shape
    can raise an iteration error in Racket.
    (#28#discussion_r3150426813)

Test/flakiness
  - test-ocapn-tcp-testing.rkt: replace fixed port 18763 with a
    listen-on-random-port helper that retries on collisions. CI
    parallelism / port reuse made the fixed-port choice flaky.
    (#28#discussion_r3150426716)

Doc-vs-code mismatches
  - promise.prologos:enqueue / take-queue: clarify LIFO storage
    (cons-onto-head) and that take-queue does NOT update the
    PromiseState. Comments now match the function signatures.
    (#28#discussion_r3150426657 + #28#discussion_r3150426758)
  - core.prologos top docstring: drop the "Promise pipelining
    (send to a promise; flushes on resolution)" claim. Phase 0
    explicitly does NOT flush; only the in-actor FullFiller pattern
    works for pipelining today. Cross-references goblin-pitfall #17.
    (#28#discussion_r3150426694)

Verification: all 149 OCapN tests still pass after the changes
(behavior 13, captp 7, e2e 8, locator 13, message 19, netlayer 14,
pipeline 5, promise 16, refr 6, syrup 22, tcp-testing 5, vat 21).

https://claude.ai/code/session_01YM6gc3cMNH2Ymor4jdZY8u
hierophantos added a commit that referenced this pull request May 24, 2026
…+ Variant B impl

Builds the A/B measurement harness per addendum §9.4.2.3 (Phase 3B.0
implementation plan). Variant B implementation lands as Phase 3B.0 Pre-0
measurement scaffolding (retires at 3B.B per §9.4.1.6 outcome).

atms.rkt changes (~30 LoC):
- New parameter current-gray-code-aid-ordering? (default #f preserves classical)
- Local gray-code-order-local helper (avoid relations.rkt import cycle)
- solver-amb modified: when parameter is #t, permute alternatives via
  Gray-code ordering BEFORE aid allocation; original alt[i] gets aid with
  bit-position via gray-code-ordered offset
- Provide block: export current-gray-code-aid-ordering?
- Cell-layer/data-layer optimization (assumption-id-n is data); NOT
  scheduler-coupled per Cell/Propagator/Scheduler Orthogonality
- Honest scaffolding label: "Phase 3B.0 Pre-0 measurement scaffolding —
  retires at 3B.B per §9.4.1.6 A/B outcome"

bench-fork-on-union-gray-code.rkt (new ~200 LoC):
- 5 workloads (W1-W5) per §9.4.1.5:
  - W1: single binary union (2 branches)
  - W2: single 4-ary union (4 branches)
  - W3: 5 sequential binary unions (10 branches total)
  - W4: 10 sequential 4-ary unions (40 branches total)
  - W5: worst-case adversarial (35 branches; varied widths)
- Statistical machinery: median + IQR via linear-interpolation percentile;
  warmup runs (default 2) excluded from distribution; n-iter default 10
- A/B variant runner via parameter toggle in same process
- run-ns-ws-all per iteration for fresh prelude env + forked network
- --quick mode (n-iter=3) for smoke testing
- Pre-committed falsification criteria documented in header

Smoke test (--quick, n=3 — NOT measurement-grade):
  W1: A=59.2±0.7 ms, B=60.7±1.3 ms, delta +1.5 ms (+2.6%)
  W2: A=63.4±0.7 ms, B=63.1±0.6 ms, delta -0.4 ms (-0.6%)
  W3: A=67.4±5.8 ms, B=67.4±0.7 ms, delta -0.0 ms (-0.0%)
  W4: A=91.3±0.1 ms, B=90.2±1.1 ms, delta -1.0 ms (-1.1%)
  W5: A=83.0±9.2 ms, B=81.8±0.6 ms, delta -1.2 ms (-1.4%)

All n=3 deltas within ±5% noise floor. Falsification evaluation deferred
to full measurement run (n=10 per §9.4.1.6 protocol).

Targeted tests pass: test-solver-context.rkt (15 tests) +
test-union-types-atms.rkt (26 tests) — 41/41 GREEN.

Suite stability assumed (no broader regressions from atms.rkt parameter
addition; default #f preserves baseline behavior).

Next: full measurement run via the harness (task #17) → evaluate
falsification criteria → close 3B.0 VAG (task #18).
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