Refusing Join
A join that cannot be made is refused, and the refusal names the field.
- Standing
- WITNESSED CHECKABLE RUNNABLE EXECUTED STAGED PUBLISHED REPRODUCED
- Last checked
- 2026-09-11 15:25:45 UTC
- Source
AmpersandBoxDesign/box-and-box/test/compose-laws.mjs@e41e5fab63c1· bytes06726c3fb022f31e…- Limit
- 101 compose laws over this algebra's own bricks, 2000 trials each, on the bytes staged on this site. It establishes that THIS composition refuses an undeclared contract. It does not establish that refusal is the right policy for any other join, and it says nothing about joins outside box-and-box.
- Next rung
external— external — an implementation not written in this tree passing the same law suite. Five ports exist (npm run parity) and all five are ours, so none of them is this rung. It cannot be reached by editing this file: the build derives the rung from receipts, and outside reproduction needs a receipt kind that does not exist yet.
Why this page says WITNESSED — the derivation, not the word
- ✓ a witness is named
- ✓ its evidence kind is one the ledger already uses (constructive_witness)
- ✓ the witness path resolves in this tree
- ✓ its rung is in_tree or above (live_deployed)
- ✓ a run is recorded for these exact bytes
- ✓ no claim is cited that could be REFUTED
- ✓ a counterexample is shipped (required once WITNESSED)
- ✓ it has run from the deployed site — 101 laws · 101 passing · 0 failing · 3 declared-open · 7584.4 ms
WITNESSED requires every line above to hold. The label is computed from them by build.mjs and cannot be typed into the registry — the build refuses a record that carries it.
Intent
Two assemblies built in ignorance of each other are brought to a join. Either the composite satisfies every compose law the algebra states, or the join returns the annihilator and says which contract field made it impossible. Nothing is coerced, defaulted or repaired on the way through. A refusal is a first-class outcome of composition, not an error path around it.
Technical register
For bricks a, b with declared contracts, composePipe(a, b) is either an admitted composite or 0̲ (ZERO, annihilated). An UNDECLARED contract field is not a wildcard: undeclared |> narrow ⇒ 0̲ (CD1). An explicit ANY is directional: a narrow producer composes into an ANY consumer, an ANY producer does not compose into a narrow consumer (CD2). & does not launder undeclaredness into a declared interface (CD3). The identities none() and idBrick() DECLARE '*' so the identity laws survive without the fix degenerating into refuse-everything (CD2/CD4). 101 enforced compose laws, 2000 trials each, totals derived by the suite.
Problem
Systems built from independently produced parts need a place where parts meet. The conventional join coerces: an absent interface defaults to the most permissive one, a mismatched type is widened, a missing field is repaired. On 2026-08-22 exactly that bug was found in this algebra — Brick() defaulted an absent contract field to '*', so an assembly that declared no interface at all received the most permissive interface in the algebra, and every hand-off passed a check that had nothing to check. Five falsifiers were written against the unfixed code and all five failed.
Solution
Represent contracts as terms with an explicit UNDECLARED kind distinct from ANY. Normalize before comparing (norm). Make composition total: it always returns a brick, and the annihilator ZERO is a brick with annihilated === true. Preserve the identities by having none() and idBrick() declare '*'. State the narrowed domain as its own law (CD4b) rather than leaving it implicit. Derive every printed total from the suite; hand-typed totals drifted three times before this rule.
Real-world analogy
Customs at a border between two factories. A crate with a declared manifest that matches the receiving line goes through. A crate with no manifest is not waved through as 'could be anything' — it is turned back with a slip that says which field was missing. The slip is the product of that transaction, and it is worth more than a crate that was let through on a guess.
Structure — on the surface
An illustration on a compute surface: loci above, carriers below. Press Play or Step; the takeaways collect as you go. Nothing here is evidence — the witness section is.
The chapter in WRL — and the chain so far
Chapter 6 of 32 — the fragment _patterns/wrl/chain/refusing-join.wrl, sealed alone by wrl.js
; REFUSING JOIN — a Door admits exactly one signal wire. rj_r is the entry AND Part II's router: in the chain
; it is fed by the machine router and feeds the composition chapters. The refused variant is beside this.
[relay:rj_r]{sig_in, sig_out}
[door:rj_join]{sig_in}
[rj_r] --sig--> [rj_join]Its test bench _patterns/wrl/chain/refusing-join.bench.wrl — drives the entry for this chapter's own film; never part of the chain
; TEST BENCH — drives this chapter's entry alone; the chain replaces it with a wire from an earlier chapter
[pulser:rj_bench](every 1){sig_out}
[rj_bench] --sig--> [rj_r]module + bench seal to → sem-92c126f2445a3b619ea35c97c1c0c9ba7d3c45c771601609621d8ede1701628b
Reduced by the native reducer (ic32); the reference reducer reproduces every epoch's film hash (0.003s).
The Film, epoch by epoch (4)
epoch 1 · sha256:71b00cb6f1cebd74326cf53e5eb584e5e27d73fd6a1934e9bee34b657653a7b9
FILM v0.7 t=1 pulser:rj_bench:mode=periodic,p=1,phase=0,armed=0,done=0,nf=1 door:rj_join:open=0,next_open=0 relay:rj_r:cur_out=0,next_out=1 wire:w__rj_bench__rj_r:cur=1,nxt=1 wire:w__rj_r__rj_join:cur=0,nxt=0 admit:policy=admit_candidate_min_firstreceipt_v1,fact_capacity_fault=0,receipt_capacity_fault=0,capacity_fault=0
epoch 2 · sha256:751cc574dd5742331ac6658ef059ca8a5d60d8842a445c3dc208c81916acb9f9
FILM v0.7 t=2 pulser:rj_bench:mode=periodic,p=1,phase=0,armed=0,done=0,nf=1 door:rj_join:open=0,next_open=0 relay:rj_r:cur_out=1,next_out=1 wire:w__rj_bench__rj_r:cur=1,nxt=1 wire:w__rj_r__rj_join:cur=0,nxt=1 admit:policy=admit_candidate_min_firstreceipt_v1,fact_capacity_fault=0,receipt_capacity_fault=0,capacity_fault=0
epoch 3 · sha256:b35d2c541c6bd6ecec54adaf99e24fe230f3b4d7b3f6fc20f83a5b49e0f13971
FILM v0.7 t=3 pulser:rj_bench:mode=periodic,p=1,phase=0,armed=0,done=0,nf=1 door:rj_join:open=0,next_open=1 relay:rj_r:cur_out=1,next_out=1 wire:w__rj_bench__rj_r:cur=1,nxt=1 wire:w__rj_r__rj_join:cur=1,nxt=1 admit:policy=admit_candidate_min_firstreceipt_v1,fact_capacity_fault=0,receipt_capacity_fault=0,capacity_fault=0
epoch 4 · sha256:9d13c257b80ddd011a5f09260448a3ee84ac603258cc3f8c67a18f9345064a92
FILM v0.7 t=4 pulser:rj_bench:mode=periodic,p=1,phase=0,armed=0,done=0,nf=1 door:rj_join:open=1,next_open=1 relay:rj_r:cur_out=1,next_out=1 wire:w__rj_bench__rj_r:cur=1,nxt=1 wire:w__rj_r__rj_join:cur=1,nxt=1 admit:policy=admit_candidate_min_firstreceipt_v1,fact_capacity_fault=0,receipt_capacity_fault=0,capacity_fault=0
A refused world beside it _patterns/wrl/refusing-join.refused.wrl
profile forge.world.core.v1
; two producers into one Door: the join refuses, and names the port
[pulser:a](every 1){sig_out}
[pulser:b](every 2){sig_out}
[door:join]{sig_in}
[a] --sig--> [join]
[b] --sig--> [join]✗ WRL_CONTROLLER_CONFLICT — join has 2 signal-wire inputs (a node admits one)
Composes with the 5 chapters before it
The chain through this chapter — every earlier fragment, this one, and the links — seals to sem-be30c93f61ed401c0084cc9613dc5d98598664a4c1845eee89f16bdea05e95f5: 17 objects, 16 edges (was 15 / 14; every earlier object and edge is still present — checked, or the build refuses).
Links only the chain carries
[lc_machine] --sig--> [rj_r]
How to read this board
Five kinds of object, two kinds of wire, and one band per Part of the book. Signal flows left to right: it starts at a clock, travels through relays, and ends at a door — or turns a spinner, which drives an orb. Nothing below is the book's own vocabulary; each line is quoted from where the definition lives.
| shape | is | and so |
|---|---|---|
| a clock; the only source of signal | Every signal on the board starts at one of these. Nothing else can make one. | |
| a pass-through, so signal can travel | One arrives, any number leave — a relay that fans out is the board's router. | |
| a sink; signal arrives and stops | It latches what reached it and passes nothing on. A door is where a path ends. | |
| rotation: takes signal, drives a pose | The only object on the board that holds a value a claim can rewrite — and only if its config says configurable. | |
| the thing that gets moved | It is driven, never driving: an orb is what you watch to see whether anything happened. | |
| not a WRL role — the book's own drawing of the receipts in the epoch's Film | It counts what the run admitted, and turns red on a Rejected outcome. | |
| SignalWire | signal: a sig_out to a sig_in | Legal from a Pulser or Relay, into a Relay, Door or Spinner. This is how the board moves. |
| SocketControl | control: a socket to a pose | Legal only from a Spinner into an Orb. At most one may land on any input port — fan-in is a typed refusal. |
Hover any object for what it is, which chapter put it there, and every field of its line in that epoch's Film — split into what it is doing now and how it was built. Click to pin the readout, then click a wired name to follow the signal. The field definitions come from TRVM/forge/film.py (the emitter), TRVM/forge/forge_state.py (nf, derived from the decoded counter and never from t), TRVM/forge/lower_e2a.py (the commit/react law), TRVM/FORGE_SEMANTIC_IR_v1_MEASURE.md §1.3; the shapes from WRL/learn.html and WRL/docs/spec/README.md. The build refuses if a Film emits a field this key does not explain.
The board so far: one band per Part, signal flowing left to right; relays that fan out are routers, doors are switches, pulsers are clock domains. Hover an object — or click the board and walk it with the arrow keys — for its role, its Part and what it is wired to. This board is the chain’s sealed shape; no Film drives it, so it has no state to report, and the whole board in the conclusion is where every object’s state is read epoch by epoch. Wheel zooms · drag pans · double-click fits.
Syntax — quoted from the tree at build time
CD1 — the falsifier, verbatim from the suite this page runs AmpersandBoxDesign/box-and-box/test/compose-laws.mjs:292
['CD1', 'an UNDECLARED feeds_into is NOT a wildcard: undeclared |> narrow ⇒ 0̲', (n) => trial(n, () => {
const tag = 'T' + ((Math.random() * 4) | 0);
const a = declBrick({ pi: 'retrieve' }); // no contract at all
const b = declBrick({ pi: 'act', contract: { accepts_from: [tag], feeds_into: '*' } });
return isZero(composePipe(a, b)) ? true
: `undeclared feeds_into composed into accepts_from=[${tag}] (stored: ${JSON.stringify(a.contract.feeds_into)})`; })],How a refusal carries its reason — the annihilator is a brick AmpersandBoxDesign/box-and-box/compose.mjs:1464
const zeroBecause = (reason) => Object.freeze(Brick({
id: '0̲', annihilated: true, refusal: reason,
value: V({ sigma: ['annihilated'] }), cost: UNCERTIFIED_COST(),
q: { confidence: 0, cost: Infinity, latency: Infinity }, utility: 0
}));
Forces
Refusing is cheap to implement and expensive to live with: every refusal is work someone must redo, so the pressure is always toward 'just compose it'. The identity laws must still hold, or the fix degenerates into refuse-everything (CD2 and CD4a exist for that). MISSING ≠ UNIVERSAL has to have content in both directions. And a refusal must NAME what it refused, or the producer cannot repair its side — a silent 0̲ is only marginally better than a silent coercion.
Applicability
Use it wherever two things built separately must meet: assemblies at a factory join, capability bricks in a governance ladder, records from two verifiers, agents handing work to agents. Do not use it where the join is the identity operation on one thing — an identity must DECLARE '*', or the pattern degenerates into refuse-everything.
Transformations
- re-associating a chain of joins (|> is a phase-graded monoid)
- commuting operands of & (a commutative idempotent monoid)
- declaring a contract explicitly where one was absent — this can only turn a refusal into a composite, never the reverse
- defaulting an absent contract field to '*' or any other value
- widening a type to make a join succeed
- repairing evidence on the way through a join (VALUE laws: defaults complete absence, never repair)
- treating the annihilator as an exception or a crash
A refusing transformation is not one that is discouraged: it is one that, applied, makes the invariant above false. The word is the tree's, and it is the same word the join uses.
Consequences
Composition becomes something a receiver can check, not something it must trust. The cost is that an under-declared assembly is unusable until it declares, which is the point. Refusal ≠ failure: the three-valued outcome vocabulary (APPLIED · REFUSED · INDETERMINATE) in three-valued-outcome is this principle for interventions.
Failure mode it answers
Invisible State — Allowing state that affects future behaviour to remain outside the shared observable. Paid for at: TRVM/LAWS.md:80 (Law 6 witnesses: rotor, receipt, once-latch)
Witness
Source identity: AmpersandBoxDesign/box-and-box/test/compose-laws.mjs · shape suite · evidence kind constructive_witness · rung live_deployed (A live-run receipt: the staged bytes ran from https://opensentience.org/patterns/refusing-join and reported 101 laws · 101 passing · 0 failing · 3 declared-open, bound to staged stamp 06726c3fb022f31e. Recorded by _patterns/build/run-live.mjs; the rung is derived from that receipt by build.mjs (P26), not authored.)
Execution identity: 2026-09-11T15:25:45.724Z on PX13 · bytes 06726c3fb022f31e… · repo HEAD e41e5fab63c1
It has already run from this site. On 2026-09-12 the staged bytes at stamp 06726c3fb022f31e were executed by a browser at https://opensentience.org/patterns/refusing-join and reported 101 laws · 101 passing · 0 failing · 3 declared-open · 7584.4 ms. That recorded run — not a word in the registry — is what puts this witness at rung live_deployed; restage the file and the stamp moves, the receipt stops matching, and the rung falls back (P26).
Staged byte-identical at opensentience.org/witness/src/AmpersandBoxDesign/box-and-box/test/compose-laws.mjs (stamp 06726c3fb022f31e).
Counterexample
AmpersandBoxDesign/box-and-box/test/compose-laws.mjs — law CD1, marker undeclared |> narrow ⇒ 0̲
, expected REFUSED.
Resolved, not grepped. Law CD1 was looked up in the suite's own exported index — the one it prints from the arrays it executes — and is enforced, not one of the declared-open gaps. The marker above sits on that law's own declaration line, so it is that law's statement and not a string borrowed from another. Both are checked on every build (P27, P28).
v0.4 named the marker narrow-into-ANY-refused, which is CD2's FAILURE string (a refusal that must NOT happen). A marker proves the file mentions a string, not that the string is a refusal — P2 finding; the page runs CD1 live.
illustration — imports the same staged modules; the suite above is the evidence
What to take away
- from the animationA join is a check on declared contracts; a refusal is a returned value that names the field.
- from the syntax0̲ is a Brick with annihilated: true and a refusal string — composition is total, and the failure case is data.
- from the syntaxMISSING ≠ UNIVERSAL: an absent contract field normalizes to {kind:'undeclared'}, never to '*'. The identity elements declare '*' explicitly so the identity laws survive.
- from the literatureThe two laws a refusing join must keep — associativity and identity — are the monoid laws; the factory page notes the λA literature derived the same pair independently (paper not yet located: prior_art says so).
- from the witnessThis is not an argument: 101 laws × 2000 trials ran in your browser above, on the same bytes CI runs.
Prior art — and what is not claimed
| work | relation | what it shares | where it differs |
|---|---|---|---|
| Monoid laws (associativity, identity) | antecedent | composition with an identity and an annihilator is standard algebra | the algebra is standard; treating the annihilator as a reportable outcome with a named cause is the pattern |
| 'the 2026 λA literature, Theorem 5.7' | NOT SEARCHED | cited by FACTORY_REVIEW_BUNDLE.md:507 from page copy only | the paper has never been located; this citation establishes nothing and is recorded so that it cannot be quoted as if it did |
Novelty not claimed. Algebraic composition with a zero is textbook. The claim is narrower: that an UNDECLARED field is not a wildcard, and that a refusal must name the field — 101 enforced compose laws, not a new algebra.
Realizations in the tree
- FACTORY_REVIEW_BUNDLE.md:460
- FACTORY_REVIEW_BUNDLE.md:507
- amp-nav.js:297
Relations with other patterns
Capability-Bounded Composition WITNESSED · Refusal Gate STATED↩ · Three-Valued Outcome STATED
A ↩ marks a relation named on the other page. Relations are symmetric here and the reverse is derived, so neither side can go missing by being written once.