A8: process-global brands via @deltic/protocol; loud multi-copy diagnosis (#83) - #105
Merged
Conversation
…83) Consumer evidence (wosh finding 26): package-local deno.json pins put four extra runtime copies in one graph, and every check keyed on class identity fails there latently — instanceof WitError on the first error path only, a foreign Stream silently pumped instead of transferred. - contracts/embedder-api.md amendment A8: every cross-boundary brand is a Symbol.for registry symbol (generation-suffixed; bumping one is an ecosystem migration event); class identity is not part of the embedder API; multi-copy graphs are diagnosed, never refused. - new @deltic/protocol workspace package (dependency-free): the brand vocabulary, canonical error classes, suspending()/isSuspending, the recognition predicates, the copy registry, PROTOCOL_GENERATION. @deltic/runtime/embedder re-exports it all unchanged; consumer import maps gain exactly one new entry (@deltic/protocol). - runtime: boundary recognition converted to brand predicates (instantiate fail path, producer failures); stateful values — Stream/ Future/ErrorContext/resource wrappers — refuse foreign copies with a named cross-copy error incl. the copy census, never the silent producer-adaptation fallback; the unbranded-throw trap and the cross-store assert append the census hint. Copy-private internals (SharedStreamImpl, CopyEnd, NameCollisionError) deliberately keep instanceof / carry no brand. - wasi-shims: Pollable/ExitError branded; poll() already consumes pollables structurally. - pins: tools/release-bundle/dual_copy_test.ts — the release bundle is the genuine second copy (query-string cache-busting shares every submodule and would pass vacuously); runtime/tests/embedder/ cross_copy_test.ts covers the foreign paths with hand-rolled brands. - docs/consumers.md: the app-owned import-map convention (#83 layer 2). - drive-bys: smoke-tls recipe gains --allow-env (common.ts's env reads landed in fab5c2e without it — broken at HEAD); smoke-c0's non-workspace config maps the new bare specifier; ports lockfiles record the new workspace link. just gates: full pass (jsc-pinned skipped — no arm64 channel).
lann
enabled auto-merge
August 11, 2026 03:10
main took A8 for the future-drop-abandonment amendment (deltic#90/#97, c855b33), so the module-identity amendment renumbers A8 -> A9 everywhere (contracts section + all code comments, messages, and test names; main's A8 references untouched). Other resolutions: smoke-tls --allow-env fixed on both sides — kept main's comment with the scoped flag variant; import unions in resources.ts / task/streams.ts; cross_copy_test's WrapperState literal gains #86's lends/pendingDrop fields. Full just gates re-run on the merged tree: runtime 451/0/3, protocol 22/0, wasi-shims 52/0/1, ct-runner 17/0/1, bundle 2/0 (incl. the A9 dual-copy pin), conformance 69/0, sched-seeds x2, examples, translate, ports, webrtc 10/0, sm-pinned OK (jsc skipped arm64), websocket 55/55, smoke-tls PASS, smoke-c0 PASS, browsers chromium+firefox OK.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Implements the operator decision on #83, restructured per the review discussion: the identity mechanism is
Symbol.forregistry brands (layer 1),@deltic/protocolis their home — a dependency-free vocabulary package — not the mechanism (package identity is exactly what keeps failing in a registry-less ecosystem, and cannot survive the two-bundles case at all), and multi-copy graphs are diagnosed, never refused (layer 3).Contract (amendment A8)
Symbol.forkey (deltic.witError/1, …): N copies agree on every brand by construction, including separately-built bundles. Bumping a generation is a breaking vocabulary change — an ecosystem migration event.instanceofstill works and stays the documented spelling in single-copy graphs; the predicates (isWitError,isTrap, …) are the multi-copy-robust form. NoSymbol.hasInstanceoverride (a consumer subclass would inherit it and match any branded value).WitError/suspending()mark is fully honored by copy A. Stateful values (Stream/Future/ErrorContext/resource wrappers) are recognized-but-refused with a named cross-copy error listing both copies' URLs — replacing the silent producer-adaptation fallback that would pump a foreignStreamby its async iterator and quietly void A5's identity guarantees.globalThis[Symbol.for("deltic.runtimeCopies/1")]: registration at embedder-module evaluation; the census rides every cross-copy error, and the unbranded-throw trap names the multi-copy hypothesis (the Module identity across multiple runtime copies: symbol brands, app-owned import maps, loud dual-copy detection #83 signature — typically a pre-A8 copy's WitError) instead of leaving the latent puzzle.The package
@deltic/protocol: brands, canonical error classes (moved fromembedder/errors.ts+cabi/trap.ts),suspending()(moved fromjspi/suspending.ts; the module-local-symbol convention is repealed), predicates, registry. Everything re-exported from@deltic/runtime/embedderunchanged — no consumer code change; consumer import maps gain exactly one entry (@deltic/protocol), same family as the existing@deltic/*pins.Pins
tools/release-bundle/dual_copy_test.ts— source tree + release bundle as two genuine copies (query-string cache-busting shares every submodule below the entry and would pass vacuously). Pins: shared census; foreignWitErrorhonored end-to-end through a fallible import; cross-copysuspendinghonor; foreignStreamrefused naming both URLs; hand-rolled brand accepted; unbranded throw carries the census hint.runtime/tests/embedder/cross_copy_test.ts— the foreign paths via hand-rolled brands, no bundle build (runs intest-runtime).Layer 2 (docs half)
docs/consumers.md: the application owns the import map — sibling packages carry no consumer-resolvable
@deltic/*pins; invariant "exactly one deltic source in the resolved graph". Sibling-side moves are their PRs.Drive-bys
smoke-tlsrecipe was broken at HEAD (fab5c2e addedPOLYMORPH_ROOT/WOSH_ROOTenv reads to smoke-c0'scommon.ts; the recipe had no--allow-env) — fixed with provenance comment.tools/smoke-c0/deno.json(deliberately non-workspace) maps the new bare specifier; ports lockfiles record the new workspace link.Gates
Full
just gatespass: protocol 22/0 · runtime 392/0/3 · wasi-shims 52/0/1 · ct-runner 17/0/1 · bundle 2/0 (incl. the dual-copy pin) · conformance 69/0 · sched-seeds ×2 · examples · translate · ports · webrtc 10/0 · sm-pinned OK (jsc-pinned skipped, arm64) · websocket-conformance 55/55 · smoke-tls PASS · smoke-c0 legs 1–4 PASS · browsers chromium+firefox OK.Closes #83 layers 1+3; layer 2 sibling follow-ups tracked there.