Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
120 commits
Select commit Hold shift + click to select a range
1dc9a2c
ocapn: phase 0 port of OCapN/Goblins to Prologos
claude Apr 27, 2026
a4852e2
ocapn: bring the suite to green on Racket 8.10 — 117/117 tests pass
claude Apr 27, 2026
300d384
ocapn: tcp-testing-only netlayer + simulated netlayer + locator
claude Apr 27, 2026
7e6fc5d
goblin-pitfalls: revise #0-#10 after testing on a real toolchain
claude Apr 27, 2026
d7982df
goblin-pitfalls: slim DELETED entries to one-liners; delete #15
claude Apr 27, 2026
fbc93af
ocapn: address 10 Copilot review comments on PR #28
claude Apr 27, 2026
d0c88db
ocapn: revert recursive table replace — caused 120s CI timeouts
claude Apr 27, 2026
d90441e
ocapn: add Level-3 acceptance + Copilot-fix coverage tests (10 cases)
claude Apr 28, 2026
737d5d6
ocapn: convert defn-match-on-arg to canonical multi-arity defn
claude Apr 28, 2026
5daee01
ocapn: revert transport-eq? to nested match — multi-arity hits #18
claude Apr 29, 2026
9097c9b
ocapn: Phase 1 — Syrup wire codec (encoder + decoder + 13 tests)
claude Apr 29, 2026
88e208d
ocapn: Phase 2 — CapTP frame codec (encoder + decoder + 6 tests)
claude Apr 29, 2026
f96abea
ocapn: Phase 3 — live tcp-testing-only handshake (2 tests)
claude Apr 29, 2026
2e11b64
ocapn: Phase 4 — verified interop with @endo/ocapn (CI gate + 22 vect…
claude Apr 29, 2026
6c107d5
ocapn: Phase 5 — live Racket↔Node wire exchange (bidirectional, 2 tests)
claude Apr 30, 2026
d72595d
ocapn: Phase 6 — bidirectional op:start-session handshake (1 test)
claude Apr 30, 2026
d238e62
ocapn: Phase 7 — multi-message conversation (3 frames each direction)
claude Apr 30, 2026
fd7b737
ocapn: Phase 8 — conversational state machine (RPC-style, 1 test)
claude May 1, 2026
eef4ac7
ocapn: skip interop tests in main suite — covered by interop CI
claude May 1, 2026
c88423f
ocapn: skip heavy OCapN tests in runner, run them via raco test directly
claude May 1, 2026
88b41b9
ocapn: Phases 9/10/11 + decoder perf profile
claude May 1, 2026
70e36f7
ocapn: Phase 12 — wire-OUT bridge (vat resolution → outbound op:deliv…
claude May 4, 2026
7c68af6
ocapn: Phase 13 — decoder perf fix (25× faster on multi-arity records)
claude May 4, 2026
6341a4e
ocapn: Phase 14 — state-aware dispatch for op:listen + op:gc-*
claude May 4, 2026
958e894
ocapn: Phase 15 — session question-table mapping (auto-allocate local…
claude May 4, 2026
1909fd3
ocapn: Phase 16 — wire-OUT pump (auto-emit bytes on promise resolution)
claude May 4, 2026
4e0c9c2
ocapn: Phase 17 — Error type for broken promises (3 tests)
claude May 4, 2026
525aef9
ocapn: Phase 18 — connection lifecycle composer (4 tests)
claude May 4, 2026
d209afb
ocapn: Phase 19 — syrup-bytes ctor (opaque bytestrings)
claude May 4, 2026
573a040
ocapn: Phase 20 — UTF-8 byte-length aware encoder
claude May 4, 2026
258220d
ocapn: Phase 21 — promise pipelining (pipeline-deliver target-aware s…
claude May 4, 2026
106c331
ocapn: Phases 22 + 23 — DEFERRED with rationale (language blockers)
claude May 4, 2026
e96611a
ocapn: post-implementation review + roadmap link
claude May 4, 2026
ea5a943
ocapn: distill PIR lessons into principles documents
claude May 4, 2026
ec5948a
ocapn: Phase 24 scaffolding — bridge-driven responder interop test
claude May 4, 2026
06067a4
ocapn: Phase 24 unblock — library helper + hand-coded ops
claude May 4, 2026
a39e8a2
ocapn: Phase 24 — restore decode-op for real interop
claude May 5, 2026
287e37e
ocapn: pitfall #31 — measured decoder perf bug (~150s for 50-byte input)
claude May 5, 2026
81ef576
docs: decoder perf root-cause investigation (pitfall #31)
claude May 5, 2026
30fe7d7
ocapn: extend test-bridge-perf to scaling test (N=1, 5, 10, 20)
claude May 5, 2026
c1eb221
docs: decoder perf fix PIR — 3 attempts, all NEGATIVE
claude May 5, 2026
ab9657e
ocapn: add pitfall #31 profiler harness (skip-listed)
claude May 5, 2026
af3898b
ocapn: revert profiler harness — broke CI's raco pkg install --auto
claude May 5, 2026
a55817f
ocapn: pitfall #31 root cause IDENTIFIED — shift, not cache
claude May 5, 2026
1cdeb1c
docs: substitution perf survey — bug + 5 solutions with citations
claude May 5, 2026
4f6b3f0
interp: pitfall #31 fix — looseBVarRange short-circuit on shift (2.4x…
claude May 6, 2026
ff0ebad
docs: pitfall #31 — issue #45 relationship + bridge-interop re-enable…
claude May 6, 2026
6f2e077
interp: pitfall #31 fix step 2 — looseBVarRange on subst (40.8x speed…
claude May 6, 2026
fa4f717
ocapn: bridge-interop test re-enabled — peer-questioner: don't gate o…
claude May 6, 2026
b2b4a80
ocapn: model the CapTP handshake — bridge emits op:start-session reply
claude May 6, 2026
4b92416
ocapn: model the CapTP handshake as bridge state — single drive call …
claude May 6, 2026
bced0fb
docs: pitfall #32 — multi-constructor data types break cross-module i…
claude May 6, 2026
bececde
docs: pitfall #32 — link to issue #60
claude May 6, 2026
b971818
test: re-enable 4 perf-skipped tests post-looseBVarRange fix
claude May 6, 2026
2723cd5
test: hard-fail OCapN interop tests on missing Node deps; remove from…
claude May 6, 2026
5d12f2a
ocapn(phase 25.1): add op-deliver-to-answer variant; decoder discrimi…
claude May 6, 2026
f406d0b
ocapn(phase 25.2): outbound question table in BridgeState + outbound-…
claude May 6, 2026
dc03b94
ocapn(phase 25.3): dispatch op-deliver-to-answer — resolve local prom…
claude May 6, 2026
f633e5a
ocapn(phase 25.4): questioner-side interop test — Prologos asks Node,…
claude May 6, 2026
ddd6397
docs: pitfall #33 — :refer-all chains don't establish type identity f…
claude May 6, 2026
359c2a6
docs: pitfall #33 — link to issue #61
claude May 6, 2026
275b159
ocapn(phase 26): multi-turn questioner conversation interop test
claude May 6, 2026
b0fd931
ocapn(phase 27): bridge-send-question — atomic vat-side ask helper
claude May 6, 2026
bff5555
ocapn(phase 28): high-level connection-ask — questioner-side companio…
claude May 6, 2026
59f3da7
ocapn(phase 29): bidirectional peer-to-peer interop — Prologos asks A…
claude May 6, 2026
ba0d362
ocapn(phase 30): high-level captp-ask in core.prologos
claude May 6, 2026
2ae40b3
docs: OCapN GC plan — manual reference counting first, automatic via …
claude May 6, 2026
32f3fea
ocapn(phase 31): bs-remove-question + bs-remove-outbound-question hel…
claude May 6, 2026
e1d2dda
ocapn(phase 32): outbound GC release API — release-answer + release-i…
claude May 6, 2026
85d2464
ocapn(phase 33): inbound op:gc-answer wired to bs-remove-question
claude May 6, 2026
2e62fc9
docs: OCapN refr-import plan — runtime Refr ADT, decoder integration,…
claude May 6, 2026
3d8c069
ocapn(phase 34a): Refr ADT — single-constructor with kind tag, smart …
claude May 6, 2026
14a68a0
ocapn(phase 34c): imports-refcount field on BridgeState + bs-incr-imp…
claude May 6, 2026
372fb9d
ocapn(phase 34b): decoder hook — extract refrs from inbound args, aut…
claude May 6, 2026
9db5901
ocapn(phase 34e): release-import wired to bs-decr-import — actual cou…
claude May 6, 2026
7c797e6
ocapn(phase 34d): refr-to-syrup encoder — Refr → wire-shaped SyrupValue
claude May 6, 2026
2308ce0
ocapn(phase 34f): refr-in-args interop test — auto-track + release en…
claude May 6, 2026
0d08adc
docs: pitfalls #34 + #35 from Phase 34 — data ctor return type implic…
claude May 6, 2026
3811ea7
ocapn(phase 36): exports-refcount field + bs-incr/decr-export + inbou…
claude May 6, 2026
eb48102
ocapn(phase 37): desc:import-object decode + encode — 5th refr kind
claude May 6, 2026
e68af4e
ocapn(phase 37): cross-impl gate — desc:import-object round-trip with…
claude May 7, 2026
2b0304b
ocapn(phase 38): wire-level promise pipelining (responder-side)
claude May 7, 2026
3f1fb74
ocapn(phase 39): questioner-side promise pipelining (sender)
claude May 7, 2026
a0c8620
ocapn(phase 40): pipelining cross-impl gate — Node Q + pipelined chai…
claude May 7, 2026
6dc1b23
ocapn(phase 41): wire-out closure — forward queued msgs on desc:expor…
claude May 7, 2026
1eca865
ocapn(phase 42): local-actor pipeline forwarding — deliver locally on…
claude May 8, 2026
f134733
ocapn(phase 43): wire-out forwarding cross-impl gate + syrup-int deco…
claude May 8, 2026
42f6640
ocapn(phase 44): chained-answer pipelining — forward via wire on reso…
claude May 8, 2026
6f1120c
ocapn(phase 45): break-forward queued delivers' answers — emit Error …
claude May 8, 2026
14c6d7e
ocapn(phase 46): plain-value-as-error forwarding — never drop the queue
claude May 8, 2026
6fca1de
ocapn(phase 47): GC pipelined-msgs after pump emits — keep queue bounded
claude May 8, 2026
a990e2f
ocapn(phase 48): op:listen notification on resolution — wire-out + GC
claude May 8, 2026
532a1e4
ocapn(phase 49): break + plain-value cross-impl gates against @endo/o…
claude May 8, 2026
2e27be7
docs: record pitfalls #36-#38 from OCapN Phase 47-49 + syntax rule
claude May 9, 2026
5df8334
ocapn(phase 50): declarative release-import — auto-emit op:gc-export …
claude May 9, 2026
2f46e29
ocapn(phase 51): late op:listen handling + multi-listener ordering pi…
claude May 9, 2026
ef8d9da
ocapn(phase 52): gift-table scaffolding for desc:import-object handoff
claude May 9, 2026
c0efdd5
ocapn(phase 53): OCapN protocols as session types — specification + d…
claude May 18, 2026
8a103d6
docs(roadmap): capture OCapN branch state + outstanding session-types…
claude May 19, 2026
6d10c58
ocapn(phase 52b): op:deposit-gift / op:withdraw-gift wire ops + dispatch
claude May 19, 2026
97ad93c
ocapn(phase 53.a): refine session payload types from String to CapTPOp
claude May 19, 2026
07d73d3
docs(roadmap): record Phase 53.a commit hash
claude May 19, 2026
6d199a6
docs: flag Phase 52b as non-canonical + log methodology pitfall #43
claude May 19, 2026
c28f44e
ocapn(phase 53.d): :throws SyrupValue on broken-promise protocols
claude May 19, 2026
1a67f23
ocapn(phase 52b revised): gift handoff via bootstrap-method dispatch …
claude May 19, 2026
c0991f9
ci(interop): add Phase 52b bootstrap-gift cross-impl test; update roa…
claude May 19, 2026
8f64907
docs: PIR for OCapN Phases 37–53.d (pipelining + GC + session types +…
claude May 19, 2026
5b25d4b
ocapn(compliance gate): add ocapn-test-suite (Python) as CI gate
claude May 19, 2026
7734bdb
ocapn(phase 58): crypto handshake — first ocapn-test-suite test passes
claude May 19, 2026
04d28db
docs(roadmap): record Phase 58 commit hash
claude May 19, 2026
9c2d285
ocapn(rename + ffi + framing): captp-bridge → captp-core; libsodium F…
claude May 19, 2026
13c99fb
ocapn(phase 58.b): crypto FFI accessible from Prologos via captp-core
claude May 19, 2026
d05fa52
ocapn(phase 58.b-2): handshake bytes builder lives in Prologos
claude May 19, 2026
4bec150
ocapn(phase 58.b-3): server uses Prologos handshake; delete Racket fi…
claude May 21, 2026
727cf2e
ocapn(phase 58.c): validate inbound op:start-session, abort on bad ve…
claude May 21, 2026
879e4ef
ocapn(phase 58.d): verify inbound op:start-session location signature
claude May 21, 2026
4434bf4
docs(roadmap): record OCapN Phase 58.b/c/d completion
claude May 21, 2026
ba19595
ocapn(phase 58.e): gate op_abort test_abort_before_setup
claude May 21, 2026
532912d
ocapn(phase 59a): server drives captp-core connection-step
claude May 22, 2026
f78b921
docs(roadmap): record OCapN 58.e + 59a; break out 59b scope
claude May 22, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
69 changes: 69 additions & 0 deletions .claude/rules/prologos-syntax.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,13 +30,77 @@
```
Body at the **same** indent as `|` is a layout violation (currently produces a hard parser error; tracked in issue #27 for diagnostic improvement). Body indent must be **strictly greater than** the `|` column.

- **Never write three-deep nested `match` as a single function body.** Three-deep nested `match` inside a single function body fails to elaborate at *import time* with "Unbound variable" errors in Prologos's current elaborator. The fix is mechanical: factor into a chain of single-match helpers. Each helper does one `match`, returns to the caller, the caller does the next `match`. Same semantics for the user, no elaborator failure. OCapN Phases 15 and 21 both shipped this workaround; codified after the second occurrence. Until the underlying elaborator bug is fixed, this is the safe pattern.
```
;; WRONG — fails at import time
defn pipeline-deliver [target args v]
match [lookup-actor target v]
| some _ -> send-only target args v
| none ->
match [lookup-promise target v]
| none -> v
| some pst ->
match pst
| pst-unresolved msgs -> ...
| _ -> v

;; RIGHT — helper chain
spec deliver-to-promise Nat SyrupValue PromiseState Vat -> Vat
defn deliver-to-promise [pid args pst v]
match pst | pst-unresolved msgs -> ... | _ -> v

spec deliver-to-promise-or-drop Nat SyrupValue Vat -> Vat
defn deliver-to-promise-or-drop [target args v]
match [lookup-promise target v]
| none -> v
| some pst -> deliver-to-promise target args pst v

spec pipeline-deliver Nat SyrupValue Vat -> Vat
defn pipeline-deliver [target args v]
match [lookup-actor target v]
| some _ -> send-only target args v
| none -> deliver-to-promise-or-drop target args v
```

## Application style

- **Uncurried** -- `defn foo [x y z] body`, `spec f A B -> C`. Multiple arguments in one bracket group.
- **Prefer partial application with wildcards** over inline lambdas -- `[int* _ 2]` rather than `[fn [x] [int* x 2]]`. Use `fn` only when the lambda body is complex enough to need named parameters.
- **Pipeline `|>` and `compose`** for chaining named functions -- `|> 5 inc dbl sqr` is idiomatic.
- **Eval is implicit** -- write `[f x]` not `eval [f x]`. Top-level expressions just evaluate.
- **Don't wrap outer tree** -- top-level forms are implicit.
- **Keep multi-arg applications on a single line.** A continuation line whose first token is a bare identifier is parsed as a sibling form, not as more args to the previous head. Pitfalls #36 and #38 (goblin-pitfalls log) both stem from this:
```
;; WRONG — `es as qs p oqs ir er pm` is parsed as an application of `es` to 7 args
bridge-state [list-filter-listeners-by-notified ls notified]
es as qs p oqs ir er pm

;; WRONG — `let X := EXPR` value can't span lines; reader sees no value for X
let step1 := [captp-incoming-with-state op1 [alloc-vat sa]
[bridge-state-with-our-session ver loc]]

;; RIGHT — all positional args on the function-head line; break BEFORE a bracketed sub-call (which becomes the last arg) if the line gets too long
bridge-state [list-filter-listeners-by-notified ls notified] es as qs p oqs ir er pm
```
If a line is unavoidably long, factor a sub-expression to a separate `let` or top-level helper rather than splitting positional args.

- **Single-arg `defn` over a `data` type uses `defn name [arg] match arg | ...`, NOT multi-arity `defn name | [pat1] -> ... | [pat2] -> ...`.** The latter shape can cause the elaborator to lift a phantom 2nd parameter (pitfall #37 in goblin-pitfalls). Multi-arity `defn` is fine for 2+ args; for one-arg over a data type, prefer `match`:
```
;; WRONG — inferred type becomes `PromiseState SyrupValue -> [Option SyrupValue]` (2 args)
spec resolution-syrup-of-pst PromiseState -> [Option SyrupValue]
defn resolution-syrup-of-pst
| [pst-unresolved _] -> none
| [pst-broken r] -> some [wrap-error r]
| [pst-fulfilled v] -> some v

;; RIGHT — inferred type matches the spec
spec resolution-syrup-of-pst PromiseState -> [Option SyrupValue]
defn resolution-syrup-of-pst [pst]
match pst
| pst-unresolved _ -> none
| pst-broken r -> some [wrap-error r]
| pst-fulfilled v -> some v
```

## Type annotations

Expand All @@ -59,6 +123,11 @@
- Trait methods: short names (`eq?`, `from`, `add`)
- Module paths use `::` not `.` -- `str::length`, `prologos::data::nat`
- Dot access is for map keys -- `user.name` -> `[map-get user :name]`
- **NEVER use `->` in identifiers** (silently fails to compile per pitfall #35). Use `-to-` for converters: `refr-to-syrup`, `op-to-syrup`, `syrup-to-op`. The Common Lisp / Scheme convention `refr->syrup` is incompatible with Prologos's WS-mode reader, which parses `->` as the function-arrow type operator inside any identifier.

## Data type definitions

- **Constructor signatures have IMPLICIT return type** (per pitfall #34). Write `data X { ctor : T1 -> T2 }` to mean "ctor takes 2 args of types T1 and T2 and returns X." Do NOT write `ctor : T1 -> T2 -> X` — that means "takes 3 args (T1, T2, X) and returns X." Existing examples: `data Listener { listener : Nat -> Nat }`, `data QEntry { q-entry : Nat -> Nat }`. Symptom of getting it wrong: smart constructors fail with `Type mismatch [Pi T -> Result -> Result]`.

## Nat vs Int

Expand Down
3 changes: 2 additions & 1 deletion .claude/rules/testing.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@
- **Full suite**: add `--all` flag. Also: `--jobs N`, `--timeout N`, `--no-record`, `--no-skip`
- **Targeted tests (CRITICAL — use this, not bare `raco test`, after any production edit)**: `racket tools/run-affected-tests.rkt --tests tests/test-X.rkt --tests tests/test-Y.rkt` runs specific files with SCOPED PRECOMPILE (driver.rkt + the listed tests, ~0.5s incremental). This keeps test `.zo` linklets fresh after export changes in production modules, which `raco test FILE` alone does NOT do. Repeat `--tests FILE` per file (conventional command-line multi-arg pattern). **When to use targeted mode**: any time you've edited production code AND want to run specific tests — e.g., iterating on a failing test during development, verifying a fix hits the right paths, confirming a subset of tests before running the full suite. Avoids the class of "passes individually, fails in batch" / "reference to a variable that is not exported" (linklet mismatch) errors. Observed failure pattern: `raco make typing-propagators.rkt` + `raco test tests/test-X.rkt` → test's cached `.zo` holds a stale linklet snapshot of typing-propagators's exports; when exports changed, linklet-mismatch or silent wrong-results. The targeted runner's scoped precompile closes this gap. Origin: PPN 4C Phase 3c retrospective (2026-04-20) — the earlier sessions' runs didn't hit it because changes were internal (no export-surface changes); Phase 3c added multiple new provides and surfaced the gap.
- **Reporting**: `racket tools/benchmark-tests.rkt --report` / `--trend FILE` / `--compare REF` / `--slowest N`
- **Skip list**: `tests/.skip-tests` -- 2 pathological perf tests skipped by default (use `--no-skip` to include)
- **Skip list**: `tests/.skip-tests` -- pathological perf tests + batch-worker-incompatible tests skipped by default (use `--no-skip` to include). When a test is genuinely incompatible with the batch worker (e.g., spawns subprocesses with `(exit 0)` skip-guards that kill the worker, exceeds the 120s per-file timeout for legitimate reasons), partition rather than patch the worker. The OCapN track skipped 9 tests (`test-ocapn-vat.rkt`, `test-ocapn-acceptance-l3.rkt`, plus 7 Node-spawning interop tests) and ran them via `raco test` directly in a separate CI workflow (`.github/workflows/interop.yml`). Patching the batch worker to handle subprocess-spawning would have been 1+ days of compiler infrastructure work; the partition was 30 minutes. Rule: **when test infrastructure mismatches a test's needs, partition rather than patch**; document in `.skip-tests` with the reason and the alternate runner.
- **Microbench from Phase 1 in protocol stacks** -- protocol stacks (codecs, wire serializers, parsers over byte streams) have asymmetric latency hidden in tight inner loops. A microbench at the encode/decode boundary catches it during Phase 1 instead of Phase 9. OCapN's decoder shipped slow through Phases 1–8 because every test was a single small case; Phase 9's pipelined RPC was the first thing fast enough to expose the regression, and Phase 13 needed a tail-recursive accumulator + inline destructure for a 25× speedup. **Operational rule**: when implementing a codec/serializer/parser, every Phase 1 should include `tools/bench-ab.rkt` runs of encode + decode at varying message counts (1, 10, 100). Catching the perf bug at Phase 1 saves the multi-phase regression chain.
- **Guideline**: Keep test files under ~20 test-cases / ~30s wall time for good thread-pool parallelism
- **Delimiter check after .rkt edits**: Run `tools/check-parens.sh <file>` after EVERY edit to a `.rkt` file, BEFORE `raco make`. Instant (~100ms, read-only). Catches mismatched `()`, `[]`, `{}` with exact line:column. Eliminates trial-and-error bracket-balancing during compilation.
- **Pre-compilation**: Both runners call `raco make driver.rkt` AND all test files before tests (skip with `--no-precompile`). The suite runner's `precompile-modules!` compiles BOTH — test files are NOT in driver.rkt's dependency graph, so `raco make driver.rkt` alone does NOT recompile them.
Expand Down
4 changes: 4 additions & 0 deletions .claude/rules/workflow.md
Original file line number Diff line number Diff line change
Expand Up @@ -65,3 +65,7 @@
- **All tests green before moving on** -- never proceed to the next phase with failing tests. A failing test is a signal, not noise. Investigate immediately. If a failure is genuinely pre-existing and unrelated, document it explicitly with a tracking reference before proceeding.
- **Update Master Roadmap on design docs and PIRs** -- writing a track design document or a PIR is a signal to update `docs/tracking/MASTER_ROADMAP.org`. Link the new design doc and/or PIR in the relevant track's row. Update track status (⬜ → 🔄 → ✅). If the design reveals new tracks or cross-dependencies, add them. The Master Roadmap is the single source of truth for all work — if it's not linked there, it's invisible.
- **Review PIR methodology before writing a PIR** -- before writing any Post-Implementation Review, read `docs/tracking/principles/POST_IMPLEMENTATION_REVIEW.org` to refresh on the 16 questions, the template structure (§1–§9+), format principles, and anti-patterns. The Track 4 PIR was initially written as a sparse 7-section summary; reviewing the methodology revealed it was missing Bugs Found and Fixed, What Went Well/Wrong/Lucky/Surprised, Architecture Assessment, What's Next, Key Files, and cross-references to prior PIRs. A PIR written without consulting the methodology will systematically omit the evaluative and meta-learning sections that make PIRs genuinely valuable. §16 must include a longitudinal survey of the **10 most recent PIRs** (not just 2-3) — the value of longitudinal analysis is in spotting slow-moving trends.
- **Read your own pitfalls log before each phase** -- when a track has produced a pitfalls/issues log (e.g., `2026-04-27_GOBLIN_PITFALLS.md`), grep it for syntactic constructs the next phase will use BEFORE writing code. The log is index, not just record. OCapN Phase 21 retried multi-arity defn with `nil` ctor pattern despite pitfall #18 being on file from Phase 0 — wasted ~10 minutes. Same applies to language-bug logs across tracks: scan for relevant entries during phase BARs.
- **Codify a 2-occurrence pattern within a track immediately** -- the existing rule "patterns spanning 3+ PIRs are codification-ready" is for cross-track patterns. For *intra-track* recurrence, **2 occurrences is enough** to codify. OCapN's nested-match-import-error fix shipped at Phase 15 without being added to syntax.md or the pitfalls log; Phase 21 rediscovered the same bug and re-derived the same helper-chain workaround. When a workaround is needed twice in the same track, add it to the pitfalls log AND to the relevant rule file (e.g., `prologos-syntax.md`) immediately, not after the third occurrence.
- **Pitfalls-log second-pass scrutiny before merge** -- when a track produces a pitfalls/issues log, schedule a "second-pass scrutiny" phase before merge. Re-test each entry against the current toolchain; delete entries that are false (out-of-scope, env-specific, first-pass diagnoses that were wrong). OCapN's pitfalls log started at 30 entries and shrank to 17 after re-testing (commits `447379a`, `b9c33ba` deleted 12 false claims). A 30-entry catalogue with 12 false entries is misleading; an 18-entry catalogue with all real entries is actionable. Tracks ending without this scrutiny pass leave noise in the project's permanent record.
- **External cross-impl gates are mandatory test infrastructure for protocol ports** -- when a track ports a protocol against an external counterparty (e.g., `@endo/ocapn`, an XMPP server, a Matrix server), a foreign-generated golden + drift gate is mandatory Phase 1 work, not optional. Pattern: write a generator script that runs inside the foreign implementation, commit its output as a fixture, add a CI step `git diff --exit-code` on the fixture. Hand-written wire vectors are a last-resort substitute. OCapN's drift gate caught 4 of 5 interop bugs that hand-written tests would have missed. The D:I ratio target (1.5:1 longitudinal) is **conditional on external validation existence** — with an external counterparty + drift gate, low D:I is fine; without one, low D:I is technical debt.
122 changes: 122 additions & 0 deletions .github/workflows/interop.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
name: Interop (cross-runtime Syrup byte-equality)

# Runs the Phase-4 cross-impl test:
# - regenerates the Syrup fixture from `@endo/ocapn` (the JS reference)
# - asserts the regenerated bytes match the committed fixture (drift gate)
# - runs the Racket cross-impl test that pins Prologos's encoder against
# the JS bytes
#
# This catches:
# - drift in Prologos's wire codec (existing fixture catches it)
# - drift in @endo/ocapn's wire format (regenerate-then-diff catches it)
#
# Design doc: docs/tracking/2026-04-29_OCAPN_INTEROP_DESIGN.md

on:
push:
branches: [main]
pull_request:

jobs:
syrup-byte-equality:
runs-on: ubuntu-latest
timeout-minutes: 30

steps:
- uses: actions/checkout@v4

- name: Install Racket
uses: Bogdanp/setup-racket@v1.11
with:
version: '9.0'

- name: Install Node 22
uses: actions/setup-node@v4
with:
node-version: '22'

- name: Install JS interop deps (@endo/ocapn + @endo/init)
run: |
cd tools/interop
npm install --no-audit --no-fund

- name: Regenerate Syrup fixture from @endo/ocapn
run: |
node tools/interop/gen-syrup-vectors.mjs > /tmp/regen.txt

- name: Drift gate — committed fixture must match @endo/ocapn output
run: |
if ! diff -u racket/prologos/tests/fixtures/syrup-cross-impl.txt /tmp/regen.txt; then
echo ""
echo "FIXTURE DRIFT — @endo/ocapn output no longer matches the"
echo "committed fixture. Either:"
echo " - the JS reference impl changed its wire format (genuine"
echo " upstream change — investigate before regenerating)"
echo " - or someone bumped @endo/ocapn locally and forgot to"
echo " refresh the fixture: re-run"
echo " \`node tools/interop/gen-syrup-vectors.mjs > racket/prologos/tests/fixtures/syrup-cross-impl.txt\`"
echo " and commit the result."
exit 1
fi

- name: Install Prologos deps
run: cd racket/prologos && raco pkg install --auto --skip-installed

- name: Pre-compile modules
run: cd racket/prologos && raco make driver.rkt

- name: Run cross-impl test (Phase 4 — static byte-equality)
run: cd racket/prologos && raco test tests/test-ocapn-syrup-cross-impl.rkt

- name: Run live-interop test (Phase 5 — real TCP exchange Racket↔Node)
run: cd racket/prologos && raco test tests/test-ocapn-live-interop.rkt

- name: Run handshake test (Phase 6 — bidirectional op:start-session)
run: cd racket/prologos && raco test tests/test-ocapn-handshake.rkt

- name: Run conversation test (Phase 7 — 3-frame multi-op exchange)
run: cd racket/prologos && raco test tests/test-ocapn-conversation.rkt

- name: Run RPC test (Phase 8 — conversational state machine)
run: cd racket/prologos && raco test tests/test-ocapn-rpc.rkt

- name: Run pipelined test (Phase 9 — multi-turn RPC)
run: cd racket/prologos && raco test tests/test-ocapn-pipelined.rkt

- name: Run abort test (Phase 10 — graceful op:abort teardown)
run: cd racket/prologos && raco test tests/test-ocapn-abort.rkt

- name: Run desc:import-object test (Phase 37 — 5th refr kind cross-impl round-trip)
run: cd racket/prologos && raco test tests/test-ocapn-import-object-interop.rkt

- name: Run pipelining test (Phase 40 — promise-pipelining receiver cross-impl)
run: cd racket/prologos && raco test tests/test-ocapn-pipelining-interop.rkt

- name: Run pipeline-forwarding test (Phase 43 — wire-out closure cross-impl)
run: cd racket/prologos && raco test tests/test-ocapn-pipeline-forwarding-interop.rkt

- name: Run break + plain-value test (Phase 49 — break-forwarding + plain-value-as-error cross-impl)
run: cd racket/prologos && raco test tests/test-ocapn-break-plain-interop.rkt

- name: Run bootstrap-gift wire-shape test (Phase 52b — gift handoff bootstrap-method dispatch cross-impl)
run: cd racket/prologos && raco test tests/test-ocapn-bootstrap-gift-interop.rkt

- name: Install deps for OCapN test suite
run: |
# The ocapn-test-suite needs cryptography, cffi, and stem.
# cryptography + cffi via pip; stem via apt (avoids wheel
# build failure on cffi-less environments). libsodium23 is
# the runtime library the Prologos crypto FFI binds.
pip3 install --quiet cryptography cffi
sudo apt-get install -y python3-stem libsodium23

- name: Run upstream ocapn-test-suite against Prologos peer (Phase 58.c/58.d)
# The Racket OCapN test server speaks the canonical 4-field
# signed op:start-session (libsodium FFI via Prologos) and
# validates the inbound op:start-session, aborting on an
# unsupported CapTP version or an invalid location signature.
# run-ocapn-test-suite.sh runs the selected upstream tests
# (tools/interop/ocapn-run-tests.py) and exits non-zero if the
# milestone (>= 3 selected tests passing) regresses. The
# remaining modules (op:deliver, op:gc, ...) land in Phase 59+.
run: bash tools/interop/run-ocapn-test-suite.sh 22045
18 changes: 17 additions & 1 deletion .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,27 @@ jobs:
with:
version: '9.0'

# Node + @endo/ocapn are required by the OCapN interop tests
# (bridge-interop, handshake, conversation, rpc, pipelined,
# abort, live-interop, syrup-cross-impl). The tests hard-fail
# if these deps are missing rather than skipping silently —
# see racket/prologos/tests/test-ocapn-*.rkt's
# `(unless (interop-deps-present?) (error ...))` guards.
- name: Install Node 22
uses: actions/setup-node@v4
with:
node-version: '22'

- name: Install JS interop deps (@endo/ocapn + @endo/init)
run: |
cd tools/interop
npm install --no-audit --no-fund

- name: Install dependencies
run: cd racket/prologos && raco pkg install --auto --skip-installed

- name: Pre-compile modules
run: cd racket/prologos && raco make driver.rkt

- name: Run full test suite
- name: Run full test suite (skipped: heavy reduction perf tests)
run: cd racket/prologos && racket tools/run-affected-tests.rkt --all --no-record
Loading