Reviewed 2026-08-20 (v0.6.0) against the roster in README and PLAN §5/§6, against D010's gap columns, and — since the plan review of 2026-08-19 — row by row against the code rather than against memory. v0.7.0 was cut 2026-08-26 and this document was not re-reviewed row by row for it, which is stated rather than left to be assumed: the rows below were last swept on 2026-08-25, records_keep_up.py reads them as current, and that gate checks only that they were opened, never that they are true. The transparency rows are the exception and are not here at all — their home is D029 §6.1, which the harness reads at run time. Re-reviewed 2026-08-25 against the tree after twenty-six batches
landed, which is the review that found five rows false: two class-B claims the
batches refuted, a served-count that had moved on the wire, a gap column
claiming work that had landed, and a test count four days stale. Where a figure
here was measured on an earlier tree and this pass did not re-run its
measurement, the figure carries the date it was taken rather than being
restated as today's. "✅" means
landed and exercised by the harness or tests named in a PHASE report;
"◐" means partially landed with the missing half stated; "❌" means no code.
Anything requiring a new dependency class needs a decision document first
(D001/D002 precedent) — those rows say so.
| Component | Status | What exists | What is missing |
|---|---|---|---|
orbweaver-cdr |
✅ | TextCodec and an owned slot (D009): a stream carries the transmission codeset for narrow text, None being the UTF-8 every stream carried before. Arc, not &dyn — this struct has no lifetime and giving it one would have changed all 145 construction sites and Cdr::put besides. Framing stays here: the codec supplies octets, and the length counting the NUL, the embedded-NUL refusal and the zero-length rule remain in one place. Full CDR, both endians, alignment origins |
—. Recovered 2026-09-06 from a merge-duplicated row (9d9e4c2 kept both sides' copies of this row and the giop one, and eleven days of records landed in the pair a renderer does not show as the table): The workspace's JSON reader and writer live here since 2026-08-26 (D026 §5 S1), moved down from orbweaver-dynamic — it has nothing to do with CDR and everything to do with height: the seed loader orbweaver-test/src/state.rs is compiled a second time inside fixtures by #[path], and while the parser sat above giop and registry no fixture in those crates could host it. orbweaver_dynamic::json is kept as a re-export because orbweaver-forge and orbweaver-console cannot name orbweaver-cdr without a new cargo tree edge |
orbweaver-giop |
✅ | A default: member's label is on the wire at the discriminator's width and ignored on read — whatever a third peer wrote there (2026-08-19; R18: 42 hand-built labels incl. colliding and invalid, six discriminator kinds, both orders, TypeCode and value level — 304 value round trips) — measured against omniORB 4.3.4 (writes an unused value, ignores it) and JacORB 3.9 (writes zeros, reads one octet that must be 0); ours are zeros, the value both accept in both byte orders. Before, a bare default wrote no label and no peer — nor our own decoder — could read the TypeCode, and nothing was red because every gate ran both ends through one encoder; nine peer captures recorded and retaken live. 1.1 wide text measured against JacORB 3.9, both directions and both byte orders (spikes/jacorb_giop11.sh, tests/wide_1_1_from_a_peer.rs): a 1.1 wstring carries no mark and follows the message's byte order — the mark we wrote was the peer's user's first character (D010 B5, 2026-08-19); and the 1.2 wchar that is itself a mark crosses both ways (spikes/wide_rust.sh arms A2/B2/D, tests/wide_1_2_from_a_peer.rs, 2026-08-19): ours marked 04 fe ff fe ff, JacORB's bare 02 fe ff read as the unit, JacORB's reader matrix (13 forms, both orders) recorded and re-driven live on every run; and the 1.1 wchar too, from our own live Rust server and client (spikes/jacorb_wchar11.sh, spikes/wide_rust.sh + spike-wide, spikes/wide.idl — the real server writes JacORB's recorded reply octets in both orders and our 1.1 request is octet-for-octet JacORB's; 1.0/1.1/1.2 self-consistency both orders): two octets in the message's order, no mark, U+FEFF as data — as always written and read here, now recorded with JacORB's whole request and reply and re-checked live against the recording on every run; Forward { Temporary, Permanent } + a defaulted Dispatch::redirect, Served::Forward carrying it, status 4 to a 1.2 peer and 3 to 1.0/1.1 (whose status enumeration has no 4), Connection::forwarded() telling a caller which it followed, and Pool::invoke_tracking / Reference::forwarded() telling a pool user the same (12 scripted cells both reply orders + our own Server, native); a permanent hop moves the object, not one handle (2026-08-20): Reference holds the address behind Arc<Guarded<Ior>> shared by every clone — a template cloned per call went from 3 requests at the address the object left to 1, both reply orders, and did not converge before (tests/forward_clone.rs); the temporary cache stays per handle by decision and is pinned as such; a shared-mode read per call, exclusive only on a permanent hop, never held across the wire (asked of the guarded tripwire). The pool follows the whole chain, not the hop that answered (adf0867, 2026-08-20): pool::Chain accumulates hops and Reference::note applies them per hop as Connection::follow does, so permanent → temporary re-points ior at the permanent hop, caches the temporary one relative to it, and restarts there rather than through it (3 shapes × both reply orders, tests/forward_chain.rs; the restart reuses the pooled connection, so it costs no dial); a caller's cap_version survives a forward and a restart — the version spoken is the lower of the cap and §9.4.1's profile ceiling, read off every request at both peers in both request orders (it was re-negotiated from the forwarded-to profile, so a caller capped to 1.1 spoke 1.2 at a 1.2 target — a wire-format change under a caller who cannot see the hop); a peer distinguishes the two by fallback-on-failure (spikes/perm_fallback.sh, tests/forward_fallback.rs, 2026-08-19): omniORB 4.3.4 restarts at the original after a temporary forward's target dies (§9.6 "shall") and stays on the dead address after a permanent one (§9.6 "may replace") — through which a server downgrading status 4 to 3 goes red; and since 3ab23d5 both our clients do the same and are asserted alongside — Connection keeps origin() and restarts there when a temporary forward's target fails with the request provably unsent (CloseConnection/write failure/poisoned at entry; never on unknown completion — one honest error, the next call restarts), a permanent forward replaces the origin; Reference caches a temporary forward (one round trip per call while it stands, was two), restarts the same way, re-points on permanent; ten cells green in perm_fallback.sh, both byte orders, tests/forward_restart.rs covers the unknown-completion, permanent and reuse arms; GIOP 1.0–1.2 both directions, codesets, fragmentation, locate, failover, SSLIOP (ssliop feature), concurrent connections and concurrent dispatch (stream E): SharedDispatch is &self/Sync with a per-servant sharing decision, the lock discipline enforced by guarded rather than documented, cap 64 with the refusal spoken as §9.4.7's CloseConnection, ServerStats counters, deadlock proven absent by test rather than by argument. Request multiplexing at 1.2 — refused below it, because a 1.1 Fragment carries no request id — measured against both peers, where omniORB answers out of order with its stock settings, so a client assuming reply order would be wrong against it. the char conversion list stays empty, and that is now measured rather than cautious (D009 §8 row 4, BLOCKED): eleven peer configurations probed, ten measured, and every one reaches UTF-8 — neither ORB has an option that names its conversion list, so no installed configuration can be the peer the row is conditioned on. Growing it was measured too, and it is not free: offered ISO-8859-1, omniORB keeps sending UTF-8 while JacORB configured native ISO-8859-1 moves down to it — café as 63 61 66 e9, and 함정 전투체계 as each character truncated to its low octet, raising nothing. §7.10.2.6 leaves that open and the two ORBs resolve it in opposite directions; an empty list is what keeps the ambiguity unreachable. Pooling keyed by endpoint + version + negotiated codeset, with a CloseConnection re-sent once and never twice. Union case labels are aligned and byte-order-normalised, which they were not: a label is the discriminator marshalled in its own type and was read with get_bytes and written with put_bytes, so it carried the byte order of whatever stream it came from and skipped alignment entirely. Our encode and decode agreed with each other, so 1200 tests were green while omniORB's long long discriminated union could not be decoded at all — reported as "string length must include the NUL", four fields past the fault — and its long one decoded and then missed every branch, blaming the caller's discriminator. Both captured from the live peer, pinned as bytes, and re-encoded back to the peer's bytes, which our own round trip could never have checked because it agreed with itself in any order. The ORB has an object (D019 steps 1–3, 2026-08-25, src/orb.rs + src/orb/config.rs): an initial-references table (RESERVED_OBJECT_IDS, the sixteen reserved ObjectIds of CORBA 3.4 §8.5.2 transcribed; resolve_initial_reference, list_initial_services, register_initial_reference §16.10.1), refusing by InvalidName and never a nil reference — which §8.5.2 forbids, so the None ObjectUrl::to_ior used to answer for corbaloc:rir: was the one answer the sub clause rules out, deferred with no reason anywhere; string_to_object/object_to_string (§8.2.2) as the single entry point that decides which of the three forms a caller is holding, where before the caller had to already know, a URL coming back with an empty type_id because §8.5.2 makes narrowing the application's job and an invented id is a claim the caller cannot check; and OrbConfig/Orb::with_config/from_orb_args (§8.5.1), which gives eight limits a home outside the process for the first time — -ORB<suffix> arguments removed from the caller's argv as §8.5.1 requires, an unrecognised one a refusal (BAD_PARAM) rather than a shrug, four standard arguments this ORB lacks refused by name with their sub clause, every setting an Option so no configuration changes nothing, a 0 refused for every cap and duration, and the table built to one side so one bad URL leaves no half-populated ORB. Measured end to end against omniNames with spike-rir: an operator's -ORBInitRef became a table entry, corbaloc:rir: resolved out of it with no address in the URL, and a foreign servant answered a call. spike-rir became a run_checks.sh group (spikes/run_checks.sh:2442, over a floor of 9 checks, its exit 3 made on this side because the spike has only 0 and 1). This sentence said "is not yet a run_checks.sh group" for some time after that stopped being true, in two rows of this file at once, and cited_and_run.py — the gate built for exactly this debt — was green throughout, because it reads spike headers and the IOU was here. And the ORB owns the transport (D019 step 4, 2026-08-26, approved one-way): Orb::server/Orb::pool are the only public ways to a listener and a pool — Server::bind, Pool::new, Pool::with_limits and Pool's derived Default are pub(crate), Poa::new likewise behind orbweaver_object::OrbPoa (create_poa/root_poa, an extension trait because orbweaver-object depends on this crate and not the reverse, so the ORB hands out a root POA rather than owning one). 13 hand-construction sites migrated — the twelve D019 §8 named plus orbweaver-test's call_bench — with 31 files touched in all. The point is not tidiness: step 3 gave the eight numbers a home, tested it thoroughly, and every call site of OrbConfig's eight getters was a unit test or a spike printing them, so -ORBmaxMessageSize 4096 parsed, validated and changed nothing on the wire while the suite stayed green — held was all any test asserted. Five of the eight now have a test asserting a difference a peer can see, each with its own control (tests/orb_config_reaches_the_wire.rs): max_message_size on both the serving and the pooled-client side, max_connections, max_fragments, message_timeout. Stubbing Server::apply_orb_config to a no-op takes four of them red; stubbing the pool's application takes exactly the client one red. Found on the way and fixed: Connection::move_to restored a hand-written field list after *self = next, and max_message_size/fragment_threshold were not on it, so a connection reverted to the compiled defaults across every LOCATION_FORWARD and §9.6 restart — both had public setters and nothing measured a limit after a forward. Not done, stated: pool::Limits' five numbers have no -ORB… key; max_forward_hops and follow_timeout are wired to Connection and Pool but have no behavioural test; stop_poll and fragment_threshold are wired and were not measured here. SSLIOP measured against an out-of-process peer (2026-08-25): spikes/ssliop_peer.py is stdlib ssl with every GIOP and IOR octet built by hand and no ORB imported, and ./spikes/ssliop.sh reports 21 of 21 cases, exit 0 — the advertisement read over both IOR and component byte orders independently (an encapsulation restarts alignment and carries its own order octet, so a little-endian component inside a big-endian IOR is a shape a deployment produces and our encoder never does), a real rustls-to-OpenSSL handshake carrying a GIOP call, and five refusals: an unsigned certificate refused and named, a plaintext peer at the advertised SSL port refused with the peer's own account as evidence that the client attempted TLS and did not downgrade, a dead advertised port not falling back to the cleartext listener beside it, no advertisement not a licence to dial cleartext, and an unreadable advertisement not an absent one. That run became possible only when the driver moved to src/bin/spike_ssliop.rs behind the ssliop feature; before that it was 15 UNMEASURED cases and exit 3, and ssliop.sh is not yet a run_checks.sh group either — the harness's ssliop gates check the D002 dependency promise and the in-process rustls tests, not this. The event channel serves all four supplier/consumer models and a server is no longer a channel — see the CosEvent row. The ORB can stop what it handed out (2026-08-26, D029 §5 O1; decision D034): Orb::shutdown / is_shutdown / wait_until_stopped(deadline), Server::stop_flag / stop_requested, ServerStats::serving, Pool::close / is_closed, Error::Stopped. Graceful at request granularity — the request inside the servant is answered in full, the pipelined one behind it is left unread, and the connection ends with CloseConnection (§9.4.10), which is what keeps §9.4.7's not processed true. Measured from a peer's own socket, 3 GIOP versions x 2 byte orders, values compared decoded (tests/orb_stops_what_it_handed_out.rs, 10 tests), with four negative controls each run red: in-flight work dropped (peer saw [Eof]), a request read after the flag (peer saw an extra Reply(2)), the ORB's flag not OR'd in (3 of 10 red), and a closed pool dialling again (2 of 10 red). The third control found a defect in the test rather than the product — a serve on the test's own thread hung instead of failing — so every serve there is now bounded. No behaviour change by default: an ORB nobody asks to stop never raises its half, orbweaver-object stayed at 124 tests and orbweaver-giop went 438 -> 448, the ten being exactly the new file |
#pragma prefix in the front end is done; a CloseConnection arriving between fragments now surfaces as Error::InterruptedMidReassembly — teardown rather than corruption, re-sendable for every caller on the connection except the one whose reply had already begun, since the peer had processed that one. this row said "unmeasured against a real peer: the shape needs a peer to shut down between two writes of one reply, and neither fixture exposes that window" and both halves of that were true while the conclusion was wrong (2026-08-25): the peer this needs is not an ORB but a socket, so it is now measured in 32 cases, both byte orders — sixteen in-process peers whose bytes are built from §9.4 and not from this crate's encoders, and sixteen from a separate process in another language, stdlib only and no ORB imported, which adds what an in-process peer cannot: the id the client says was cut checked against the id the peer says it cut, by two processes separately (tests/two_writes_of_one_reply.rs, spikes/half_reply.sh + half_reply_peer.py, spike-half-reply). The caller whose reply had begun hears InterruptedMidReassembly naming its own request id and is not told it may re-send; the other caller on the connection hears ConnectionClosed and is. No defect was found and nothing was fixed — the value of the batch is that mux.rs's "still not observed from a peer" is retired and the claim is held. Still unmeasured and stated as such: the same window against omniORB or JacORB themselves, which is a claim about their teardown and only they can make it; and half_reply.sh is not yet a run_checks.sh group — the runner exists and exits 3 for "nothing measured" as distinct from 1 for "refuted", but the harness does not call it. JacORB's 1.1 writer in a little-endian message is unmeasurable from here (it writes big-endian only) — for wchar as for wstring; JacORB 3.9's own 1.2 writer writes U+FEFF/U+FFFE bare and its own reader cannot read them back — the peer's, unreachable from here; a 1.2 wstring that is only U+FEFF is indistinguishable from an empty marked one from a bare writer (ours marks it, JacORB's form unmeasured); Pool/Reference have no cap_version at all — a per-caller cap would have to enter pool::Key or the pool could hand a capped caller a connection speaking above its cap (D012, PROPOSED); two Pool::reference calls for one IOR are still two references and do not learn from each other, at a measured cost of one forward per reference, once — three independently created references over seven calls cost 3 requests at the address the object left and 7 at the object, both reply orders, because a second reference re-points itself on its own first hop, and omniORB 4.3.4 charges the same 3 of 7 in the identical shape under both forward statuses with _is_equivalent answering true (2026-08-21, tests/forward_clone.rs); an identity map would need a lifetime rule and an Option<Ior> discipline that keeps one IOR's pool::Key codeset out of another reference's profile (D013, PROPOSED, recommends not building); GIOP 1.0/1.1 clients and JacORB not driven in the fallback shape; a native has no TypeCode to send and a ValueBase is a valuetype (2026-08-21): omniORB produces no tk_native by any of its four routes — its C++ back end exits 1 on the declaration, its Python back end ignores it and leaves a typeMapping entry that raises KeyError one import later, and the ORB has no create_native_tc — so TypeCode::Native carries no TCKind, encode refuses it by name and from_u32 still has no arm for 31, refusing a peer that sends one (tests/native_typecode_from_a_peer.rs, spikes/native_capture.py); 30/31/33 remain absent from the wire kinds, and 31 was asked for and refused rather than merely unseen; the ORB's stop is built and mostly unused: measured 2026-08-26, **17 of this workspace's 63 serve sites still pass ` |
orbweaver-idl |
✅ | A third front end, measured 2026-08-31: spikes/tao/setup.sh builds tao_idl 4.0.7 from ACE+TAO 8.0.7 source (Homebrew's ace formula ships no tao_idl), and the differential runs 99 files through omniidl, tao_idl and jacorb_idl with no unexplained divergence. The first run with it present reported 37; 29 of those were the harness rather than the tree — tao_idl_verdict returned TAO's own exit status (2 on a parse error) into a protocol compared against 0/1, and asked the oracle about IDL 3, TAO's default, against a corpus written to 4.2. The remaining 8 are real, each narrowed to a rule by probe and recorded in corpus/divergences.tsv: TAO alone rejects a folded constant whose operand overflows the target type, and rejects a user module CORBA that also names ::CORBA::TypeCode (declaring the module alone is accepted by all three — the narrowing is the finding); TAO and JacORB predeclare no CORBA::Principal; and TAO accepts a duplicate union case label, two default: arms, a 32-digit fixed literal, an over-bound const string<3>, and the case-insensitive operation-name clash. Where the fixture is absent the column is a counted SKIPPED, which is what it was until this date. const_type narrowed (2026-08-20): seven shapes agreed with omniidl in both directions — the bare fixed accepted, fixed<d,s>/any/sequence/void/Object/ValueBase refused — TypeSpec::Fixed carrying bounds: Option<…>; 33 golden, 19 negative, 78 files through the differential when this row was written (35 / 19 / 80 as of 2026-08-21) — counted from the tree 2026-08-25: 37 golden, 31 negative, and 94 files through the differential (differential.sh accepts corpus/golden 37 + corpus/requirements/generated 20 + spikes/*.idl 6 = 63 and rejects corpus/negative 31). The negative corpus grew by twelve in one batch and this row had 19 in it, which is the drift a file count makes silently; these three are file counts and are recomputable, unlike the pass figures elsewhere in this table. JacORB's refusal of the bare keyword is recorded in corpus/divergences.tsv. corpus/include/ has its first pair case (evo-*, 2026-08-20): a released contract and a proposal differing only in the name inside their #include, with the breaking change in a file neither root mentions — gated from orbweaver-forge, because cases.tsv has no baseline column and its own gate cannot call the comparison without a dependency cycle; signature types held to param_type_spec/op_type_spec rather than type_spec (2026-08-20): a bare fixed, an anonymous sequence and void are refused in attribute, parameter and return position, matching omniidl — ten divergences measured, eight closed by one function; corpus/negative/n13–n17; the §4.4 closure (deferred_wire_types, wire/deferred-type, 2026-08-19): every declaration that is or carries a valuetype, an abstract interface or a fixed — through members, typedefs, elements, signatures, raises and inheritance — with the reach as prose and a fix per family; 30 declarations over golden as of 2026-08-21 — four families, since a native joined the closure with its own sentence (it is not deferred; there is nothing to defer) — and a separate list rather than a diagnostic, so oracle agreement is untouched; 35 declarations and five families as of 2026-08-26, when ::CORBA::Principal joined it (PRINCIPAL_CONSTRUCT, family() == "withdrawn types"): the fifth is neither deferred nor never-marshallable — GIOP 1.0 carried one in every request header and CORBA 3.0 removed the type — and it was the one family with no Definition to become a finding, so S4 said nothing about corpus/golden/34's five declarations while both emitters skipped them, and deferred_wire_agreement was green because both of its sets were empty. A use is recorded as a direct cause by wire_type, told apart from a contract's own Principal by the scope the symbol was found in rather than by its spelling; its fix names where caller identity went (a CSIv2 IdentityToken), which is the only fix in this rule's set that can point at a replacement; a constant's value measured against its type (const-value-type, const-value-range, not-a-const-type, 2026-08-21): 67 constant shapes and 25 of their neighbours outside const position through omniidl -b dump, 26 divergences from three causes — the lexer chose a Rust type and lost what it could not hold (9.9d folded to a float, 18446744073709551615 refused, and the same line refused it in a case label), no code compared a value to its type at all, and no wide literal existed (L lexed as an identifier) — closed by Tok::Int(u64)/Tok::Fixed/Tok::WChar/Tok::WStr, ConstExpr::Int(i128) and exact decimal folding in the registry, the checks following typedefs; 26 → 2, both of them places we follow CORBA 3.4 over omniidl and recorded in corpus/divergences.tsv; idl-diff had been blind to every fixed constant — both sides folded to None, so a released rate could change and §5.3 printed "no change" — and long long union discriminators, refused by both emitters, were closed with it; corpus/golden/33-const-values.idl, corpus/negative/n19–n22, every one through both front ends at landing — JacORB 3.9 disagrees twice and neither disagreement changes what we do: it cannot lex a fixed literal whose written integer part begins with 0 (0.0d stops the parse, .5d and 1.0d do not, and const double B = 0.0; compiles), and it accepts const long double and writes it as a Java double, narrowing the constant silently; the AST keeps the default: position among a branch's labels (UnionCase::default_at, 2026-08-19); IDL 4.2 front end, SIDL comments, full oracle agreement, #include resolution: quoted against the including file's own directory, angled against -I, cwd never searched (a validator run from a build directory would otherwise resolve differently and the difference would surface as a repository id, not an error); once-only by canonical path so guards are not required — the estate measured that real IDL in the wild has none — with advice when an unguarded file repeats; cycles named, a missing file listing every path searched, diagnostics reported against the file the line was written in; every fixed-point refusal files under fixed-literal (2026-08-24): LexError::rule classified by a retyped prefix that one of its own three construction sites did not carry, so a malformed literal filed under parse and never received the fix hint orbweaver-forge keys to fixed-literal — the sites and the classifier now share FIXED_LITERAL_SUBJECT, three refusal shapes tested under one rule with a parse negative control. every rule id is a documented constant with one construction site (src/rules.rs, 2026-08-25): each names which single diagnosis it stands for, ALL publishes the set, every site in lex/parse/sema/include uses one, and a test scans this crate's own source and fails on a site that spells an id itself — ALL is what lets a consumer's hint table be checked against the rules that exist at all, the comparison nobody could make before. tests/negative_corpus_rules.rs holds the table: the rule every file in corpus/negative/ files under, every file rejected, the table and the directory holding the same files, and every rule in ALL either reaching a file or named with the reason it does not. Found by that sweep and fixed: SymbolKind::is_type counted an exception as a type, so struct S { E field; }; validated clean here while omniidl refuses it — found by asking which kinds a not-a-type file could be written from, because there was no such file; raises now asks only that the name resolve, and that nothing checks it resolves to an exception is written down rather than smuggled under a rule about types. Also measured there: a rule id names a class, and a second diagnosis joins it and inherits a hint written about the first — 5 instances, 2 already losing in the product, the span differing with the diagnosis so a hint written to quote one thing quotes another — and a hint keyed to a rule no corpus file produces has never been executed, 3 rules, the same gap the target-keywords file closed for escaping. orbweaver_idl::sema::PREDECLARED_CORBA publishes the predeclared table so one row cannot be repaired and its neighbour left, which is exactly how ::CORBA::Principal outlived the identical fix to ::CORBA::TypeCode. Prefix scope across a file boundary measured through omniidl -Wbinline, 13 roots and 49 ids agreeing |
conditional compilation is refused, not skipped (skipping #if compiles every arm at once); cross-front-end portability of the include semantics: JacORB now measured (corpus/include/inc-*, 32 ids, both oracles), TAO absent; #include inside a module whose file sets a prefix is exercised by no file we havefixed constant's value — the registry now holds it as an exact decimal, but Rust's std has no decimal type and decimal.Decimal would hold it exactly while importing it into every generated module is a decision about the emitted package rather than about the value, so both emitters quote the decimal they are skipping instead of claiming it could not be evaluated; 1.0d / 3.0d folds to None (there is no exact decimal quotient and IDL names no rounding rule to invent one); constants reach no console catalogue surface at all — it renders interfaces only |
orbweaver-registry |
✅ | idl-diff --approve writes an approval store (2026-08-19, approval.rs): rows bound to both units' SHA-256 fingerprints and a required --approver (or ORBWEAVER_APPROVER; absent → exit 2), read back by the gate ([approved by …] passes, an edited byte invalidates, a nameless row refuses the store), replay byte-identical apart from the timestamp (approval_replay.rs); the §5.3 differ compares union members by role (2026-08-19): labelled cases by label, the default by default_index — type then name — wherever it sits, the discriminator once; a folded pre-expansion TypeCode against the expanded one is no change, and a retyped default behind an inserted case is named (corpus/evolution/union-default/, union_default_pair.rs); verdicts reasoned from the generated stub — omniORB reads no member after an unlabelled discriminator and raises nothing, so default added → conditionally breaking, removed → BREAKING, renamed → compatible; a union's member list is the peer's (2026-08-19): a branch that is both labelled and default: gives one member per label plus a labelless default member where default: was written, default_index on it — == to omniidl's/JacORB's TypeCode over 4 unions × 2 stream orders (union_shape_from_a_peer); a bare default: keeps an empty label in memory by design — the wire form is the giop codec's; an inherited scope is searched before the enclosing one (CORBA 3.4 §7.19.2) — a raises declared in a base did not resolve from a derived interface, so the §5.3 gate exited 2 on a contract omniidl and JacORB both accept; and asking what Unresolved meant found it recorded bases and raises and not types, so struct S { Widget w; } diffed as no change, exit 0, with the member marshalling nothing where a peer expects a value. registry from IDL, ::CORBA::TypeCode resolves to a TypeCode — it loaded as void for months because the front end predeclares the name for checking and the registry resolves against the spec's own definitions, so an operation returning one generated -> () and marshalled nothing at a peer expecting a TypeCode; CLAUDE.md requires that spelling, which is what made the gap read as support. ::CORBA::Principal was the next row of the same table and answered TypeCode::Void until 2026-08-25, so a member the author had typed marshalled zero bytes and a peer writing a Principal there hands us octets every later field is mis-parsed from — and nothing was red: sidl-validate rejected 0, contract-check saw a void, both emitters produced the member, and the §5.3 differ told the only lie anyone would have read, "not declared in this unit; a missing #include", about a name its own front end declares. It is now TypeCode::Principal, refused by name at generation and in the dynamic path, and the member's type change diffs as BREAKING. Every marshalling layer had already refused Principal by name and none of those arms was reachable from a contract until the fix; the name is carried rather than refused because omniidl -b dump accepts the file, so refusing it would buy a divergence from a conformant compiler for nothing. All four predeclared CORBA names are now swept — two answered, two refused at parse, since object and valuebase are lexer keywords and ::CORBA::Object/::CORBA::ValueBase cannot be written at all. _get_def_kind told a conformant IFR client that three definitions the registry holds do not exist (fixed 2026-08-25): the catch-all _ => DefinitionKind::None answered dk_none for a valuetype and a native and dk_Interface for an abstract interface, under a doc comment asserting the opposite of the code that had been false for five days — it said the registry cannot tell them apart, which stopped being true when those became TypeCode::Value and TypeCode::Native, and nothing went red because the catch-all took both new variants the moment they existed, so the registry's new distinction never reached the wire. Measured against omniORB 4.3.4's own IR client over TCP (dk_none → dk_Value 20, dk_none → dk_Native 23, dk_Interface → dk_AbstractInterface 24, six controls unmoved); kind_of_type is exhaustive over all 33 variants with TypeCode::Recursive the only dk_none and the reason true of it alone. ifr::DefinitionKind names 0..24 and stops where the measurement stops — the peer's own enumeration carries 25 members and answering 26 to it raises MARSHAL in its own stub — while corpus/services/ir-subset.idl declares all 36 of §14.5.1 for the opposite reason: a decoder must accept what a conformant sender may write. 82 compared cases had been green over a shared blind spot, because the subject contract declared no constant, typedef, valuetype, native or abstract interface, so the byte comparison never reached the arm either classifier got wrong — two duplicates agreeing because they are the same wrong code; the matrix is now 131 cases × 2 byte orders and walks the registry's own ids rather than a list. TypeCodes vs two peers, §5.3 differ, the read-only IFR facade, remote IFR ingestion: JacORB 3.9's IR served us (their server, our client) — and shaped the design, since its base_interfaces are Java class names and its version field is ":1.0", so ingestion takes from a peer only what cannot be derived (the repository id is the sole accepted identity) and records disagreements as advisories. Provenance is marked and contagious upwards: touches_ingested answers for a local interface whose base came off the wire |
ingested entries carry no SIDL, so the guard's ai_effect/ai_authz gates have nothing to key on — a second, independent reason exposure stays off; and nothing binds an IR's description to the object that implements it, which has no protocol-level fix; the differ does not model member order / default_index position or the any dimension (TypeCode::equivalent compares union members in order, so an any carrying a reordered union may fail extraction on a peer), for any kind; the registry keeps no module annotations, so a registry-only consumer (an ingested contract, the console catalog) has no SIDL version — //@ sidl_version is read from the syntax tree; the approver is a typed name, not an identity, and the store binds to a file's bytes, not to a registry of record; ValueBase derives tk_value with VM_NONE (2026-08-21) — not VM_ABSTRACT — tk_null base, zero members, byte-for-byte omniORB's in both stream orders, and a native derives TypeCode::Native (tests/valuebase_shape_from_a_peer.rs) |
orbweaver-object (poa) |
✅ | references, identity, POA, LOCATION_FORWARD emit, The two MoE fixtures load their population instead of inventing it (D026 §5 S1, 2026-08-27): every tenant, region, capability, cost, adapter delta, policy domain, grant and declared node in spike_tenants and the reported node and capability vocabulary in spike_experts come from corpus/state/moe-estate.json, reached by #[path] because orbweaver-test sits above this crate in the dependency graph and Cargo has no bin-only dependency. Byte-identity measured before and after: spike_tenants identical, spike_experts one added line naming the reported node — added because a seeded value that reaches no output cannot be shown reaching anything, and its first negative control came back green without it. What each fixture still invents is named in its own module docs, because a seeded population must not become the only one. The MoE/registry key collision D028 §1 recorded is fixed: one MOE_BASE_KEY, one plane() building server and servant together, and a gate that reads the real pair rather than retyping either half of it. expert residency state machine + ExpertLoader (F3, 2026-08-14), moe::ExpertRegistry/ExpertLoader served on the wire (2026-08-15), the seven POA policies of CORBA 3.4 §15.3.8 written down and cited (D020 Stage A, 2026-08-25, src/policy.rs, Poa::policies()): this crate cited CORBA 3.4 zero times while being the half of CORBA a server author meets, and a POA has those seven policies whether or not anyone names them — so not naming them did not make the choices absent, it left seven facts with no home. No signature and no behaviour changed; the answer is computed from fields that already existed. Two corrections came out of writing it down: Servant Retention is RETAIN, not NON_RETAIN (the earlier reading came off the name ServantLocator, which is the specification's NON_RETAIN half, but the located id is inserted into active and survives the request, so the next request is served with no locator passed at all — RETAIN with a ServantActivator under a name borrowed from the other half), and USE_SERVANT_MANAGER with no manager answers OBJECT_NOT_EXIST where §15.3.8.6 says OBJ_ADAPTER minor 4, recorded and not fixed because Stage A changes no behaviour. IdAssignmentPolicy::Either is ours, not the specification's — §15.3.8.4 makes it a per-POA choice and one adapter here answers to both models — named in the type as the backward-compatible mode a new POA should not want. Policies::spec_violations() compiles the three constraints §15.3.8 states between policies; it reports and refuses nothing, and went red twice under the controls on combinations it was written for. Three references a manifest holds are named as relationships (D023 R1, 2026-08-25): base_model, experts and policy_domain each had an integrity rule the code enforced and no document stated, and CosRelationship is the standard's name for exactly this; the batch changed no behaviour and pinned each rule with a test that goes red if the rule were different. Dangling is impossible rather than detected — creation materialises, mutation requires existence, nothing destroys a target, so the graph only grows |
§15.3.8.1 Thread and §15.3.8.3 Object Id Uniqueness have no behavioural test and say so in their own documentation rather than being covered by a test that would pass whatever they said: the first is not observable from this crate (the concurrency is the server's), the second is not observable in principle — a policy about servants, in a map that holds none, so None is the claim and nothing can refute either value. Two findings measured and deliberately not fixed. Poa::object_key concatenates name, optional incarnation and id with nothing constraining the components, so under Lifespan::Persistent a POA named Root with id POA/x and one named Root/POA with id x mint the identical object key and each POA's parser accepts the other's — while tenant_service::is_key_safe enforces exactly that rule for the other key space in the same crate and neither names the other. No fix is behaviour-preserving: refusing / changes behaviour for a data-driven caller and the minting function has no failure channel, and escaping the separator changes every key already minted, persistent ones included; no caller here puts a / in either today. And create does not apply the two integrity rules bind_expert enforces — a manifest may name one capability twice, and may name one whose expert already exists over a different base, so a model can be composed from an adapter its own base does not match. Same rule, two paths, one enforcing it; pinned as measured and explicitly not endorsed, because making the paths agree turns that test red, which is the signal wanted. No inverse role and no navigation: no operation of the contract turns a capability id or a domain name into a reference, so the argument the two mutators demand can only be obtained out of band. The plane rule's census has one home and a test computes it (2026-08-26): plane::TENSOR_BEARING is five rows — operation, direction, status, reason — for every operation of corpus/golden/22 and 23 carrying a moe::Tensor across the wire, four served and one refused (Router::dispatch, NO_IMPLEMENT). tests/one_plane_rule_for_a_tensor.rs computes census membership by parsing both contracts with our own front end — a typedef/struct/sequence fixpoint, so a Tensor one struct deeper still joins — and checks the recorded status against what the servants answer over a socket, so an operation that starts carrying a Tensor joins on the next cargo test rather than on the next reading; three negative controls, each run, each red. This measures the census and settles nothing about the rule: PLAN-SERVICES §1 rule 2 and D006 option E point opposite ways at moe::Expert::process, which is served here and excluded there, and D006's status is the owner's. Recorded in the same pass and not changed in code — accepted as a named floor on 2026-08-31 (D037 option C): Router::select returns N IORs carrying host, port and object key, so it is a location-transparency leak with live consumers. What changed is that it is now a decision with a test under it rather than an observation: a_selection_hands_the_caller_every_candidate_address fails if the addresses stop being there, so the day somebody proxies them D029's Location row moves deliberately instead of quietly. The load-state half of that sentence was answered on 2026-08-26 and the answer was that select is a contract, not a leak (D029 §6.1's Activation row, which had named it, now says so): Constraints declares no residency member, the contract gives load state two homes where it is a value a caller asks for (ExpertLoader::status, Capability::state), and a filter could not have closed the leak in any case — select answers at T and the caller dials at T+ε. The leak was one layer down, in what the reference does across an eviction: residency::MissPolicy's two variants both answered Located::Unknown for an OFFLOADED expert and the POA turned that into OBJECT_NOT_EXIST, so the same reference invoked twice answered differently. MissPolicy::Activate closes it — demand-load inside locate, answer Located::Here — and being POA-level it holds for any target rather than for one contract. The refusal that had ruled demand loading out is kept verbatim and quoted, because every clause of it is about cost. tests/what_a_caller_can_tell_about_load.rs measures it with one live Connection across the eviction and carries its own control in the tree (the_refusing_miss_policies_are_the_leak requires the refusing variants to fail naming OBJECT_NOT_EXIST); four negative controls were each run red. It is also the first thing in this workspace that routes a wire request through Poa::dispatch_target — USE_SERVANT_MANAGER, the locator and the activation had been reached only by unit tests calling it directly. Still open: time (a demand-loaded call is slower and a caller with a clock can tell; in this repository a load is two map writes, so it is not measurable here). The mount landed later the same day (2026-08-26): expert_host::ExpertHost owns a Poa and the ExpertLoader for the ids that POA mints keys for, serves moe::Expert's three declared operations and defaults to MissPolicy::Activate — the ownership answer being whoever owns the expert's residency owns its server, which is why neither existing servant could be it (ExpertService owns the loader and stores registrants' Iors verbatim; TenantService serves experts and has no residency, and the tenantless shared base would then have no owner for its own). tests/a_mounted_expert_host_across_an_eviction.rs measures the row against it — 9 tests, 3 controls in the file, 4 more run red — and found two things the isolated fixture could not: a LocateRequest probe is a second surface for this property (so knows is residency-independent), and moe::Capability's fifth member is Residency, so the contract's own describe() reports load state and is unchanged across an eviction for an ordering reason rather than an independence one. Still open there: nothing constructs one, so the adoption is available and not taken; and a demand load that fails is OBJECT_NOT_EXIST — the leak reinstated — unreachable here, live in a deployment, and the same undecided question as MissPolicy's deliberately-absent deadline |
orbweaver-dynamic |
✅ | a §4.4 refusal names the rule and the type, in one sentence shared with the CDR path and the generated Python runtime (2026-08-21): deferred_wire_sentence applied in both AnyJSON directions — the layer a peer-fed document actually meets, which used to say tk_value cannot cross yet and name a fixed type <anonymous> — pinned by deferred_sentence_agreement and by python_target's equality comparison across crates; the description still crosses structurally, asserted beside the refusal so the two cannot drift into agreement (D008). value marshalling (a marshalling error names the argument and the path inside it — at key.tag[2]: string is bounded at 8 but 9 were given — on the marshaller's own Path, encode_named/decode_named, 2026-08-19; the dry run and the live call now write the same sentence), DII-shaped invoke, AnyJSON follows TypeCode::Recursive on the marshaller's own Path (bound 64, both directions, both byte orders, plain and inside an any — 2026-08-19; it resolved aliases only, and no test was red because every recursive witness was the empty list), AnyJSON v1.1 — _t is a structure for a type a name cannot hold, so an any carrying a struct comes back (it used to be written and then refused on read, which put the failure on the return leg) and a bound inside an any is no longer dropped by its own type name; Value::TypeCode makes tk_TypeCode a value the dynamic path carries, which is what §8's static=dynamic oracle needs to be applicable rather than merely passing (D008) —, first-party JSON, recursive types (markers resolved against the enclosing type the error path is already standing on; nesting bounded at 64 on both sides, because on decode the depth is the sender's choice) |
the CORBA DynAny interface over the wire is deliberately not built — it needs a POA-hosted object per component with a lifecycle, which is the reference-outliving-its-value hazard the local design removes, and nothing on the MCP path holds one; the CDR path has no fixed arm; void/null/Principal refuse as <anonymous>anyjson::type_name named fifteen primitives and asked everything else for a repository id, and seven variants carry none (sequence, array, any, typecode, void, null, Principal), so a peer-fed document naming a void was answered "<anonymous> cannot cross yet" and a value of the wrong shape for a sequence<long> was answered "is not a value of <anonymous>", under a doc comment claiming the decoder "says so rather than guessing". The class was diagnosed once already in this file and closed the wrong way — a fixed was repaired on 2026-08-21 by a guard above the mismatch arm rather than by the function, which took the one witness out of reach and left the defect live for seven other variants for four more days: a guard that stops one caller reaching a defect is not a fix for the defect. type_name is now exhaustive over TypeCode, asking the owning functions for each name, with the bound in the subject (string<5>, sequence<octet, 7>) because it is in the type; tc_to_json and describe are exhaustive too, and the controls are build controls — a 34th variant is error[E0004] at each site — because exhaustiveness has no assertion in it by construction. Measured with the repairs stashed: a construct nobody has met would have had its description cross the wire as the string "void". A third home for the same naming fact, in another crate, is reported and not fixed and has a live wrong answer — a native parameter described to an agent as <recursive>; a native TypeCode crosses AnyJSON structurally (2026-08-21) — it used to cross as the string "void" — while its value is refused by name, and a sequence whose element cannot be sampled is measured across both legs rather than skipped, taking the corpus from 5824 to 5952 of 5952 CDR round trips also crossed; the refusal sentences for all four unmarshallable families come from two functions (2026-08-21) — deferred_wire_* for §4.4's three, unmarshallable_wire_* for a native, whose wording differs on purpose because it is not deferred — read by five Rust layers and the generated Python runtime, held by equality across the crate boundary and asserted to make the distinction (no §4.4 deferral claim, no "yet"); ten sentences across thirteen call sites before, two of which were false to a reader; the heads are pub and read across the workspace (2026-08-24) — they were pub(crate), which is how twelve literals in two other crates came to write them again, one of them false to a contract-check reader for three days; all twelve now call the heads, and orbweaver-test's one_home_for_a_wire_refusal.rs computes the expected text by calling the same functions, so a kept literal fails at the next rewording rather than the next reading; the subject carries the repository id and its spelling has one home per language (2026-08-25): valuetype_subject/abstract_interface_subject/native_subject/fixed_subject here, _subject in the generated Python runtime, sixteen sites across three crates and two languages that were formatting their own subject now ask the owner — a simple name was ambiguous (two modules' Describable produced one string), and the reworded subject is the event one_home_for_a_wire_refusal.rs survived untouched, the first live proof of its class |
orbweaver-forge |
✅ | sidl-validate's three output forms agree on what a line number means (2026-08-20): --json and --repair-prompt map positions as the human printer does, a finding written outside the report's file names it in a per-finding file emitted only when it differs (single-file output byte-identical over five corpus pairs), a line-0 finding prints the file with no line, and a position prints once — --repair-prompt is read by a model, so a wrong line sent a repair to the wrong file. --against compares two resolved units (2026-08-20): it used to re-preprocess each side's splice, which read a spliced header's #ifndef as conditional compilation and refused every guarded multi-file contract without ever running the §5.3 diff — while still exiting 1, so nothing looked wrong; validate_unit_against{,_for} leave positions to the caller's unit as validate_unit_for does, so --against and the plain form place a header finding identically. WireGate: §4.4 is a warning by default and a refusal in the pipeline's S4 and under sidl-validate --wire v1 (2026-08-19); SIDL_VERSION, declared_sidl_version, s3/unknown-sidl-version; S4 gate, §5.1 orchestrator, S5 exposure-off registration (I2 ✅), S3i: annotations inferred for ingested contracts that never occupy a key a gate reads — inferred_effect/inferred_authz with evidence, approve() the only transition, read_only refusable but never proposable (a wrong destructive costs a click; a wrong read_only removes the gate). Measured 68% → 95% over 19 interfaces, unknown rate 63% reported rather than hidden. S1–S3 as distinct stages, each a producer plus the gate that judges it: S1 emits a first-party Brief (entities/shapes/constraints/open questions, plain words rather than IDL types — choosing types is S2's job) that a human can correct before any IDL exists, S3 annotates as its own pass with its own check. Any stage runs alone (--from s3), and resuming produces byte-identical artifacts to a full run. Measured with a real model over 20 requirements: S1 90%, S2 95%, S3 100%, S4 20/20, 2 rounds (docs/pipeline-runs/2026-08-13-split-pipeline.md). render_type no longer answers <unnamed type> for a type the wire marshals (2026-08-25): its catch-all sat under a comment claiming everything left "v1 does not marshal or does not name", and half of that was false and the false half reached a reader — the catch-all swallowed nine of the 33 TypeCode variants, two of them marshalled in both directions, one being the very type another fix had rescued from becoming a silent void. This renderer feeds the operation signature line in the S3i subject, the prompt a model and a human read, so long double price() arrived as <unnamed type> op(...). All 33 now carry a verdict — 26 unchanged, 2 repaired and marshalled, 4 repaired and refused by the wire and spelled by calling the owning crate's *_subject functions so the prompt shows the string the marshaller will refuse it by, and 3 keeping a placeholder with the reason true of it. Swept for the same shape, is_reference had the defect pointing the other way: _ => false meant a reference travelling inside a struct or union was not marked, so Object get_root() was a caution and struct Handle { Object it; } get_root() was not. ai_precond and ai_example reach a reader (D025 P1, 2026-08-25): both were in SIDL's known-key list, so writing one tripped no unknown key, with no consumer anywhere in crates/ and no user anywhere in corpus/ — a slot for a worked example and a slot for a precondition, the two things a prompt most needs and a type contract least carries, both empty and unread. Subject::to_prompt renders an authored precondition above the signature it constrains and an authored example below the one it instantiates, and where in the prompt is the whole decision: a precondition read after the signature is advice about a call the reader has already composed. Both are marked [authored], which is safe to trust only because D025 §7 forbids inferring into either slot; eight corpus operations gain one, hand-written |
the two new keys are a measured null result: the frozen S1–S3 benchmark cannot see them, and not because it was not run — Subject::to_prompt has one caller, the S3i path, and the frozen requirement set is driven through three other prompts entirely, so the stage is never even constructed on it. Checked rather than argued: a capture stub recorded the exact prompt and input handed each item over the whole frozen set on both arms, 92 pairs per arm, diff -r no differences, exit 0 — pass rates identical by construction, and no model was called to produce a number that could not have been attributed to anything. The instrument was controlled too, since a diff that finds nothing is the shape a broken diff also has. There is no frozen S3i benchmark at all, and the nearest thing is unannotated by design so its two arms would also be identical; a real measurement needs a frozen set of ingested interfaces where M of N operations carry authored keys from their real contracts, two runs with the rendering on and off, compared on the gate pass rate and Proposal::unknown_rate() — 2N model calls plus repair rounds, and indicative when it comes. Audited with them and a weaker claim than inert: ai_unit and ai_idempotent are read by checkers only — warned about when misapplied, and nothing converts, renders, validates or retries on either — so the live half of the eight-key vocabulary is four; rates are indicative — one model family generates and evaluates, one run per cell, so S3's 100%→95% between rounds is variance, not regression; contract-check --json (orbweaver-test) emits the same Report::to_json and maps no positions — correct only while it is pointed at self-contained files; Report::repair_prompt renders a line-0 finding as line 0, column 0evolution/*, registry, released-unreadable) named the wrong place in the one string an agent acts on; Finding::position() is now its single reader and a whole-file finding renders its source identifier instead. contract-check's output changed with it, since every finding that crate builds is whole-file by construction |
orbweaver-mcp |
✅ | orbweaver-mcp-server --dry-run=<id>.<operation> [--dry-run-args <json>] [--dry-run-handle <name>=<IOR|file>] asks about one operation with values and held references — the IOR is parsed and issued through the serving path's own issue_checked, never dialed (process test: a listener the test owns is never contacted, no host/port/key in any output) (surveys unchanged); Guarded::dry_run_with resolves declared handles against the session's table shared with the bridge, no dial (Detonator-held); tests/ifr_reaches_the_agent.rs witnesses non-empty sequences and asserts it — the third empty recursive witness 1b6b4c8 found, closed; Bridge::dry_run_with(id, op, args, approval), Would::ALL = 9 (marshal), map_arguments/parameters shared by the dynamic call, the guard's static view and the prediction; triad, stdio transport, capability handles, default-deny, ai_authz, promotion+I4, an unannotated operation is refused, not allowed — the gate asked annotations.get("ai_effect")?, so a misspelled effect required approval and a missing one did not, and a twelve-interface legacy estate answered allow=76, refuse=0 including SHUTDOWN and purge; closed means refused (Denied::EffectUnstated) rather than approval-required, because an approval is a human saying yes to a described call, with --assume-effect as the operator's single declaration of what an estate's silence means, carried into every allow that rests on it. describe_interface resolves inheritance — an agent was shown 11 operations and could invoke the 13 the guard judged, an Interface Repository is now readable through the bridge (D008): the five descriptions the IFR facade hands back cross and return byte-identically, asserted by repository id against the real contract rather than against a TypeCode written in the test, and the test also asserts a structural TypeCode is actually in the document so a contract that quietly lost its ::CORBA::TypeCode members could not pass by round-tripping nothing, vector search by external command (D003-A): spikes/embed.sh process boundary, orbweaver-vectors cache format, lexical∪vector union tagged via, no-index path byte-identical to before (golden-literal assertion). A deployment's numbers have a home that is not a source file (2026-08-25, src/deployment.rs, deployment.example.json): --config <policy.json>, named and never discovered, because a file this process found on its own could start applying to a deployment nobody changed — parsed with the workspace's own JSON, so no dependency. Scoped to a number or a policy only a deployment can know has one home rather than to the three items handed over, which changed the count in both directions: of twenty-one hard-coded values seven moved and fourteen stayed with the reason written where they live. Verified before building, and the brief was wrong three ways — the handle TTL was worse than stated (a consuming builder against a table the bridge shares with every guard it issues: unreachable by construction, not merely unwired), the quota half-stated (a flag had installed it since the ledger batch), the exposure one word narrower (already argv-driven, so a restart and never a rebuild). Three properties, each the reason for the next: absent is not zero (every setting an Option, no default restated — the new module references the constants that own them); default-deny cannot be widened by an absence; refused whole or applied whole, since a key no setting is named by stops the process naming the file, the key and what was expected — handles.ttl_second is a setting an operator believes is in force, and ignoring it is the harness's silent skip arriving through a config file. One sharp edge reported and deliberately not fixed: two --expose grants narrow order-dependently; it errs closed, predates the batch, and changing it would widen an existing deployment. Every guard-chain refusal carries what would make the call legitimate (2026-08-25, policy::remedy with REMEDY_ACTORS/REMEDY_FORBIDDEN, 14 tests): each of the twelve refusals an agent can receive names a next step — which id is not allowlisted, which scope the contract asked for, which annotation is missing, who may approve — and nothing in it is inferred, discovered or guessed, every clause built from a field the refusal already carried. It is a second sentence and not a field, which is a decision about reach: every reader takes a refusal as prose through one rendering, so a field would have taught exactly the readers somebody rewrote to ask for it and silently not the others. The rule the batch could not break is written at the site — a remedy names an act belonging to somebody who is not the caller, and never a route the agent can take by itself — and the exhaustive match with no _ arm is the codification, because a rule about diagnostics that lives only in a document is one the next variant's author will not read. The control that mattered most asserts the verdict and not the message: every row still refuses, since the worst outcome here is a remedy that turns a refusal into an allow. The agent gets four IDL tools (2026-08-26, D024 §5, src/contract.rs): validate_contract, diff_contract, describe_type, preview_generation — the tool list is 7, pinned by name and order, with register_contract named as one that must never appear. Every one returns Report::to_json plus repair_prompt rather than a verdict (D024 §3), and every one runs the same interceptor chain as invoke_operation over a real contract surface (IDL:orbweaver/ContractTools:1.0, four operations each annotated ai_effect: read_only), so no stage is special-cased and the tools are default-deny like everything else. Writing them required reversing orbweaver-forge -> orbweaver-mcp, which had put the boundary upstream of the pipeline it exposes; the entire coupling was exposable_interfaces, now Registry::exposable_interfaces. describe_type is gated twice — the chain decides the tool, type_is_reachable decides the argument — because a type is not in the allowlist and a tool allowed once would otherwise enumerate an unexposed estate's data model. Its answer and the IFR's Contained::describe are one answer for name/defined_in/version (ifr::contained_of, published for this) and proved equal end to end over real GIOP in both byte orders; they agreed on first measurement, which is honest rather than strong. The annotate-or-assume sentence has one home (orbweaver_forge::effect): six sites in four vocabularies before, one sentence with a per-audience offer now — three values for a contract's author, two poles for a refused caller, the flag and no values for an operator — and policy::is_harmless's two mirrors are now the same constant, so that half has nothing left to test |
the synonym class is UNMEASURED — no VOYAGE_API_KEY here, the harness SKIPs it, and the offline stand-in's 0/10 is a plumbing number a token-overlap embedder cannot beat by construction |
orbweaver-guard |
✅ | the content seat sees the static path too (D010 A3, 2026-08-19): Guarded reads the stub's own bytes back through the contract into the dynamic path's AnyJSON document — no stub, trait or emitted file changed, so stubs already compiled are covered; an unreadable payload is refused MARSHAL/BAD_OPERATION after the gate and before the wire; the leak test has a static arm, red-then-green in one commit. A dry run with values (Bridge::dry_run_with / Guarded::dry_run_with) predicts marshalling from the same TypeCodes, both byte orders, into a dropped buffer (Would::Marshal): string<8> given nine characters predicts marshal where it predicted allow, nothing dials. quota seat filled: budget scoped caller/interface/operation, window taken as a host-supplied label rather than from a clock (the crate has none, by D004's discipline), refusal typed TRANSIENT when it renews and NO_PERMISSION when it does not — "not right now" and "never" are different answers to a stuck agent. the content seat reads argument values — filled 2026-08-14, and this row said "measured empty" for four days after. What that batch did not check was its own second condition, that the audit must not thereby gain a way to log a value: it had. Denied::Intercepted's reason is free prose written by a deployment's stage, the content seat is the one stage that both holds payloads and refuses, and the ledger rendered it verbatim — measured, with a PIN in an argument reaching why=, in the one artifact this crate writes to disk and people grep. guard.rs's own claim that a line "can carry no credential material" was false and left standing. The ledger now takes the stage's name and drops its prose; the full sentence still reaches the caller, the dry-run report and every observer stage — readers who already hold the arguments authz scopes, destructive approval, audit emitted on BOTH paths (one formatter, string-equality pinned), F4 interceptor chain: audit / telemetry / authz.exposure / authz.scopes / safety.approval, short-circuit on first refusal and after unwound in reverse. Registration order is deliberately not acting order — observers register outermost so an audit stage still sees a refusal a gate ahead of it produced, dry-run: Chain::run and Chain::dry_run wrap one private walk so there is no second composition to drift, audited under its own DRYRUN- decision token, and verify_promotion refuses a hypothetical by name |
a stub's over-bound argument never reaches the seat or the ledger (the stub's probe refuses first; pinned by guarded_stub.rs); a --dry-run-handle's type_id is not checked against the parameter's interface — parity with the live path (from_json resolves by handle only), not a new gap; Bridge is !Send (Rc<RefCell<CapabilityTable>> — nothing threaded needs it yet); IF2 is one store: a connect_static guard records into the session's counters under the static column of CallStats (2026-08-19); the promotion policy reads the dynamic column, so a promoted path is still never re-recommended; absorb_static remains for a history kept elsewhere |
orbweaver-capability |
✅ | lives inside mcp (handles.rs) rather than its own crate — a location choice, not a gap |
expiry policy configuration surface |
orbweaver-identity |
◐ | per-peer capability record (PeerCapability, 2026-08-19): enforces_identity / transport_secured / enforcement point, read off the IOR's TAG_CSI_SEC_MECH_LIST and TAG_SSL_SEC_TRANS, one classification shared by Assertion::RecordedOnly, the audit line and the console page; measured on both fixtures = bridge only, cleartext; negative control a fabricated identity-asserting IOR, both byte orders; CSIv2 wire, delegation policy, hygiene, Caller seam, token → Caller exchange with the verifier as a trait this project deliberately does not implement (a verifier wrong in the accepting direction interoperates with every honest token and also accepts a forged one, and no oracle we own can tell), a credential store whose secrets cannot reach a log by construction, and a scope audit that reports a contract scope no issued token can satisfy before any call |
nothing has been through a real identity provider — hand-built claims only, the same shape as CSIv2 being a per-peer claim rather than a feature; from omniORB import sslTP still raises ImportError here, but SSLIOP is not a protocol an ORB implements on top of IIOP — the Security Service's chapter defines unmodified GIOP over a TLS connection plus one component saying where the TLS listener is, with no handshake, negotiation or framing of its own — so what the claim needs is a peer that speaks IIOP over TLS, not an ORB that does, and Python's ssl is in the standard library while the certificates have been in spikes/tls/ since 2026-08-13. ./spikes/ssliop.sh reports 21 of 21 cases, exit 0 against spikes/ssliop_peer.py, which imports no ORB and builds every GIOP and IOR octet by hand; details in the orbweaver-giop row. The residue is real, is not closed, and stays named: a TAG_SSL_SEC_TRANS component produced by omniORB's or JacORB's own encoder, with the association-option bits and port convention that implementation chose — a claim about their encoder that only they can make. A hand-built advertisement is not ours either, which is what makes the rest of it worth having. Not yet a run_checks.sh group, and spikes/tls/PEER-STATUS.md still states the pre-measurement position — it is outside this record's footprint and is named here rather than restated |
orbweaver-gen |
✅ | A servant can say permanent (D010 A1, 2026-08-19): every generated servant trait has a defaulted redirect -> Option<rt::Forward>; status 4 at 1.2 / 3 below, read raw off the wire from a generated skeleton, both byte orders; omniORB 4.3.4 follows it (requests at the old reference: 1, the same as under temporary — the status byte is the oracle, the count is not). Declared bounds are enforced by both paths and the oracle can see it: rt::Bounded<T, N> puts the bound in the type so it is checked once rather than per generated line, at the same refusal point the dynamic path uses, and the new refusal oracle compares verdicts and bytes for violating values — the reading of §8 that would have caught this class, where byte equality over valid values never could. A string bound stays encode-only on both paths, because orbweaver-dynamic's decoder does not check it and generated code refusing what the reference accepts would be the worse divergence. Rust client stubs, oracle static=dynamic in both directions (204 server-side reply comparisons against the dynamic encoder, three GIOP versions × two byte orders × two reply origins), I1/I4, server skeletons: servant trait + generated Dispatch + a user-exception enum per interface, _is_a from the resolved chain, MARSHAL/BAD_OPERATION answered rather than panicked. multi-object skeletons: knows() required with no default (a default of true is the single-object bug), identity as an explicit Target argument argued from measurement — none of the five hand-written servants keeps a value per object — and 77 pinned cases × 2 byte orders answer byte-identically to the hand-written IFR facade — including describe_interface, the operation the facade exists for — down to a minted reference's object key and port. Byte equality is not the whole check: omniidl accepts the contract, and the reply is decoded back by the oracle's own decoder and asserted field by field, because two servants can agree on the wrong bytes. omniORB's python client drives a generated skeleton and reads its system exceptions by class (harness), raised through a #[must_use] Raising that cannot become a SystemException without naming the completion status — the value that decides whether a caller may retry, and one a generator-picked default would get wrong silently. A second target: Python clients — the only thing that separates the IDL mapping from what was convenient in Rust, and it found the Rust emitter's keyword list missing Rust's own reserved words (yield emitted fn yield(), which does not compile; no emitter's escaping had ever been executed). Seam is a local process, not FFI: AnyJSON v1 to orbweaver-py-bridge, no new dependency, cargo tree unchanged (D007 approved: the seam is deliberate and the bridge is not a security boundary; PyO3 stays the return path, pure-Python CDR/GIOP stays refused). ir-subset went from 18 generated + 10 skipped to 28 + 0 once a TypeCode became a value (D008), and the ten it used to lose included InterfaceDef itself — the skip propagated up through every container until describe_interface, the operation the facade exists for, was ungeneratable. The cross-implementation round trip over corpus/services went 12 values/12 calls to 21/21, 0 divergences. 129 scope names and 100 operation names agree with omniidl -bpython, with no name on our side it does not also produce; 28 golden contracts and 12 services generate, import and execute, 0 divergences over 182 values and 139 calls (2026-08-24; floors of 170/137 in the harness) (golden — 31 files since 29-labelled-default.idl, whose label-and-default: branches the Python emitter, the Python runtime and the Rust emitter all lost: the Rust one emitted the variant twice and did not compile) and 70 / 46 (services), constructed anys and forward-declared references included, and an any whose type the package never declared is read and reproduced byte-for-byte; 12 real calls against the omniORB fixture. One function spells every identifier, and six ways a contract could collide with one are closed (2026-08-25): measured by 2793 probes — 147 identifiers × 19 positions, every emitted Rust crate compiled out of workspace and every emitted Python package imported — first pass Rust 92 failures (96.7%) and Python 39 (98.6%), after: 0 and 0, in two rounds. Scoped to the rule (every identifier the generator emits is spelled by one function, every site that looks one up calls the same function, and everything the generated code does not itself define is reached by a path no contract can bind) rather than to the five defects handed over, which is why four of the six causes are not in that list. Escaping moves the name where the offender is the contract's own and no qualification reaches it — r#Self is not a raw identifier and r#Ok resolves to the same Ok a pattern matches — so ident gains CANNOT_BE_RAW, CANNOT_BE_A_BINDING and PRIMITIVES; qualification moves the reference where the offender is the generator's, because a list of library paths inside ident would be a second home for a fact the emitter owns, so the site writes ::std::result::Result, the runtime imports as __rt/__Cdr, and every hand-written binding is __-prefixed — an IDL identifier cannot begin with an underscore, so __rt is unshadowable and a bare rt:: written tomorrow no longer compiles, which is the strongest form available. Primitives are escaped rather than qualified on a stated line: Rust's primitive set is closed so a list of them cannot fall behind the emitter, while library paths can. Found on the way and unrelated to any keyword: a declaration outside any module emitted a file with no runtime import at all — every corpus file opens a module, which is the whole reason nothing was red. Two of the earlier probes had been silently one, because macOS is case-insensitive and Self and self wrote the same file. A third target: Java clients, and the suite accepts it (2026-08-26): spikes/binding_suite.sh --language java reports cells run 3, skipped 3, red 0, and — unlike Python's client column — the client direction meets clause 2 and clause 6: client × little read from omniORB and client × big read from JacORB, each read off §15.4.1's flag byte of the reply the peer wrote, through jacorb_giop11_tap.py sitting in the path. The client × jacorb cell is the one spikes/bindings/python.manifest states as its own gap ('nothing drives generated Python at a JacORB server'), established here for Java and not for Python. 12 generated calls against each peer, no Rust stub in the path and no org.omg.CORBA anywhere: JDK 11 removed CORBA (JEP 320) so the only one on this machine is JacORB's jar, and generated Java speaks AnyJSON v1 to orbweaver-py-bridge instead — the seam D007 settled, unchanged and needing no second protocol. The oracle behind the client/self cell crosses 131 values and 167 calls, both byte orders, over 37 golden contracts (2026-08-26), comparing CDR bytes rather than documents; its first pass was 89 failures with four root causes, two in the emitter (a typedef's Java type was the alias holder class, in 20 contracts at once; ::CORBA::TypeCode reached a catch-all and was refused by Java alone, in 1 of 37 — descriptor is now exhaustive so the next such construct is a build error). Clause 5 for Java: 38 of 59 reserved words executed by 28-target-keywords.idl (2026-08-26), which grew Java's positions, its four contextual keywords in the type-name position where they are fatal, and a template-locals section — D030 §5 L2's third consequence, since JacORB 3.9's own template makes an IDL parameter named e fail to compile. This emitter prefixes every local it binds with _, which no IDL identifier can begin with, so that class is impossible rather than escaped. Found while measuring it: the coverage instrument was reading the verbatim runtime along with the generated files, so java_rt.java's _default and python_rt.py's _lambda read as covered by contracts that never name them; targets::without_runtime excludes it, and Python's verdict is unchanged at 28 of 37 |
the pool cannot dial in the non-native byte order (Reference::set_endian is advisory), so a real server is heard through it in one order only; spikes/bench/stub.rs is a checked-in generated stub outside tests/emitted, so it is re-blessed by hand rather than by the emitter — it has carried redirect since bb3f973 (2026-08-19), and the gap is the re-blessing discipline, not a missing hook; a oneway fault is still dropped (§9.4.1 leaves nowhere to put it) though now logged; Python is no longer clients only (2026-08-26): the second protocol direction exists — orbweaver-py-bridge --serve writes a call document and Python answers it, pyservant::PyServant is the Dispatch behind it, and python.rs emits a <Name>Servant base from client_operations, so a Python servant answers exactly the names a Python client of the same contract can send. What is left is narrower and is two things, not one: an object reference reaching a Python servant is a handle into the bridge's table that it cannot invoke (closed 2026-08-31, D029 §6.1.1 item 4 — see this row's seam paragraph), and a Python servant cannot mint a new reference, having no POA on its side — §4.5 emits no IOR, so the seam carries values and an object reference is the one value whose meaning is a capability rather than data (D029 §6.1.1). The second of those closed later the same day and had been understated (2026-08-26): it was not one thing but three — a foreign servant could not mint a reference, could not tell which object it was (two calls to two different object keys produced byte-identical call documents), and claimed every key in the process, so it was a singleton leaf rather than a leaf. pyservant.rs is now orbweaver_gen::seam with the language taken out of it — ForeignServant, Answerer, ObjectIdentity, SeamReferences — plus surface::callable_operations, because the seam had been resolving its contract through the Python emitter and nothing was red, the function being language-neutral all along. The value representation gained one shared addition rather than a per-language one: anyjson::References::resolve_as hands the decoder's declared repository id to the table, which from_json had always known and discarded, so a minted reference advertises the id the contract names and the far side, which spells only an oid, cannot mint the wrong type at all. Measured: 126 cells byte-identical (tests/a_reference_crosses_the_seam.rs over corpus/golden/26-object-identity.idl, the generated Rust skeleton beside the seam, 3 GIOP versions × 2 byte orders × 3 objects × 7 calls), the minted IOR also compared decoded, with four controls each run red (minting disabled → 18 of 126 and exactly the reference-returning cells; the oid removed → 7 of 10 tests; knows claiming everything → 1 of 10; the key infix disagreeing with the generated <I>Refs::KEY_INFIX → 6 of 10). Reachable from a real Python process, not only in-process: orbweaver-py-bridge --serve takes an ObjectHome off the bound server, and _rt.Servant.own_oid() / _rt.ObjectRef.own(oid) are the far side's half. The seam's protocol is now a value rather than three comments and a set of literals in two languages: seam::protocol() and _rt.seam_protocol() are each built from the constants their own side reads with, asserted equal by tests/the_seam_is_one_protocol.rs, printable by cargo run -p orbweaver-gen --bin seam-protocol, and a third language enrols by adding one function and one row. That inbound half CLOSED on 2026-08-31 (D038 option A, D029 §6.1.1 item 4): a reference arriving was a handle the far side could not invoke, because invoking it needs a call travelling the other way and the protocol had no message for one. It has one now — invoke, answered as answer, seam protocol version 2, published by seam::protocol() and by _rt.seam_protocol() and asserted equal — so the seam is re-entrant: the far side asks mid-answer, this side dials by handle, and the answer arrives before the reply to the call the servant is still inside. Measured by tests/a_call_travelling_the_other_way.rs — a Python servant handed a reference to a Rust target on a real socket invokes it, the target records exactly one call, and the servant refuses unless the value it read back is that target's; neither assertion is sufficient alone. Its control is the same servant with the nested call removed, and the target is never reached. What it does not establish, stated because a green run reads as more otherwise: it is not a side-by-side comparison of a Rust and a Python servant doing the same thing — what it establishes is that the Python one can — and the nested result crosses as AnyJSON rather than as a mapped value, because that side knows only the repository id the reference advertises. The peer half is measured in both byte orders (2026-08-26): omniORB's client little-endian and JacORB's big-endian, the order read out of §15.4.1's flag byte on each request rather than assumed from the peer's language — 12 requests, 11 replies, at IIOP 1.2 and 1.1 — and the Python servant's replies are byte-identical to a Rust servant's for the same driver run, 11/11 at each version (spikes/jacorb_python_servant.sh, jacorb_calls_a_python_servant). So D030 §3's both-orders clause is met for the servant direction and is not established for the client direction, whose both-order test is a loopback with no peer in it and whose live peer writes its native order (D030 §3.1). Not measured against JacORB: GIOP 1.0, and one peer per order rather than two. That table is no longer prose (2026-08-26): spikes/binding_suite.sh is one acceptance suite parameterised by language (D032 §5 B3) with no language name in the driver — a language is a manifest plus what it names, and the axes live once in spikes/bindings/AXES. D032 §4's six clauses turned out not to be six checks: 3/4/5 are language-scoped, 1 is one measurement over a (direction × peer) grid, and 2 and 6 are coverage requirements over that grid, which is why a "both byte orders" line would have printed ok off a loopback with no peer and no socket in it. An order read off §15.4.1's flag byte is observed; one inferred from the peer's host is claimed; only observed counts. Python, both fixtures present (2026-08-26, superseded 2026-09-01 and kept as the dated reading it was): 5 of 6 cells supplied, 0 red — servant × big read from JacORB at 1.2 and 1.1, servant × little and client × little claimed but never read, client × big reported by nothing, no foreign-peer reading in the client direction at all, GIOP 1.0 reached by no cell either way, and client/jacorb — a JacORB server that generated Python dials — printed as a counted SKIPPED every run. Measured 2026-09-01: 6 of 6 supplied, 0 red, and the client direction has its reading. spikes/bindings/python/client-jacorb.sh drives generated Python at spikes/jacorb/Server.java through the recording tap, so client × big is now observed giop=1.2 order=big — read off §15.4.1's flag byte of what JacORB wrote — and clause 6 is met in the client direction as well as the servant one. Twelve generated calls, no Rust stub in the path. The manifest had named this cell as "precisely the cell that would give the client direction a big-endian reading off a foreign peer's flag byte" since the suite was written, which is what a waits row is for. What it did not move, on that date: client × little and servant × little were still claimed and never read, and GIOP 1.0 was reached by no cell in either direction — the suite prints both, and the reading is observed only where a peer wrote it. Both halves of that stopped being true when the Java cells landed, and re-measured 2026-09-02 the grids read: python and java each 8 cells run, 0 skipped, 0 red, with all four direction × order cells ok — read off the wire from a foreign peer in both languages and clause 6 met in both directions. What is left is versions, not orders: java reads client 1.1 1.2 / servant 1.0 1.1 1.2, python reads client 1.2 / servant 1.1 1.2, claimed-only[] empty in all four — so client GIOP 1.0 is read by no cell in either language, python's client has never read 1.1, and python's servant has never read 1.0. Those three are what the suite names as UNMEASURED, and this row does not restate them as anything else. Clause 5 also gained its first instrument: of Python's 37 reserved words the contract set executes 28 and of Rust's 66 it executes 42, with the residue in spikes/bindings/keywords-not-executed.tsv in three computed classes; the one real gap is that eleven Rust primitive names are exercised by nothing (one_spelling_for_an_identifier.rs covers only i32 of the twelve). Re-measured while wiring that suite, because the sweep's own figures in this row had drifted the way a floor lets a figure drift: 2026-08-26 the sweep crosses 242 values / 162 calls over 37 golden files and 124 / 87 over 3 service files, 0 divergences — the harness floors are still 170/137 and 70/46, and the 70 / 46 this row quoted for services was undated and is now a figure from an earlier corpus rather than a measurement of today. Found building that fixture: org.jacorb.idl.parser 3.9 emits Java that does not compile for an IDL parameter named e, its stub template putting catch (java.io.IOException e) in the same scope — not a keyword hazard, a template-local one. Also open: one servant per bridge process, where rt::Servants puts several behind one Rust server; a peer's §4.4 deferral is a readable description and an unmarshallable value, on both sides (2026-08-20): _rt.py reads AnyJSON v1.1's value/abstract_interface forms, synthesises _rt.ValueType (modifier, concrete base, per-member visibility, registered before its body so recursion resolves) and writes both back byte-identically, while an instance is refused in both directions by one format string identical to orbweaver_dynamic::decode's sentence — and since 2026-08-24 fixed, native and principal forms read the same way (every kind tc_to_json writes now reads back), _form_of returns the very document that arrived, and only the value legs refuse, in the Rust layers' sentence, the error's path naming _v and never _t; until then _desc_of refused the _t half itself — a peer told their description was not understood, the opposite of what the Rust end of the same bridge answers for the same document; the Python sweep crosses 182 values / 139 calls over golden and 70/46 over services, 0 divergences (measured 2026-08-24; the harness pins floors of 170/137 and 70/46, so the golden figure had grown past the number this row and the harness comment both quoted — 172/137 and 170/137 — with nothing red, because a floor proves no regression and says nothing about the count. Not re-run by the 2026-08-25 record pass: the corpus has grown since — golden went 33 → 37 files and one of them is excluded from generation by name — so 182 / 139 is what 2026-08-24 measured and is not a claim about today; the harness floors are what still hold), with tk_value (29) and tk_abstract_interface (32) TypeCodes in the witness any; python.rs registers no name for a skipped abstract interface — one guard away from the unnamed-TypeCode defect fixed for object references; both emitters' skip cascades ask the type mapper at every node (2026-08-25) rather than each keeping a hand-written list of the four unmarshallable families — rust_type refuses what it has no arm for while representable used to clear it, so anything in the gap between the two lists was skipped at its declaration and emitted at every container naming it: golden 34 emitted gp34::Envelope for a type its file never declares and did not compile, and python.rs's identical split was not red at all, writing ("ref", "IDL:gp34/Envelope:1.0") for a class its package never defines; the gap is now unrepresentable rather than detectable, and every corpus file's differential verdict is checked-in data (corpus/differential-results.tsv) compared by an oracle-free test inside cargo test --workspace, after eight files reached the harness having met neither front end; _DEFERRED/_UNMARSHALLABLE in the generated Python runtime are equal to the Rust strings (2026-08-21), the runtime having written its own fourth wording for fixed — measured by nothing until it was broken on purpose — and gen having kept the same sentence as three identical literals; one list per target of what has no static mapping (2026-08-25): the type mapper and the cascade that propagates a skip to everything naming the skipped type each held their own match over the four families the wire cannot carry, and the mapper's catch-all refused while the cascade's cleared — TypeCode::Principal fell in the gap, so corpus/golden/34's Manifest was emitted naming a skipped Envelope and the out-of-workspace build failed (cannot find type Envelope), while the Python emitter wrote ("ref", "IDL:gp34/Envelope:1.0") for a class its package never declares and nothing was red at all, Python having no compile step. representable and crossable now ask rust_type/descriptor at every node, so the gap is unrepresentable rather than detectable; pinned by no_emitted_item_names_an_item_that_was_skipped over golden and services, both emitters, each half computing the expected spelling from the emitter's own namer. The pin that existed exempted a file allowed to skip from having its skip set checked at all, which is why three days passed; Java was clients only (2026-08-26, by scope rather than by discovery — D030 §5 L2, and no longer true as of 2026-09-01: Java is 8 of 8 cells, 0 red): all three servant cells were counted SKIPPED then, because a Java servant needs the bridge's serving direction to carry a dispatch into a Java process and pyservant.rs was that seam for exactly one language. The second half of that sentence stopped being true later the same day: the seam became language-neutral (orbweaver_gen::seam, its protocol a published value), so what a Java servant owed was an Answerer over the bridge's pipes and a _Rt.Host/dispatchCall in java_rt.java — the two things python_rt.py had and java_rt.java did not — and not anything in the seam's definition. That prediction held exactly: both landed on 2026-09-01 and seam::protocol() never changed. Both of those landed on 2026-09-01 — this sentence said "the second of those landed and the first did not" for the few hours between them, and the paragraph below already recorded the first arriving, so the cell contradicted itself until 2026-09-02. Appending a third correction would have made it worse; the two are stated once, here: _Rt.dispatchCall, _Rt.Servant, _Rt.Op, _Rt.Raise, _Rt.serveOnPipes, and a generated <Name>Servant base beside every stub, whose methods refuse with NO_IMPLEMENT until overridden and whose dispatch is a generated switch rather than reflection. Nothing in the seam's definition changed, exactly as that sentence predicted — seam::protocol() is unchanged and tests/the_seam_is_one_protocol.rs still passes. Measured by spikes/java_servant_half.sh over spikes/echo.idl: four call documents in, four reply documents compared whole — a converted result, a string result, NO_IMPLEMENT for an operation in the contract that this servant has not written, and BAD_OPERATION for one that is not in the contract at all. Two controls, each restored: the servant's result dropped instead of converted (two cases answer returns: null), and NO_IMPLEMENT downgraded to BAD_OPERATION (the refusal case answers the wrong id). That spawner landed the same day: pychild::PythonChild became SeamChild with python and java constructors, because only the command was ever language-specific — the document framing, D038's re-entrancy loop, the process group and the Drop that reaps a tree are the seam's, and a JavaChild beside a PythonChild would have been a second copy of the loop that answers a nested request. The rename touched the constructor and nothing else, which is the evidence the split was in the right place. tests/a_java_servant_this_process_owns.rs mounts java as a child of the test's own process behind a ForeignServant, and a request built the way a peer builds one — encoded, framed, decoded — comes back carrying the Java object's answer. No listener and no address, so a language swap stays a language swap rather than becoming a move. Two controls: the servant answering a default (left: 0, right: 42) and the child never calling serveOnPipes (a seam failure rather than a hang). Two of the three CELLS landed the same day and the third is named. servant × omniorb first, because java.manifest had refused the cheap one in that order and said why — "a self cell that existed while the foreign ones did not would report a seam we had never run against anybody else" — and honouring an ordering a manifest chose is cheaper than discovering why it chose it. omniorb_calls_a_java_servant binds a server this test owns, mounts the Java servant as its Dispatch, and lets omniORB's own Python client import the contract through omniidl, narrow to spike::Echo and call: add -> 42, echo_string -> java:hello, is_a Echo -> True. The servant arrives as a servant and not as a second endpoint, which is what keeps this the Language row rather than the Location one. servant × self follows it. Java goes 3 cells run to 5, 0 red, and the harness's counted skips 13 to 11. servant × jacorb landed the same day and closed that: JacORB's own client drives the Java servant through a recording tap, and the order is read off §15.4.1's flag byte of the peer's requests — because in the servant direction the peer is the caller, so its writing is in the requests, and reading the replies there would be reporting our own order as a foreign peer's. spikes/lib/tap_orders.sh keeps the two readings in two functions rather than one with a flag, because the mistake being prevented is picking the wrong one. Java is 6 of 8 cells, 0 red, and D032 §4's clause 6 is met in both directions; the harness's counted skips went 13 to 10 across the three. Eight requests read at GIOP 1.2, all big-endian. Two things that only a foreign peer found: the generated servant base never called _Types._ensure(), so a Ref descriptor could not resolve and echo_ragged failed with no type is registered under IDL:spike/Ragged:1.0 — the in-process probe drove add and echo_string, both primitives, and a primitive needs no lookup. That is the argument for a foreign peer, and it is why java.manifest refused to supply the cheap servant × self cell before a foreign one existed. Both of those closed the same day. The tap is peer-agnostic — its own header says the version and codeset choice come from the ORBs and the log is what they did — and it was already in front of JacORB one cell over, so there was no reason left for the omniORB cell to be a different kind of evidence: it reads too, and servant × little is observed giop=1.2 order=little rather than claimed. And the JacORB cell drives a second pass at IIOP 1.1 the way jacorb_giop11.sh does it — by republishing the profile, because a peer's outbound version follows the profile it dialled — so the servant direction reads [1.1 1.2]. The Java servant direction is now fully read: both byte orders and two versions, none of it claimed. Both binding grids are complete as of 2026-09-01 — Java 8 of 8, Python 8 of 8, 0 red — after the three C-peer cells landed together: a generated client calling c_peer --role server in each language, and c_peer --role client calling a Java and a Python servant. spikes/cpeer.idl declares the contract the peer answers, because its type id is compiled into the peer and a fixture taking its identity from a flag would make _is_a unfalsifiable. What those cells buy is bounded by spikes/bindings/AXES and they print no observed line: independent refutes coding errors and does NOT satisfy clause 6, the peer sharing the same reading of the same specification by the same process. Both directions gained their versions the same day, by republishing the profile — a peer's outbound version follows the profile it dialled, which is how jacorb_giop11.sh has always reached 1.1. The servant direction reads 1.0, 1.1 and 1.2 off the wire, all big-endian from JacORB, with neither[]; the client direction reads 1.1 and 1.2. The client direction's 1.0 is unread for a reason the run states rather than leaves blank: EchoClient drives wide text and our own runtime refuses it — GIOP 1.0 cannot carry wchar or wstring data (§9.3.1.6); this connection negotiated it — which is a correct refusal and a limit of the fixture, not of the stack. The servant direction reads 1.0 from the same peer, which is what makes that distinction checkable rather than a story. This cell reports claimed and not observed deliberately: no tap sits between omniORB and the servant, the little-endian order is inferred from the peer's host, and a sound inference is still not a reading. It needs no bridge and no socket because dispatchCall is a pure function of a servant and a parsed document, which is the same design decision that makes it measurable. Named separately from C because the two are not in the same position: Java's runtime already exists and already speaks AnyJSON v1 (it is the third implementation of it), so it owes the serving half of a runtime it has; C owes a runtime from nothing, and owes it in a language with no exceptions, no garbage collection and no dictionary — which is where the seam's assumption that a reply is a document will be tested for the first time. What is not missing there is the fixture — JacORB already drives a Python servant behind our ORB, with the tap and the byte-order reading. Also unmeasured for Java: GIOP 1.0 and 1.1 in the client direction (both peers negotiated 1.2 and no cell asked for another), the Java runtime relays an any rather than rebuilding it — a peer's document round-trips to the peer's own bytes and _Rt.Any.open() refuses the five families in the published words, but no Java class is synthesised for a struct a peer describes structurally, where _rt.py does synthesise one — and the sweep drives neither recursive types nor the 52 operations whose multi-value result its driver does not build. One naming limit is known rather than decided: an interface's nested scope becomes <Name>Package (the OMG Java mapping's rule, and the reason that suffix is reserved), so a module named FooPackage beside interface Foo would collide — the same shape as the <Name>Servant collision recorded for the other two targets |
orbweaver-test |
✅ | SIDL has a version (2026-08-19): SIDL_VERSION = "1" beside both vocabulary copies, pinned equal across crates for the first time (the_mirror_matches_the_s7_authority); a contract may declare //@ sidl_version: N, read from the syntax tree, unknown → contract/unknown-sidl-version (Warning), none → v1; the property sweep takes every value across AnyJSON too (to_json → text → from_json → CDR, byte-equal to the CDR-only leg, both byte orders, 8 phases; 6016/6016 crossings over golden printed on the summary line (measured 2026-08-24; the harness pins a floor of 5248 and equality of the two halves, which is why the corpus growing past that number never went red — and why this row read 5248/5248 as if it were current for three days after it was the before figure. Not re-run by the 2026-08-25 record pass, and the corpus has grown since it was taken — golden went 33 → 37 files, and one batch put three ids on the json/unmapped pin and one on the empty-sequence pin — so this figure is what was measured on 2026-08-24 and is not a claim about today; the orbweaver-dynamic row's 5952 of 5952 is the same measurement one corpus-state earlier, which is why the two rows disagree); json/unmapped names what the mapping documents as not crossing (seven ids over golden), and the summary line carries the wire-refusal count (30 declarations over golden, §4.4 and natives, 18 unmeasured by the property — since 2026-08-24 deferred_wire_gaps computes the owning crate's head markers by sentinel instead of matching a §4.4 fragment a native's sentence rightly no longer contains); first pass over four corpora 0 findings, two negative controls red), every property case produces a value or reports prop/unmeasured (golden 15's TreeSeq was [] on every valued case and None on 22 of 32, skipped silently, while the summary said 32 — the sampler's depth predicate now mirrors the sampler), seeded round-trip property over 66 golden types × 32 cases × 2 orders × 8 alignment phases (0 defects, findings replay from seed=), annotation contract advice, contract-check in the harness, wire-fuzz panic freedom: 10 decoders a peer reaches before any policy runs (read_message, decode_request, decode_reply, TypeCode, IOR, dynamic any), uniform/mutated/truncated inputs, 0 panics in 50k cases × 10 targets with the reach reported so a green run can be read |
a contract rule keyed on a configured budget rather than on the contract's shape, which needs a deployment's policy to check against — the shape-keyed half landed on 2026-08-14 (contract/inherited-destructive-splits-the-quota, 7b27889) and the approval-shaped rules beside it (contract/effect-unknown, contract/gated-without-authz) with it; this row named the whole class as absent for eleven days after half of it existed; fixed reports as prop/unsupported-type (Advice, naming §4.4) on the sampler's leg and json/unmapped on the AnyJSON leg, rather than passing — this row said prop/unmeasured, which is the other id, the one kept for the sampler contradicting its own predicate; the two facts shared an id until the split and the row still named the pre-split one; the recursive gap closed and is now asserted at zero; a corpus file with no recorded differential verdict fails cargo test --workspace (every_corpus_file_met_both_front_ends, 2026-08-25): eight files landed across two batches without either batch running spikes/differential.sh, seven of them diverging between omniidl and JacORB 3.9, and the gap was invisible until the coordinator's harness run days later — the differential was never broken, it was never run, and agents are told not to run the harness. Naming the command in a document is the form that had already failed here, so differential.sh --record writes corpus/differential-results.tsv and refuses to write it with one oracle, and this test — needing no oracle — compares that record's file set against the corpus on disk. It checks membership only and says so in the record header, the test docs and the failure message: that the recorded verdicts are today's is a claim only the differential can make. 94 files recorded 2026-08-25 |
orbweaver-console |
✅ | catalog … --ior <file> carries a per-peer CSIv2 record beside each interface (2026-08-19), and says unmeasured here in words when no reference is supplied; the diff page renders the approval store — who / why / when / still-applies per finding — as words (2026-08-19); catalog, contract diff and D004 traces as self-contained HTML — no web framework, no template engine, no serialiser. The read half of administration (D024 §6 item 1, 2026-08-25, src/orb.rs): services, config and stats as subcommands of this binary, not a second orbctl — the deciding argument is tests/escaping.rs, which asserts structurally that no page carries an element this crate did not write over an allowlist of eighteen literal tags, and a second binary would have duplicated Output, the --html/--text contract and the usage text while sitting outside that proof until somebody remembered to extend it. Nothing here ends a channel, deactivates a POA, drops a connection or registers anything; the write half waits on D019. What an operator cannot see is said in words: the three stats types live inside a running process and D024 §7 refuses a wire interface for administration until a caller model exists, so orb::Snapshot is the input in both honest forms — live in-process, and a file that process writes — and since nothing in this workspace writes one yet, an operator can point this at a snapshot and cannot point it at a running server, learning that from the tool's own refusal rather than from an empty page. The sixteen reserved ObjectIds are deliberately not copied into this crate: the snapshot's writer states reservedness per id because the writer is the ORB, and where it said nothing the row renders not stated — a third state and not a no, which is behavioural rather than cosmetic, since omniORB answers NO_RESOURCES for a reserved id with nothing bound and BAD_PARAM for a name it never heard of. The seven ORB values are read from the constants that own them and each says compiled default; the drop split is never re-summed, and the reconciliation is ChannelStats::split_adds_up() called, not re-implemented. One defect the oracle did not catch and a test now does: a config row stating a value and no origin was dropped in silence and the page showed the compiled default beside it — the console answering the one question an operator came with by guessing. 94 #[test] functions counted in the crate on 2026-08-25 (75 before this batch). The catalog re-implements no exposure rule: every verdict is the real Chain through dryrun::survey, and where the gate names a scope only on refusal it runs a second caller-less pass rather than reading the requirement off the annotation. Escaping is a type — Markup::text is the only data constructor and there is no raw, checked by enumerating every element against the eighteen tags the crate emits |
measured past corpus scale 2026-08-18, and it found a defect: pointed at the thirteen-contract estate the console drew 58 of 76 operations and said nothing about the missing 18 — nine interfaces inherit a base declared in another file and the string entry point could not resolve the include. Also a header card counting properties of the contracts read 0 exposed, 0 need a human, 0 gated over an estate the gate refuses entirely — a page of zeroes that reads as nothing to worry about — and 58 of 58 effect cells rendered as an em dash. Words now, with the gate's own tally beside them, and #pragma prefix is free text, so a locally authored contract is a route for markup into a repository id; that case is a fixture and renders inert. 37,631 B / 9.5 ms for the estate catalog, 2.15 MB / 46 ms for 5,000 trace spans, 0 elements the crate did not write. The attribute gap it inherited is closed — and closing it found that accessors were not merely invisible but ungated: the catalog now lists _get_/_set_ with the attribute's own scope and effect. And the page drew 57 of 208 registry entries (2026-08-24, declarations.rs): 151 declarations reached no reader surface — 39 constants, 47 structs, 35 typedefs, 11 exceptions, 8 unions, 7 enums, 3 valuetypes, 1 native — because Entry::Interface was the only variant the crate reached, so two golden files that declare no interface printed "the catalog is empty" over 22 constants and a union. Now every kind draws, a constant's value is the registry's ConstValue spelled the way the §5.3 differ spells it, and the test is a partition — every id the registry holds is an interface row or a declaration row, over every golden file — rather than a check that constants are present, which would have gone green with seven kinds still missing. (That sentence was a fifth cell in a four-column table until 2026-08-25 — every markdown renderer dropped it, so the closure was invisible to every reader of this page while the source looked complete.) |
| Service | Status | Note |
|---|---|---|
| Catalog storage (PostgreSQL + pgvector) | ❌ | in-process Registry + exposure.todo.tsv are the stated seams; D003 approved, half (B): the durable store stays deferred until a pilot demands durability, with the adoption path pre-cleared (tokio-postgres + pgvector, licences verified) so the batch that needs it does not also have to settle the licence question |
| Embeddings / semantic search | ◐ | D003-A landed: wrapper, cache format and lexical∪vector union built and tested; the synonym class is still UNMEASURED because no key exists here. The frozen v1 set keeps the 0/10 headroom baseline beside v2's widened 28/28 |
| Observability | ✅ tier 1 | D004 approved and tier 1 built: one span record per decision on F4's telemetry stage, JSON lines, cargo tree still at two. ts is a required argument with no default, so a host with no clock renders - rather than a plausible time — and two sessions replay byte-identically across processes (harness). SpanRecord has private fields and no public constructor, so a credential cannot reach a line by construction. The emitter and the console were built against the decision's table, never against each other, and the harness now runs one into the other. Earlier draft: first-party JSON-lines sink behind a sink trait on F4's chain (zero crates), tracing (MIT, verified) and OTLP pre-cleared with triggers, awaiting approval |
| End-to-end path | ✅ | spikes/end_to_end.sh, in the harness: a fresh requirement → S1–S5 → both generated halves → a hand-written servant on our POA → an agent-shaped caller through the guard, with a scope refusal visible in the transcript. 185 hand-written product lines against 778 generated. The composition's finding: re-running S1–S3 on the same requirement passed every gate again and produced a different contract, including a different authorization scope — nine downstream failures, one cause, and no gate in the project catches it |
| Deployment (Docker/K8s, IOR rewriting) | ◐ | R7 built (giop::nat, PHASE6): profiles and TAG_ALTERNATE_IIOP_ADDRESS rewritten, object key and IIOP version refused as targets (a key is identity, not a route), an undecodable profile preserved byte for byte — which is why rewriting uses RawIor and not Ior, since Ior keeps only TAG_INTERNET_IOP and would delete a profile silently (measured). Publish time preferred, read time available. Both real failures constructed: refused and timed out. Three routing-domain probes have now all run (2026-09-04): the VM probe (2026-08-14, PHASE6), the container probe (first ran on CI 2026-09-04 — its "never executed" was a false skip repaired by PLAN-NAT-PROBE's lanes), and the cluster probe, which ran and demonstrated on its first execution ever the same day — CI provisions kind (~57s + 7s in-group, measured against §G's ~4× tripwire and nowhere near it), the pod IP did not dial from outside, the Service address did, and the NodePort case translates the port as well as the host. The first run was of the repaired script: three defect causes were fixed by reading before any cluster existed (rollout overlap, want=fail accepting any non-zero, a silent ConfigMap failure), which is the absent-oracle rule paid forward instead of after. Where no engine or cluster answers, each stays a counted skip naming where it does run. Still missing: no rewritten IOR has been put in front of a foreign ORB — all three probes dial with our own client. (That sentence was a fourth cell in a three-column table until 2026-08-25 — every markdown renderer dropped it, so this row's whole "what is missing" half was invisible to every reader of the rendered page while the source looked complete. Same defect as the console row's fifth cell, found the same way: by counting the delimiters rather than by reading the file.) |
| Naming (CosNaming client) | ✅ | corbaname/corbaloc + omniNames in harness |
| MCP transport | ✅ | stdio JSON-RPC; no real MCP client driven yet (stated in PHASE3) |
| CosNaming server | ✅ | F6 landed 2026-08-14: full context surface + NamingContextExt, both oracle directions measured — omniORB's client decoded our NotFound bytes. Suite plan: PLAN-SERVICES §2 |
| Event/Notification (CosEvent) | ✅ push model | F7 landed: EventChannel/both admins/both proxies served on our POA, any relayed verbatim, bounded queue (64, drop-oldest), dead consumers disconnected after 3 consecutive failures with drops counted, never silent. omniORBpy 4.3.4's PushConsumer attaches to our channel and decodes what we push (harness group). Drops are split by cause since 2026-08-20 — dropped stays the total and dropped_overflow / unrelayable / dropped_on_disconnect / dropped_on_failure_disconnect / dropped_at_stop sum to it, with fanned_out (per-proxy copies) as the denominator a drop rate needs; measurable: the back-pressure rate and the fan-out multiplication; not measurable here and stated as such: per-consumer attribution (accounting is channel-wide) and whether fan-out was unwanted (CosEvent has no subscription predicate) — PLAN-DEFERRED §1. Pull model: the consumer half is served since 2026-08-18 (obtain_pull_supplier, pull, try_pull — the same bounded deque as push) and the supplier half since 2026-08-25 (obtain_pull_consumer, connect_pull_supplier, disconnect_pull_consumer), so all four supplier/consumer models of the 2×2 are creatable and all_four_models_carry_the_event_they_were_given builds each pair over the wire and asserts an event crosses it. event_server::is_deferred now matches destroy alone, whose deferral turns on a caller model reaching this servant (PLAN-DEFERRED §11, trigger unfired) and not on any of this. Measured, not predicted — ./spikes/service_sweep.sh --raw: TOTAL CosEvent probes 28 dispatched 27 NO_PERMISSION 0 NO_IMPLEMENT 1 BAD_OPERATION 0 unmeasured 0, where it was dispatched 24 / NO_IMPLEMENT 4; eighteen declared operations, seventeen served, one deferred. The sweep itself had to move with the work: it probed ProxyPullConsumer's operations against a push consumer proxy, which was honest while nothing could be addressed and would have reported the interface unserved the moment it became served — the same mis-measurement its own comment records from the consumer half on 2026-08-18. The channel asks with try_pull and never the blocking pull, because the source round is shared and one silent supplier would be every other supplier's outage; the price is an interval the channel must invent (DEFAULT_SOURCE_POLL, 100 ms) and a round that finds an event does not sleep. pull_failures is kept apart from push_failures — one is what the channel could not send, the other what it could not fetch — and no drop cause joined the split, because a ProxyPullConsumer holds no queue and there is nothing to drop; a supplier answering Disconnected is released and not counted as a failure, since it did not fail, it said it was finished. Oracle is an omniORBpy CosEventComm::PullSupplier our channel dials, both byte orders. A server is no longer a channel (2026-08-25): EventChannelServer holds a name→channel map instead of one Arc<Shared> and three fixed keys, with create_channel/channel_names/handle_named/total_stats additive and every existing signature unchanged — a server built the old way is one channel whose keys are its base_key verbatim, and spike-events is byte-for-byte unchanged across the commit. There is no factory and that is deliberate: CosEventChannelAdmin declares none, the standard's factory belongs to CosNotification and is deferred, so creation is a Rust API and a deployment decision as Poa creation is. is_channel_name_safe carries the proof in its doc comment, because two names minting one object key are two channels that are one channel and the symptom is silent; routing is exact membership and never a prefix match, and there are two outbound threads per channel, since one shared thread would make one channel's dead consumer every other channel's latency. total_stats() states its own limit: it cannot say which channel lost an event, and nothing divides by the channel count to guess. (This cell said "refused BAD_OPERATION" three answers behind on 2026-08-19, and said the supplier half was NO_IMPLEMENT for the rest of 2026-08-25 after it stopped being true — twice now the wire moved and the row did not.) PLAN-DEFERRED §10 graduated to PLAN-SERVICES §4.1 on 2026-08-25, the first chapter ever to leave that file; §11 is unchanged and is still the citation for destroy. A channel is reached by its name and not by its address (E3, 2026-08-26): publish_channels binds each channel of an EventChannelServer into a naming context the caller supplies as { id: <name>, kind: "EventChannel" }, the mapping stated on CHANNEL_BINDING_KIND rather than implied — a free function and not a method, because binding is an outbound call and the registry lock is dropped before the loop that dials. tests/channel_found_by_name.rs, 8 tests: the client holds corbaloc:rir:NameService and a name and no channel IOR, enforced by reach_by_name's signature; the channel's server is stopped and restarted at a different address with the same object keys and nothing the client observes changes, with three negative controls each shown red. Peer half spikes/event_by_name.sh: omniORB resolves the name, narrows and receives over a reference whose address it was never given. Two limits, both stated rather than implied: the test re-runs the whole bootstrap, so it measures that a new client is unaffected and measures nothing about an existing connection surviving the move; and the peer script is not in spikes/run_checks.sh — that file was held by another batch as this landed — so this is a cargo test gate plus a standalone spike, and the D029 §6 ledger below cannot see it. What E3 closed for Location and the four leaks it did not are in D029 §6.1 |
| Trading (decision engine) | ✅ | orbweaver-trading (2026-08-14; 87 #[test] functions counted in the crate on 2026-08-26, 60 on 2026-08-25 — this row read 37 tests for eleven days): offers, §4.3 constraint queries, §6 loading policy over deterministic traces, three-valued matching: a field the offer's source could not populate is unanswerable rather than false, and — since 2026-08-19 — unranked: an offer with no value for the ORDER BY field is set aside and named (Selection::is_complete() is the router rule), because "unknown sorts last" still picked an unmeasured expert whenever nothing was measured; the placeholder 0.0 before that satisfied every latency_p50 < bound. moe v1.1 (D010 A2) carries specialization/latency_p50_ms through MeasuredCapability and register_measured/heartbeat_measured, both byte orders, additive by idl-diff against the frozen corpus/evolution/moe/v1.0. Two languages, engine only (D022 T1/T2, 2026-08-25 — no wire surface, no new crate, cargo tree -p orbweaver-trading still one line). T1 grows the §4.3 constraint subset with OR, NOT, parentheses and EXIST, precedence written down rather than implied and chains parsed flat so a fifty-thousand-conjunct query costs a loop and not fifty thousand stack frames. Its finding: AND/OR cannot tell three-valued logic from two, and NOT can — with monotone connectives only an expression is Yes exactly when it is true with every unknown replaced by false, so for the whole grammar as it stood, three-valued matching and "a field nobody recorded does not match" returned the same offers and the difference was visible only in the report beside them. NOT breaks that in the dangerous direction: under missing means false, NOT specialization == 'math' returns the expert nobody ever described, the original bug Truth was built to prevent arriving through the new operator. Here it stays Unknown and EXIST turns a gap the report could only name into one a query can close. The three-valued tables are chosen, not cited — Kleene's strong logic, which is also SQL's — because TCL is a separate OMG document and the copy of Part 1 available carries no TCL grammar; no normative text was read for this behaviour and none is quoted for it. T2 adds the preference expression (MAX, MIN, WITH, RANDOM, FIRST) as its own module, because CosTrading::Lookup::query takes constraint and preference as two parameters of two grammars; five semantics were decided with their reasons and none is quoted from a text nobody read. MAX/MIN refuse residency and the text fields by name despite their total order, because reading "the largest value of a number" as "the last enumerator" would be this engine deciding what a standard word means. WITH over an unanswerable offer places it in neither group, which makes the consequence worth stating: the constraint decides membership and the preference decides order, and their gaps land in different buckets — unanswerable and unranked. RANDOM is a seeded permutation with the seed in the text, because replay reproduces a trace bit for bit and an unseeded shuffle would end that where it is easiest not to notice; shuffle_key is written out rather than reached for, since DefaultHasher is explicitly not stable across Rust releases and an ordering that changed under a compiler upgrade would be a replay divergence nothing would catch. RANDOM with no seed and an empty preference are refused rather than defaulted, because inventing a documented default would be a semantic nobody could check. MAX f/MIN f are ORDER BY f DESC/ASC exactly, pinned offer-for-offer over seven pairs including the gapped field; a query carrying both orderings is refused by name rather than having one win. MAX_DEPTH = 64 came in with the first nesting constructs rather than after them, since unbounded nesting over untrusted input is a stack overflow — a crash, not a refusal, and refusing with a position is this parser's whole argument for being first-party. Wire surface: the row below. Still missing: Router::select still orders by route_freq and binds max_latency_ms to p99: ordering by p50 over the wire would be a select_measured with its own reason, and no consumer has named one; experts announcing through v1.0 register_expert remain unanswerable/unranked on the two fields by design. T1 and T2 were engine only when they landed; T3 and T4 gave them one on 2026-08-26 — service_type and lookup (the row below), reached over the wire by orbweaver-giop::trading_server. The keyword-case reconciliation query.rs deferred to the facade is in the facade, where that scope note said it belonged: lookup::fold_keywords upper-cases whole-word keywords outside quoted text, so TCL's lowercase and/or/not/exist and min/max/with/random/first are accepted over the wire while the engine keeps one spelling. It is a case fold and not a rewrite precisely so ASCII length is preserved and every parse error's byte position still indexes the caller's own string. Property names do not fold — they are identifiers, and folding one would invent a name the contract does not have. Measured by omniORB, not by us. (This row's "what is missing" half was also a fourth cell in a three-column table until 2026-08-25 and was dropped by every renderer.) |
| Interface Repository (read-only facade) | ✅ | orbweaver-registry::ifr: lookup_id, the Contained getters, describe_interface, is_a, _get_base_interfaces, served on our POA with keys derived from the repository id (no per-reference state, references survive a restart). omniORB's own IR client narrows it and prints enumerators by name (PARAM_IN, OP_ONEWAY, dk_Interface), so the ordinals are right rather than self-consistent. Writes refused NO_PERMISSION before target resolution — the registry is populated from IDL through S4, and a writable IFR would be a second, ungated ingestion path. The browse half landed 2026-08-25 and ifr::is_deferred is now empty: SERVICES-COVERAGE §5 measured 9 of 44 served, 25 refused, 10 NO_IMPLEMENT, and it is now 19 of 44 served, 25 refused, 0 deferred. The ten were not ten decisions but one — Container::contents, lookup, lookup_name, describe_contents, Contained::describe, _get_defined_in, _get_containing_repository, get_canonical_typecode, get_primitive, IDLType::_get_type are the walk that lets a client browse rather than look one entry up by an id it already had to know, and nine are unusable without the tenth. Three objects had to be minted first: ModuleDef, derived from the scopes entries sit in with the segment count taken from the qualified name and never the id path (so IDL:acme.com/bank/Money:1.0 yields IDL:acme.com/bank:1.0 and not a module that does not exist); OperationDef/AttributeDef, where member_id becomes the one home for a derivation describe_interface already did and member_for its inverse, so a member reachable by a description is reachable by a key — a reference a client is handed and cannot dial being the failure that avoids; and PrimitiveDef, the one object §14.5.14 gives no repository id, so its key carries pk:<kind>. §14.5.4.1's two lookups are different operations and implementing the second as the first with one level would have been a plausible and wrong reading: lookup takes a scoped name and resolves outward returning at most one, lookup_name takes a simple identifier and searches inward returning every match. levels_to_search of 0 is undefined by the sub clause and is refused BAD_PARAM, because answering something arbitrary for an undefined input is how an undefined input becomes a compatibility promise nobody chose. Both contents parameters filter — a contents ignoring either would answer every call identically and pass any test that only counted results — so eleven kinds are asserted to return exactly their own definitions and exclude_inherited to move an operation and an attribute in and out independently. omniORB 4.3.4's own omniORB.ir_idl client walks the whole repository over TCP and every leg answered, the anys extracting as the structs §14.5 names; the peer found two defects an in-tree test had not — lookup on a top-level module answered nil because Registry::load removes a module's qualified name after walking into it, and the first probe's gate asserted dk_Module < dk_all at a root where every object is a module, a gate that could only ever be red. Narrower than the specification, deliberately and recorded rather than faked: results are in repository-id order, not the declaration order §14.5.4.1 asks for, because Registry holds entries in BTreeMaps and declaration order is gone by the time the IDL is loaded — the order given is total, stable and identical in both byte orders; and only Repository, ModuleDef and InterfaceDef are claimed as containers though §14.5.10/§14.5.20 also make a StructDef and an ExceptionDef ones, because a servant that refused the narrow _is_a and then honoured the operation would tell a client two different things about one reference — widening means widening both, in one commit. ValueDescription::supported_interfaces, abstract_base_values and OperationDef::_get_contexts are empty because the AST carries no supports clause and nothing is invented; a constant whose value the wire cannot carry gets an any of tk_void — a value that says there is no value here — rather than a guess |
| Trading wire surface (project contract) | ✅ | orbweaver-object::expert_service serves moe::ExpertRegistry/ExpertLoader (corpus/golden/22) — the project contract. Oracle is our own client (spike-experts); no foreign MoE peer exists to test the other direction, and none is claimed |
Trading wire surface (standard CosTrading facade) |
✅ | Open since 2026-08-26 (D022 T3/T4); the deferral in PLAN-SERVICES §3 was until a foreign trading client is named, and omniORB 4.3.4 is one — it ships CosTrading.idl and generated Python COS stubs. orbweaver-giop::trading_server serves CosTrading::Lookup::query plus the twenty readonly attributes of TraderComponents/SupportAttributes/ImportAttributes, over a TypedOfferStore (D022 T3: a service type is a name, an interface repository id and a property schema checked at registration — no ServiceTypeRepository servant, §7). Oracle is not ours: spikes/trading_client.py imports omniORB's COS stubs, narrows IDL:omg.org/CosTrading/Lookup:1.0 and calls query — 45 assertions, 0 failures on 2026-08-26 against spike-trading. omniORB runs as a separate-process wire peer over TCP, so cargo tree is unchanged. offer_itr is always nil and nothing is ever truncated to make that true: the specification's escape is legal only when the matches fit how_many, so a query that does not fit is refused NO_IMPLEMENT (this workspace's rule: declared and deliberately not served) rather than answered short under a nil iterator that would claim completeness. The bound is on the wire as max_return_card, answered from the same constant lookup::cannot_answer_completely quotes. Still missing, named: no Register/Link/Proxy/Admin (all four answer nil), no OfferIterator object, no service-type inheritance, no import policy — every policy name is refused, because CosTrading::Policy carries an any in a sequence and CDR gives an any no length prefix, so skipping one needs a TypeCode walk orbweaver-giop does not have; an offer's reference is nil unless a deployment bound one (set_reference), since offers here are capability descriptors and IF1 keeps IORs off the MCP face. In spikes/service_sweep.sh since 2026-08-26, against corpus/services/trading-lookup-subset.idl — the first-party contract the sweep needed, written from the OMG specification (omniORB's CosTrading.idl must not be vendored). Measured that day: 21 declared, 21 served, 0 unmeasured, and the trader is the first standard service whose operation list the sweep reads from a contract of ours rather than from omniORB's installed IDL; the rest are marked fixture in SERVICES-COVERAGE §8's provenance table |
| LifeCycle / Property | ✅ | F5, and since 2026-08-18 driven in both directions — an omniORB client calls all sixteen operations of golden 23 through its own stubs (the one direction SERVICES-COVERAGE §9 had open); found that bind_expert/set_policy take references no operation of the contract returns: ModelFactory/ComposedModel/PolicyDomain/EnterpriseExpert served from corpus/golden/23. The object key is the tenant context — moe::CallContext carries no tenant and a servant sees no service context, so there is a factory per tenant; a shared one could not have checked retire at all. Refusal precedes the existence check, so it is not an existence oracle. base() is served, counted and audited: two tenants on one base necessarily get the identical reference, which is a correlator by construction and only stops by not sharing. Scopes and caller identity are not enforced here and the module says which layer owns each (guard chain, MCP capability handles, CSIv2) |
| Transaction / Time / PSS / Concurrency / CosCollections / Notification / federated naming / full Security Service | — | excluded and designed: docs/PLAN-DEFERRED.md gives each a chapter with the concrete trigger that would un-defer it and a v1 sketch, so "excluded" means "designed enough to resume" rather than "forgotten". Honest absence over decorative interfaces |
Every service row above says ✅ and every servant implements a subset,
deliberately. SERVICES-COVERAGE.md is the measured
list and its home for these figures is that file, not this one — what
follows is a pointer with the date it was taken, because a count restated here
drifts from the sweep at whichever copy is edited second. Declared operations
are probed with raw GIOP rather than with our own client, classified served /
refused-with-a-reason / absent.
The number that mattered when it was taken: 12 of 107 were BAD_OPERATION
with no reason in the servant, the plan, or anywhere in the tree. The wire
cannot distinguish a considered refusal from a forgotten one, so the wire
supplies the fact and a document has to supply the reason — and for those
twelve, none did.
Two services moved on 2026-08-25 and that tally has not been re-run as a
whole. CosEvent went from 14 of 18 served to 17 of 18 (service_sweep.sh --raw: probes 28 dispatched 27 NO_PERMISSION 0 NO_IMPLEMENT 1 BAD_OPERATION 0 unmeasured 0, from dispatched 24 / NO_IMPLEMENT 4), and the Interface
Repository from 9 of 44 to 19 of 44 with its deferral list emptied. Both
changes are NO_IMPLEMENT → served, so they do not by themselves move the
twelve; what they do move is the served/refused split those twelve are quoted
against, which is why the figure above carries its date and is not restated as
today's. This record pass did not run the full sweep — it needs the
fixtures and the machine-wide lock — so the whole-estate number is
unmeasured here and is reported as such rather than carried forward as a
pass. SERVICES-COVERAGE §5 and §8 are the rows that need it. (Both were
regenerated from the wire later the same day — coverage_tables.py --check
exits 0 over 113 rows and 238 sweep lines.)
The supplier side of pull now states what a disconnect guarantees (2026-08-25).
disconnect_pull_consumer returning did not mean the channel had stopped
asking: the source thread snapshots a round, releases the state lock — a
network call cannot be made holding it — and only then invokes, so a round
snapshotted first still asked, across a window containing a whole connect
timeout. The loop now has a commit point taken under that same lock with no
I/O between it and the request going out, which makes the bound sayable:
after disconnect_pull_consumer or stop returns, at most one further
try_pull reaches that supplier, within the outbound timeout — later rounds
are cancelled and counted in ChannelStats::pull_rounds_cancelled. The
predicate is one function serving both callers and compares the supplier IOR
rather than a flag. Held by a deterministic seam rather than by repetition:
with the commit point removed the pin fails 20 of 20 runs, with it 0 of
20 — the defect itself never reproduced on macOS in 20 serial runs, 5
concurrent whole-suite runs, or with the poll forced to 200 µs, and was found
by CI on Linux.
pull 공급자 쪽은 이제 disconnect가 무엇을 보장하는지 말한다(2026-08-25).
disconnect_pull_consumer가 반환해도 채널이 묻기를 멈춘 것은 아니었다 — 소스
스레드는 라운드를 스냅샷하고 상태 락을 놓은 뒤에 호출한다. 이제 같은 락 아래
커밋 포인트가 있고 그것과 요청 발신 사이에 I/O가 없으므로 한계를 서술할 수
있다: disconnect나 stop이 반환한 뒤 그 공급자에 도달할 수 있는 try_pull은
아웃바운드 타임아웃 안의 하나뿐이며, 이후 라운드는 취소되어 계수된다. 반복이
아니라 결정적 이음매가 고정한다 — 커밋 포인트를 빼면 20/20 실패, 두면
0/20. 결함 자체는 macOS에서 재현되지 않았고 리눅스 CI가 찾았다.
Known and unfixed, from that sweep: _get_version answers BAD_OPERATION
while _set_version answers NO_PERMISSION, which is backwards by ifr.rs's
own argument that NO_PERMISSION says the operation exists and the answer is
no — on data the registry already parses out of every repository id it holds.
moe::Router::select/dispatch are declared in a landed contract, served by
nothing, and named in no plan including the exclusions table.
✅ 행은 모두 의도적으로 부분집합을 구현한다. 이 수치들의 집은 이 파일이 아니라
SERVICES-COVERAGE.md 이며, 여기 적힌 것은 측정 날짜를 단 포인터다 — 여기에 다시
적은 수치는 둘 중 나중에 고쳐지는 쪽에서 어긋난다. 측정 당시 107개 중 12개가 이유
없는 BAD_OPERATION 이었다 — 와이어는 숙고된 거부와 잊힌 거부를 구분하지 못하므로,
사실은 와이어가 주고 이유는 문서가 줘야 하는데 그 12개에는 없었다.
2026-08-25에 서비스 둘이 움직였고, 그 집계 전체는 다시 돌리지 않았다. CosEvent는
18개 중 14 서빙에서 17로(service_sweep.sh --raw: dispatched 27 NO_IMPLEMENT 1, 이전은 dispatched 24 / NO_IMPLEMENT 4), 인터페이스 저장소는 44개 중
9에서 19로 가고 유예 목록이 비었다. 둘 다 NO_IMPLEMENT → 서빙이므로 12라는 수
자체를 직접 움직이지는 않지만, 그 12를 인용하는 서빙/거부 분할을 움직인다. 그래서 위
수치는 날짜를 달고 있으며 오늘의 측정으로 다시 적히지 않았다. 이번 기록 패스는 전체
스윕을 돌리지 않았다 — 픽스처와 머신 전역 락이 필요하다 — 따라서 전체 추정치는
여기서 미측정이며, 통과로 넘기지 않고 미측정으로 보고한다.
The completion criterion's home is
D029 §6 and the five
transparencies and their leaks live in its §6.1. Neither is restated here.
What belongs here is the status of the instrument: since 2026-08-26 the
harness declares, per transparency, which groups measure it and what is named
unmeasured (D031 H1/H2), so the answer stops being a reading assembled by hand.
| Status | |
|---|---|
| the five names | ✅ one home, docs/decisions/D029-...md §6.1, read by spikes/transparency.py; a harness tag naming anything else fails the run by name |
| the ledger | ✅ printed before the verdict, computed from the run; no score, and an untagged run reads as NONE measured, not as a pass. It also distinguishes a group that looked from a group that declared it had not (tp_measures_nothing), so a transparency whose only declaring groups measured nothing still reads UNMEASURED with their blockers in the load-bearing column. Its eight negative controls run without the harness's lock in about a second — ./spikes/ledger_control.sh, 33 assertion groups, 0 failed (2026-08-26) — and since that day they are also a harness group, which they were not when control 5 went red and stayed red unnoticed |
| groups declaring | ◐ seventeen tags over all five names, measured 2026-08-26 (location 8, backend 3, language 3, lifecycle 2, activation 1). Every transparency now has at least one declaring group; two of the five (language, activation) are declared only by groups that declared they measured nothing, so the ledger still reads them UNMEASURED. Recompute rather than quote this: python3 spikes/transparency.py --check spikes/run_checks.sh, and the ledger of any run is the live answer |
| leak tests per transparency | ◐ D029 §5 O0 landed and reaches the instruments (2026-08-26). crates/orbweaver-test/tests/what_a_caller_can_tell.rs changes the hidden property under a live caller; spikes/leak_tests.sh gives one leg per transparency, read from spikes/transparency.py and never retyped; spikes/leak_controls.sh puts each leak back and requires the test to see it (14 checks, 0 failures, 2026-08-26). Three legs measure — a target moved under a live caller, the implementation behind one reference replaced mid-session, and (since 2026-08-26) a target evicted under a live caller, whose test is crates/orbweaver-object/tests/what_a_caller_can_tell_about_load.rs and whose control is inside it rather than in leak_controls.sh. None is a counted SKIPPED any more. The last of them was language, and it |
retired on 2026-08-30: it waited on *a Python servant mountable as a Dispatch |
|
in a server the test owns*, which orbweaver_gen::pychild::SeamChild now is. |
|
| Counted skips went 16 to 15, and every one of D029 §6.1's five transparencies | |
is MEASURED by a leak leg with a live caller. Lifecycle stopped being the second on 2026-08-28 — D035 was approved with displacement is not closure, so the row was no longer waiting on a decision that could not reach zero, and crates/orbweaver-giop/tests/what_a_caller_can_tell_about_a_removal.rs measures what is above the floor: removing one target is invisible to a caller of another. The floor itself — a caller of a removed target can tell — is asserted rather than left to prose, so a change that made it stop being true could not pass unnoticed. Counted skips went 17 to 16. One line is owed in run_checks.sh: leak_leg fails a MEASURED row whose group still carries the static tp_measures_nothing it was given while its leg was a skip, which is what stops a leg that starts measuring from being swallowed by a stale declaration. The activation instance of that was settled on 2026-08-27 — the declaration is gone and a comment stands where it was. This row, and leak_tests.sh in two places, went on billing for it and all three cited line 4318 for a group that had moved to 4792: a debt naming a location nobody re-checked is a debt nobody re-checked, and the line number is what made it findable. The activation skip is the evidence for what a named blocker is worth: its sentence — "a POA-level activation path that reloads an evicted target" — was what the closing batch scoped itself from, and the second sentence of the same skip turned out to be wrong (it said the leak was Router::select), which a named blocker makes refutable and an absence does not. All five are harness groups, so the skips are counted by the verdict and cited in the ledger's unmeasured: column, which is what "reaches the instruments" means and is what they did not do on the day they landed. Still outside: orbweaver-giop's channel_found_by_name.rs is a test of this shape with three controls each shown red, and it has no declaring group — it counts toward nothing above. Its peer half now does (2026-08-28): spikes/event_by_name.sh was cited by D029 as what makes E3 "a measurement rather than a self-test" and was run by nothing — grep -c over the harness and over ci.yml both returned 0 — and it is a group declaring location now. Three more of that class were found the same day and are recorded below |
완성 기준의 집은 D029 §6, 다섯 투명성과 그 구멍의 집은 §6.1이며 여기서 다시 적지
않는다. 여기에 사는 것은 계기의 상태다: 2026-08-26부터 하네스는 투명성별로 어느
그룹이 그것을 재는지와 무엇이 미측정인지를 스스로 밝힌다(D031 H1/H2). 위 표의 개수는
인용하지 말고 다시 계산한다 — 어느 실행이든 원장이 살아 있는 답이다. 원장의 부정
대조군 여덟 개는 하네스 락 없이 1초 남짓에 돈다: ./spikes/ledger_control.sh,
단언 그룹 33개·실패 0(2026-08-26). 그리고 그날부터 그 대조군들 자체가 하네스
그룹이다 — 대조군 5가 붉어진 채 아무도 모르고 지나갔을 때는 아니었다.
D029 §5 O0이 착지했고 계기에 도달했다(2026-08-26): what_a_caller_can_tell.rs가
살아 있는 호출자 아래에서 숨은 성질을 바꾸고, leak_tests.sh가 투명성마다 다리를
하나씩 놓으며(이름은 transparency.py에서 읽고 결코 다시 적지 않는다),
leak_controls.sh가 각 구멍을 되돌려 넣고 테스트가 보는지를 요구한다(검사 14개,
실패 0). 재는 다리 셋 — 이동, 서번트 교체, 그리고 2026-08-26부터 살아 있는
호출자 아래에서의 축출(테스트는
crates/orbweaver-object/tests/what_a_caller_can_tell_about_load.rs, 대조군은
leak_controls.sh가 아니라 그 파일 안에 있다) — 과 장애물을 이름 붙인 계수되는
SKIPPED 둘이며 다섯 모두 하네스 그룹이므로 스킵이 판정에 세어지고 원장의
unmeasured: 열에 인용된다 — 착지한 날에는 하지 못했던 바로 그것이다. 활성화 스킵은
이름 붙인 장애물의 값어치에 대한 증거다: 그 문장("축출된 대상을 되적재하는 POA
수준의 활성화 경로")에서 닫은 배치가 범위를 잡았고, 같은 스킵의 두 번째 문장은
틀린 것으로 드러났다(구멍이 Router::select에 있다고 적혀 있었다) — 이름 붙인
장애물은 반박될 수 있고 부재는 반박될 수 없다. leak_leg는 다리가 스킵이던 시절의 정적
tp_measures_nothing 선언을 아직 달고 있는 그룹의 MEASURED 행을 실패시키며,
그것이 재기 시작한 다리가 낡은 선언에 삼켜지지 않게 한다. 활성화의 그 건은
2026-08-27에 정리되었다 — 선언은 사라졌고 그 자리에 이유를 적은 주석이 있다. 이
행과 leak_tests.sh의 두 곳이 그 뒤로도 계속 빚을 청구했고, 셋 다 4318행을
가리켰는데 그 그룹은 이미 4792행으로 옮겨 가 있었다: 아무도 다시 확인하지 않은
위치를 이름 붙인 빚은 아무도 다시 확인하지 않은 빚이며, 행 번호가 그것을 찾을 수
있게 해 준 단서다. 원장은 이제 본 그룹과 보지 않았다고
선언한 그룹을 구분하므로(tp_measures_nothing), 선언 그룹이 전부 아무것도 재지
않은 투명성은 여전히 UNMEASURED로 읽힌다 — 다섯 중 둘(language, activation)이
그렇다. 아직 바깥에 있는 것: channel_found_by_name.rs는 이 모양의 테스트이고
부정 대조군 셋이 각각 붉어졌으나 선언 그룹이 없어 위 표에 아무것도 더하지 않는다.
Added 2026-08-28, after the same defect turned up four times in one day: a document names a script as its evidence, and nothing runs that script, so the evidence is never taken.
| Found | What it had been | |
|---|---|---|
spikes/c_peer.sh |
cited by C-PEER-STATUS.md and D029's Backend row |
never compiled on Linux. Its first CI run failed on a glibc -Werror=format-truncation that macOS clang cannot produce |
spikes/event_by_name.sh |
cited by D029 as what makes E3 "a measurement rather than a self-test" | run by nothing; its own header said "Wiring it in is one hr group and is named as undone in the report" |
spikes/scope_controls.sh |
the negative control for two scope widenings | run by nothing — and it had stopped being able to run: the widening it controls gained a git ls-files scan, and the control feeds it a tree git archive extracted, which has no .git |
spikes/half_reply.sh |
cited by this document and D017 | its row here said "not yet a run_checks.sh group", and had said so since it was written |
Three of the four said so in their own headers. That is the finding rather
than a detail: a debt named in prose is a debt nobody counts.
spikes/cited_and_run.py is the gate, and its distinction is the whole of it —
a header that refuses the gate ("a report, not a gate", which
gap_symbols.py and plan_numbers.py say, and CLAUDE.md says why) is a
decision and passes; a header that defers it ("not wired into", "named as
undone", "the recommended group") is an IOU and fails. Measured 2026-09-02: 58 cited spikes run, 2 state a refusal, 0 owe a
group — this sentence said 47 and 1 under the word "Today" and no
date, which is how a figure in prose drifts while the gate stays green over
it. Recompute rather than quote: python3 spikes/cited_and_run.py.
And a sixth class, 2026-09-02, which that gate cannot see and neither could
its neighbour. Two gates were green and neither was wrong: gap_symbols.py
printed 22 symbol(s) named by gap columns, 22 exist in the tree while four
sites named orbweaver_gen::pychild::PythonChild, a type renamed to
SeamChild the day before — it asks its question of the gap columns, and
the stale names sat in prose, in a table row, and in a plan document. In this
same file two rows said spike-rir is "not yet a run_checks.sh group"
over a group running at run_checks.sh:2442 against a floor of 9, while
cited_and_run.py reported 0 owe a group — it reads spike headers, and
the IOU was here. Each gate was scoped to a place; the rule is about a
claim.
spikes/doc_symbols.py asks it of the claim, and spikes/doc_symbols_control.sh
synthesises the tree it is asked about. Three exclusions, each a reason and not
a quieting: dated records are out of scope by construction, a head we do not own
is Cargo's business, and a rename record claims a change rather than an
existence — the same refuse-versus-defer split as above. Its first draft
reported 0 over the known defect, because the leaf still occurred in a comment
recording the rename: an occurrence is not a definition. Control 5 killed the
second draft, which derived "ours" from the crates that currently exist and so
went silent exactly when a crate was deleted.
여섯째 부류(2026-09-02): 두 게이트가 초록이었고 둘 다 틀리지 않았다 — 각자 장소에 범위를 맞췄고 규칙은 주장에 대한 것이다. 첫 초안은 알려진 결함 위에서 0을 냈다(이름 변경을 기록한 주석에 잎이 남아 있었다 — 출현은 정의가 아니다), 둘째 초안은 대조군 5가 죽였다: "우리 것"을 현재 존재하는 크레이트에서 끌어냈으므로 크레이트를 지우면 조용해졌다.
A fifth turned up one hour after that gate landed, in a leg written the
same day, and it is the class's other half: spikes/leak_tests.sh ran the
removal-isolation test and only narrated its control —
say "control: ORBWEAVER_LEAK_CONTROL=removal_isolation ... Run 2026-08-27: the fresh dial of B answered Gone where it must answer Reply(22)". A control run
once, by hand, on a day, recorded in prose. spikes/leak_controls.sh's own
header calls that "a sentence in a commit message and not a property of the
tree", and it did not catch this one because its subject was a single SRC
naming one test file. The subject is a table now, computed to a path rather
than typed a third time, and the harness runs 19 checks over two files where it
ran 16 over one. Both negative controls were run: an injected subject whose file
does not exist fails rather than silently skipping, and a subject stripped
of its THE CALLER …: sentence is refused rather than passing vacuously
(16 checks, 1 failure). The first attempt at the first control was itself the
defect it was checking for — the copy ran from the scratchpad, so ROOT was a
different tree and the live subject failed too.
한 시간 뒤에 다섯째가, 같은 날 쓴 다리에서 나왔다 — 부류의 나머지 절반이다:
leak_tests.sh는 테스트를 돌리고 그 대조군은 서술만 했다. 손으로 한 번
돌리고 산문에 적어둔 대조군이며, leak_controls.sh 헤더가 바로 그것을 *"트리의
성질이 아니라 커밋 메시지의 문장"*이라 부른다. 그것이 못 잡은 이유는 대상이 SRC
하나였기 때문이고, 이제 대상은 표이며 경로는 타이핑이 아니라 계산된다. 부정 대조군
둘 다 돌렸다. 첫 대조군의 첫 시도가 그 자신이 검사하던 결함이었다 — 복사본을 다른
디렉터리에서 돌려 ROOT가 달랐다.
It does not read tests/*.rs — cargo test --workspace is a group, so a
cited Rust test is already taken. The first hand-written sweep of this class
reported 53 hits and 47 were that false positive; the gate reports 0 because it
was fixed rather than tuned. Two more of its own defects were fixed the same
way: it globbed spikes/* and could not see spikes/jacorb/setup.sh, which
ci.yml runs by that exact path, and its invocation check was one level deep
when trading_client.py sits three.
2026-08-28 추가. 같은 결함이 하루에 네 번 나왔다 — 문서가 스크립트를 증거로
지목하는데 아무것도 그것을 돌리지 않아, 증거가 채택된 적이 없는 것. 넷 중 셋은
자기 헤더에 그렇게 적어두고 있었다, 그것이 세부가 아니라 발견이다: 산문에 이름
붙인 빚은 아무도 세지 않는 빚이다. 게이트의 구분이 전부다 — 거절("게이트가
아니라 보고")은 결정이라 통과하고, 유예("아직 연결 안 됨", "미완으로 이름
붙여둔다")는 차용증이라 실패한다. tests/*.rs는 읽지 않는다: cargo test --workspace가 그룹이므로 인용된 러스트 테스트는 이미 채택된다. 손으로 돌린 첫
스윕은 53건을 냈고 47건이 그 오탐이었으며, 게이트가 0을 내는 것은 조인 것이 아니라
고친 것이다.
17 of 63 was never computed. D029 §6.1's Lifecycle cell carried it from
2026-08-27 as a hand-typed count of serve sites passing || false — servers
nothing can stop. spikes/serve_sites.py is the method now and the same tree
answers 21 of 80 (2026-08-30): both halves drifted and no gate could go red,
because there was no gate. Its own first two answers were 513 and 509 —
comments quoting serve(…, || false) counted as servers, then a directory walk
counting the eight agent worktrees as eight more copies of the repository, which
is the git ls-files rule this file already carries arriving from the other
side. A report, not a gate: there is no defensible number for how many
servers may be unstoppable, and the count is a lower bound because it
classifies spellings, so a stop handed in through a binding reads as stoppable.
Repaired 2026-09-04, by the rule rather than the count: the same tree
answers 12 of 85, every one carrying a recorded refusal at the site and 0
unexplained — nine sites shared one cause (a helper spawning a detached serve
thread and discarding every stop handle) and took one fix (the helper keeps the
server's own StopFlag, the owner's teardown raises and joins); twelve are
refusals (stopping measurements' own contrast arms, and serve-until-killed
fixtures with no in-process actor left to raise a stop), and the scan now tells
a recorded refusal (serve_sites: refusal at the site, with its reason) from
an unexplained || false, so the next unexplained site is a defect and not a
figure.
The ledger was swallowing the end of that sentence. transparency.py split
each markdown row on every |, so a cell holding an inline-code pipe was cut in
half and the words fixable rather than fixed never reached a reader. The
splitter respects backtick spans now. D029's pipes were not escaped to suit the
parser — that makes the document worse for every other reader to spare this one.
Two tests were asserting timing rather than a property, both found by CI and neither reproducible here:
| what it asserted | what it asserts now | |
|---|---|---|
spike_events's phase 2 |
that the dead consumer had a backlog (dropped_on_failure_disconnect > 0) |
that every discarded event names a cause (split_adds_up) |
handles::expired_entries_stop_counting_against_the_bound |
that ten insertions finish inside a 30 ms TTL | that the bound is reclaimed — which is what its own docstring claims |
The second is the sharper finding: its control had been green. len()
filters by liveness, so it answers the same whether an expired entry was
reclaimed or merely hidden, and stripping the reclaim left the old test passing.
issue_checked bounds on the raw map size, so the property lives at the bound;
asserted there, the stripped-reclaim control fails with the sentence that names
it.
17 of 63은 계산된 적이 없다 — 손으로 적히고 인용만 되었으며, 같은 트리가
2026-08-30에는 21 of 80을 답했다. 그 스크립트의 첫 두 답(513, 509)도 틀렸다:
주석 속 예시를 서버로 셌고, 순회가 워크트리 여덟 개를 셌다. 2026-09-04에 수가
아니라 규칙에 맞춰 수리되었다: 같은 트리가 85개 중 12개, 전부 지점에 거절이
기록되어 있고 설명 없는 곳 0을 답한다 — 아홉 지점이 원인 하나(도우미가 분리된
serve 스레드를 띄우고 멈출 손잡이를 버림)를 공유해 수정도 하나였고, 열둘은
거절이며, 스캔이 이제 기록된 거절과 설명 없는 || false를 구별하므로 다음 설명
없는 지점은 수치가 아니라 결함이다. 그리고 대조군이 초록이던 테스트가
하나 있었다 — len()이 만료를 걸러 세므로 회수와 은닉을 구별하지 못했고, 성질은
한계에 있었다.
Measured 2026-08-30. Rust and Python behind one server, one reference and one open connection; the language changes underneath a caller that never learns a new address.
spikes/leak_tests.sh's language leg had been a counted SKIPPED since it was
written — the last of the five — and the blocker it named was not nobody wrote
it: the only route to a Python servant bound its own listener, so the swap
became an address swap, which is the location row. A test built that way
would have measured the wrong row and been green while it did.
Control, run by spikes/leak_controls.sh rather than narrated:
ORBWEAVER_LEAK_CONTROL=language makes the Python half answer a different
number — left: [0,0,0,4] right: [0,0,0,3], naming THE CALLER COULD TELL WHAT
LANGUAGE ANSWERED — and reddens exactly that one test. The file's second test
is the anti-vacuity companion: both counters must show a servant ran, or a run
where the swap silently did not happen would compare one implementation with
itself and pass.
tp_measures_nothing came off the group in the same change, for the third
time on the third leg: the declaration exists so a group that measures nothing
cannot flip a ledger row, and a group that has started measuring must lose it
or the ledger reads the measurement as nothing.
Not measured: two languages, not N — and one operation. A pair agreeing
on count and diverging on a wstring is not measured by any number of runs.
python_servant.rs is the wide comparison (19 calls × 3 GIOP versions × 2 byte
orders) and has no live caller; this has the live caller and the narrow pair.
2026-08-30 측정. 러스트와 파이썬이 하나의 서버·참조·연결 뒤에 앉고, 호출자는
새 주소를 배우지 않는다. 다섯 중 마지막 계수된 SKIPPED였으며, 막고 있던 것은
아무도 안 썼다가 아니라 유일한 경로가 리스너를 바인딩해 교체가 주소 교체가
된다는 것 — 그렇게 만든 테스트는 틀린 행을 재면서 초록으로 보였을 것이다.
tp_measures_nothing을 같은 변경에서 뗐다: 세 번째 다리에서 세 번째로.
spikes/leak_tests.sh's language leg has been a counted SKIPPED since it was
written, and it named its own blocker: the only route to a Python servant was
orbweaver-py-bridge --serve, which binds its own listener, so the servant
arrived as an endpoint and swapping the language became swapping the address.
A caller made to dial elsewhere has been moved — a different row of D029
§6.1 — so a leak test built that way would have measured the wrong row and
looked green doing it.
The route exists now, in two pieces and no new protocol:
python_rt.serve_on_pipes |
the inverse of Host: answers seam documents on this process's own stdin/stdout |
orbweaver_gen::pychild::SeamChild |
spawns python3 — or, since 2026-09-01, java — as a child of this process and is an Answerer, so seam::ForeignServant makes it a plain Dispatch. It was PythonChild when this row was written; only the command was ever language-specific |
Host.run's loop was extracted into python_rt.answer_calls and both
directions call it: writing it twice would be one protocol with two
implementations. The child is spawned into its own process group and the group
is signalled in Drop — reaping a child is not reaping its tree, measured
once at twelve leaked processes.
crates/orbweaver-gen/tests/a_python_servant_this_process_owns.rs measures the
route and says it is not the leak test: the caller-across-a-swap test is
what this unblocks, not what it is. Its controls: the Python servant answering a
different value is caught (left: 38, right: 37, which is why the value is not
0 — a default would answer that); a child that never serves is refused with
UNKNOWN/Maybe rather than passing; and five runs leave no python3 -c
process and no orphan, counted rather than read off a verdict.
언어 다리가 막혀 있던 이유는 유일한 경로가 자기 리스너를 바인딩해서 파이썬
서번트가 엔드포인트로 도착했기 때문이다 — 주소를 바꿔 다이얼하면 그것은
이동이고, 이동과 언어는 D029 §6.1의 다른 행이다. 새 프로토콜은 없다:
Host.run의 루프를 추출해 양방향이 함께 부른다. 이 테스트는 누출 테스트가
아니며 그렇게 적는다.
D036, approved 2026-08-29 (option A). The default that accepted every object key
is deleted from both Dispatch and SharedDispatch; 22 implementations now
state their answer where they used to inherit one.
It closes nothing by itself, and the decision says so. A servant that writes
true leaks exactly as one that inherited true did. What changed is that it
cannot happen by omission — the gap is unrepresentable rather than detectable —
and meaning it and forgetting it are no longer spelled the same way, which is
a sentence Dispatch::knows's own rustdoc had been carrying since before the
decision.
Three things the doing found:
cargo build --workspacedoes not reach two of the sites.spikes/estate/servant.rsandspikes/e2e/servant.rsare copied into a generated crate by their ownrun.sh, so the compiler-error evidence D036 §4 rests on stops at the workspace edge. The second file's own comment had said so: "compiled standalone, outside the workspace … which is exactly how it survived two sweeps." The harness is the only judge for those two.- The two paths diverged because of mutability.
knowstakes&selfandPoa::dispatch_targettakes&mut self, so a servant checking the key on the request path could not ask the same question fromknows.orbweaver_object::Poa::servesis the read-only half, and both spikes call it rather than restating the check — one question, one home, two callers. It deliberately excludesdispatch_target's activation, which is a side effect and cannot be had from&self. - The roster's own guard demanded its retirement, and got it. With every
impl overriding,
verdict()refused: "the finding this test carries is CLOSED — retire the test deliberately and record it. If it is not true the scan broke. Neither reading is allowed to be a silent pass." The guard was obeyed rather than deleted: its two clauses are flipped, so what was an error is now the expectation and a non-empty inheritor list is now the error.default_knows_policy()lost its referent with the defaults and is gone; the fixtures that stateServeAnywaycarry that constant now, beside the answer.
wrong_hook is a subset of inheritors and is therefore vacuous now. It is
asserted anyway and named as vacuous, because the class it described — a
servant stating true while checking the key in dispatch_body — is not
closed by D036 and would need a different scan to hunt again.
D036 승인(선택지 A). 모든 키를 받던 기본값을 두 트레이트에서 지웠고 22개 구현이
이제 자기 답을 말한다. 그 자체로는 아무것도 닫지 않으며 결정서가 그렇게 적는다 —
바뀐 것은 그것이 누락으로는 일어날 수 없다는 것뿐이다. 하는 중에 셋이 드러났다:
cargo build --workspace가 두 자리에 닿지 않는다(스파이크 서번트는 실행 시점에
복사된다); 두 경로가 갈라진 이유는 가변성이었다(knows는 &self, dispatch_target은
&mut self); 그리고 로스터의 가드가 스스로 은퇴를 요구했고 그것을 지우는 대신
방향을 뒤집었다.
D029's lifecycle row named a second floor and did not measure it: Orb::shutdown
says §9.4.10's goodbye, a killed target says nothing, and a caller can tell
those apart. Measured 2026-08-29 by a second arm on the existing fixture pair.
| the fixture does | the peer sees |
|---|---|
Orb::shutdown on the servant's entry signal |
reply to request 1, then CloseConnection |
| SIGKILL at that same instant, servant still held | one reset — no reply, no goodbye |
The measurement is the 2×2, not the two matching runs. Each fixture is also
driven against the other expectation and required to be refuted, because a run
where the peer expects what it gets proves nothing on its own. That is this
project's anti-vacuity rule read backwards: the claim here is that a caller
can tell, so what has to be shown is that the two answers differ. held 6 · refuted-or-broken 0 · unmeasured 0.
What is asserted is the absence of the goodbye, not the presence of a reset. Whether an abruptly dead process is answered with an RST or a FIN depends on what is unread in the receive queue and on the platform; pinning it would be measuring the OS, so it is recorded and not pinned.
The arm was abort() first, and four hand runs left five
spike-orb-shutdown-*.ips crash reports in ~/Library/Logs/ DiagnosticReports. A harness group that files a crash report on every run
trains its reader to ignore crash reports — the opposite of what the previous
day established, when two of them were the only diagnosis available. SIGKILL
leaves none, and is the more faithful model besides: a signal no handler can
catch is what killed means. It is spawned (kill -9 on its own pid) rather
than called, because unsafe_code = "forbid" is a workspace rule.
The row does not move. A caller can tell, which is what a floor means; what changed is that the floor can no longer stop being true without something going red.
D029 생애주기 행이 이름만 붙이고 재지 않던 두 번째 바닥을 2026-08-29에 측정했다.
측정은 맞는 짝 둘이 아니라 2×2다 — 기대를 엇갈리게 건 두 실행이 반증되어야
한다. 피어가 받을 것을 기대하는 실행은 그 자체로 아무것도 증명하지 않기 때문이며,
이는 공허함-방지 규칙을 뒤집어 읽은 것이다: 주장이 구별할 수 있다이므로 보여야
할 것은 두 답이 다르다는 것이다. 고정하는 것은 작별의 부재이지 리셋의 존재가
아니다 — RST냐 FIN이냐를 고정하면 OS를 재게 된다. 처음엔 abort()였고 손으로 네
번 돌린 것이 크래시 리포트 다섯 개를 남겼다: 매 실행마다 크래시 리포트를 쌓는
그룹은 독자에게 크래시 리포트를 무시하도록 가르친다. 행은 움직이지 않고, 바뀐
것은 그 바닥이 조용히 참이 아니게 될 수 없다는 것이다.
spikes/orbexit.py is the one home for how an omniORB fixture leaves: flush,
then os._exit, skipping the Py_Finalize that races omniORB's C++ thread
scavenger. Twenty-three fixtures called it. Four did not, and nothing said
so — the module was adopted by a sweep on the day it was written, and that
sweep's scope became the record of who was covered. Same shape as a sweep is
scoped to a rule, not a file, one layer up: here the rule had a home and the
coverage had none.
On 2026-08-28 one of the four took the crash the home exists to prevent:
Thread 0 __exit <- exit <- dyld4::LibSystemHelpers::exit (finalization)
Thread 1 bind_gilstate_tstate <- _PyThreadState_Attach
<- PyGILState_Ensure <- omnipyThreadScavenger::run_undetached
EXC_BAD_ACCESS (SIGSEGV) at 0x1cd8
It was the -c child carried as a string constant inside native_capture.py,
and the harness reported it as FAIL the omniORB runtime probe did not run — the probe discarded the exit status, so the crash reporter held the
whole diagnosis and the harness held none of it. Third instance that day of a
red run throwing away the half that says why.
Diagnosed rather than suspected: the old child, 12 runs, one −11; with
leave, 40 runs, none. The other three were echo_server.py,
evolution_server.py and matrix_server.py — all three fall off the end of
__main__ after orb.run() returns, which is the finalization path.
spikes/leaves_cleanly.py is the gate, a harness group since the same day —
and its first draft was green over eight more of them. A second report
arrived four hours later, and its differences from the first were the whole
finding: Parent Process: Python where the first said Exited process, and
thread 0 in __cxa_finalize_ranges running omniORB's C++ static destructors,
which os._exit does not reach. Seven fixtures carry a second program as a
string constant and run it with [sys.executable, "-c", …]; eight of those
nine children call ORB_init, and only the one repaired by hand that morning
was leaving cleanly. The gate had asked whether the FILE mentions orbexit,
and every one of those parents does.
A rule about programs, checked against files, is green over every program a file carries. That is a sweep is scoped to a rule, not a file one layer down — written into CLAUDE.md the day before, and repeated by the gate written to close it.
The repair is one home applied at the launch: orbexit.wrap_child(source)
returns the child as a program that leaves cleanly, and all nine launches go
through it. The gate now asks the launch, never the string — every -c
list must hand its program to wrap_child. Its own first rewrite walked the
AST for string constants that parse as Python and found nothing in
union_label_capture.py, whose child is assembled at run time from a template;
the control caught that, by staying green when a spawn site was unwrapped.
Today: 26 programs create an ORB, 26 leave through the home.
Negative controls, both run and shown red: unwrapping one spawn site names
union_label_capture.py — the -c child launched at line 176, exit 1; a
synthesised top-level __probe_leaks.py names itself, exit 1. All five wrapped
capture scripts run clean afterwards.
orbexit.py 는 omniORB 픽스처가 어떻게 나가는가의 유일한 집이다. 23개가 그 집을
불렀고 넷은 아니었으며, 아무것도 그렇게 말하지 않았다 — 모듈이 쓰인 날의 스윕
범위가 곧 누가 덮였는지의 기록이 되었기 때문이다. 2026-08-28에 넷 중 하나가 그
집이 막으려던 크래시를 가져갔고, 하네스는 그것을 *"프로브가 돌지 않았다"*고만
적었다 — 프로브가 종료 상태를 버려서, 진단은 크래시 리포터에만 있었다. 추측이
아니라 진단이다: 옛 형태 12회 중 한 번 −11, leave 적용 후 40회 중 0회.
그리고 그 게이트의 첫 판이 여덟 개를 더 놓치고 있었다. 네 시간 뒤 두 번째
리포트가 왔고, 첫 번째와 다른 점이 곧 발견이었다 — 부모가 Python이고 thread 0이
__cxa_finalize_ranges에 있었다. 일곱 픽스처가 두 번째 프로그램을 문자열로 품고
-c로 돌리며, 그 아홉 자식 중 여덟이 ORB를 만든다. 게이트는 파일이
orbexit을 말하는지 물었고 부모들은 전부 말한다 — 프로그램에 대한 규칙을 파일에
대고 물으면, 파일이 품은 모든 프로그램 위에서 초록이 된다. 수리는 기동 지점에
집 하나를 적용하는 것(wrap_child)이고, 게이트는 이제 문자열이 아니라 기동에
묻는다. 그 재작성의 첫 판도 대조군이 죽였다: 상수를 파싱하는 AST는 실행 시점에
조립되는 자식을 찾지 못했다.
Added 2026-08-27, after a run that could not be explained afterwards. The
machine froze at 15:17:50 KST with the unified log ending mid-second and no
panic report, and the cause could only be named because the kernel happens to
stamp memorystatus_available_pages onto unrelated idle-exit lines: they show
available memory falling 6.42 GB → 0.75 GB in 33 seconds at 15:09 and
sitting between 0.06 and 0.36 GB with the compressor holding 11.8 GB of
16 until the machine stopped. That is a lucky reconstruction, not a
measurement, and it does not survive the next incident.
| Status | |
|---|---|
| the trace | ✅ spikes/memlog.sh record, started before the first group and stopped by the memory group; one line every 5s, appended and flushed, so it survives a machine that dies without unwinding. The previous run's file is moved to .prev rather than deleted — if a run died, that file is the only account of it |
| the gate | ✅ did the kernel kill anything for memory while this run was measuring — a counted FAIL, because a fixture that was shot cannot report that it was, and an unmeasured check is a failure, never a pass. No threshold anywhere: "too little memory left" has no defensible number, so the trace is a report, exactly as entry_cost.py and the CI df steps are |
| its negative controls | ✅ three, run 2026-08-27 — a pressure kill in the window goes FAIL; a parser widened to count idle-exit reaps goes FAIL rather than red-forever (782 such lines in fifty idle minutes here); the two-line probe must report exactly one before the query's silence over the machine means anything. The subject is synthesisable via ORBWEAVER_MEMKILL_SOURCE, and a run that uses it says so above its own verdict |
| in CI | ✅ free -m either side of the harness, the kill query, and the trace kept as an artifact by an if: always() step — a runner that runs out of memory reports no failing step, only The operation was canceled |
| what it does not cover | ◐ a run, not a machine. The freeze above happened while no harness was running, and this recorder would have seen none of it. What exhausted 16 GB that day was ~1,700 node processes from a Vite toolchain in a different repository |
2026-08-27, 설명할 수 없었던 실행 하나 뒤에 추가. 15:17:50 KST에 머신이 멎었고
통합 로그는 초 중간에 끊겼으며 패닉 리포트는 없다. 원인을 이름 붙일 수 있었던
것은 커널이 무관한 idle-exit 줄에 memorystatus_available_pages를 찍어두기 때문
이었다 — 15:09에 가용 메모리가 33초 만에 6.42 GB → 0.75 GB로 떨어졌고, 멎을
때까지 0.06–0.36 GB에 컴프레서가 16 중 11.8 GB를 쥐고 있었다. 그것은 운
좋은 복원이지 측정이 아니며, 다음 사고에서는 통하지 않는다.
그래서 기록기가 첫 그룹 전에 시작해 5초마다 한 줄씩 덧붙이고 즉시 흘려보낸다 —
되감기 없이 죽는 머신에서도 파일이 남도록. 이전 실행의 파일은 지우지 않고 .prev로
옮긴다: 그 실행이 죽었다면 그 파일이 유일한 진술이고, 다음 실행이 증거를 없애는
것이 되어서는 안 된다. 게이트는 다른 문장이다 — 이 실행이 재는 동안 커널이
메모리 때문에 무언가를 죽였는가. 죽였다면 계수되는 FAIL이다: 총 맞은 픽스처는
자기가 맞았다고 보고하지 못하고, 측정되지 않은 검사는 통과가 아니라 실패다.
어디에도 임계값은 없다 — "메모리가 너무 적다"에 방어 가능한 수는 없으므로 추이는
게이트가 아니라 보고이며, 이는 entry_cost.py와 CI의 df 단계가 그런 것과 같은
이유다. 부정 대조군 셋이 2026-08-27에 돌았고, 대상은 ORBWEAVER_MEMKILL_SOURCE로
합성할 수 있으며 그것을 쓴 실행은 자기 판정 위에 그렇다고 크게 적는다.
덮지 못하는 것: 이것은 실행을 덮지 머신을 덮지 않는다. 위의 프리즈는 하네스가
돌지 않을 때 일어났고 이 기록기는 아무것도 보지 못했을 것이다 — 그날 16 GB를
먹은 것은 다른 저장소의 Vite 도구 사슬이 띄운 node 프로세스 약 1,700개였다.
Measured 2026-09-02 → 03: the seam's third implementation, both grids, two SSLIOP peers, and what the harness could not say / 2026-09-02~03 측정
Recorded together because records_keep_up.py went red at 14 commits behind
— which is the gate working, and the reason this section is one section rather
than the batches it covers. Every figure carries its date; recompute rather
than quote.
The seam has three implementations and one protocol document, and the third
was the one nobody had enrolled. the_seam_is_one_protocol.rs's BINDINGS
held one row (python) while its header promised a binding in a third language
adds a function and a row here; theirs() hardcoded python3. Java's runtime
published no protocol document and read its envelope key as get("call"), and
nothing could go red while the binding grid read 8 of 8 — the grid enrols a
binding's cells, the test its protocol, and both were correct. Java is
enrolled now; its 37 protocol-key literals read through named constants; its
document is equal to the ORB's. It was equal only after three pinned
differences were struck by the work that made each false — the pin is exact,
not a floor, and each time the test refused to let a struck line linger.
A Java servant invokes a reference it was handed (D038 option A, all three
implementations): ObjectRef.invoke on a channel installed for one dispatch
and cleared after, the four reply branches lifted into _okOrRaise rather
than copied, the nested read sharing serveOnPipes's one reader. Two controls
that fail differently — minor 0, Maybe (could not invoke) and minor 1, Yes
(invoked, wrong value) — and a third: a reference kept past its dispatch is
refused. And a Java servant can tell which object it was addressed to,
which was a leak and not a gap: the Rust side has put oid in every call
document since homes existed, a Python servant read it through own_oid(),
and a Java one could not read it at all — so it answered every object of its
interface identically, and a caller holding two references could tell the
language from what the servant could do. Found by writing seamProtocol(),
which must state what the file reads.
Both binding grids are complete (2026-09-03): python and java, both
directions, GIOP 1.0/1.1/1.2, neither[] empty, 8 cells run and 0 red each.
Two of the three gaps were not about a language — one cell had a per-version
loop and its twin did not (spikes/lib/giop_versions.sh now, shared), and one
test's loop drove [1.2, 1.1] with no reason recorded for stopping. The
third was an interoperability defect: orbweaver_dynamic::invoke required a
wide codec before marshalling anything, so every call on a GIOP 1.0
connection was refused, ping() included — a peer that speaks only 1.0 could
not be called at all. The servant side had the identical defect and had fixed
it; the client side went on refusing because no cell could reach 1.0 to see
it. One home now: codeset::wide_for_version, both sides call it, neither
restates it, because a caller and a servant are not free to disagree about
what a 1.0 connection carries.
Two SSLIOP peers, both other ORBs' own encoders — the residue D010 B3
named, and a counted SKIPPED retired by building its fixture.
spikes/tls/setup.sh builds omniORBpy with sslTP (the one thing that
stopped configure was pkg-config; Python 3.14 passed);
spikes/jacorb/SslServer.java publishes over JacORB's sun_jsse. omniORB
writes supports=0x0066 requires=0x0066, JacORB writes supports=0x007a requires=0x0060 for the same request — two encoders that share no code, read
by one decoder, and what is asserted is that each peer then does what its
component says: both enforce ESTABLISH_TRUST_IN_CLIENT (CertificateRequired
with no identity), both answer add(7,35)=42 over mutual TLS — omniORB
little-endian, JacORB big-endian. The first dial of each found something:
omniORB's, that our driver could not present a client identity because no
peer of ours had ever asked for one; JacORB's, that its 3.9 KeyStoreUtil
loads from a file only when the type is JKS (PKCS12 silently yields an
empty store — size=0 vs size=1, measured by calling the loader from its
own package). Six counted SKIPPED are five.
The harness records where its time went, because CI took 41 minutes where
the push before took 24 and the runner's flush-time stamps could not name the
group. Its first CI reading named NAT rewriting at 202s against 10s here —
and tracing that found a probe whose header says THIS SCRIPT HAS NEVER BEEN RUN running on every CI push, failing, and reported as SKIPPED — no docker
because the group read the script's skip count where the script prints
which. The group reads the lines now (spikes/lib/nat_probes.sh, six-shape
control), and the next push said why: the in-image rust:1.85-slim build
could not compile the tree — rust-version = "1.85" was false; the tree
uses let-chains, stable in 1.88, and cargo +1.85 build fails with ten
E0658s. It says 1.88 now and a harness group builds a crate with the declared
toolchain. The Dockerfile no longer compiles anything: it copies the
spike-nat the host already built, and a derived .dockerignore stops
COPY . . shipping the runner's target/ into the build daemon.
Two transients, neither diagnosed, both recorded: os error 35 on a GIOP
1.0 ping in CancelRequest (harness 83; 6 standalone passes; second group it
has appeared in), and GIOP 1.1 against JacORB red once in harness 99 (the
run was discarded for a different reason — a standalone re-take beside a live
run shares its fixture ports — and 100 passed).
14 커밋 뒤처져 빨개진 records_keep_up.py가 이 절이 하나인 이유다. seam의 세 번째
구현이 유일하게 등록되지 않은 것이었고, 자바 서번트는 건네받은 참조를 호출하며 자기가
어느 객체로 불렸는지 안다(누출이었다). 두 그리드가 완성되었고, 세 공백 중 하나는
상호운용 결함이었다 — 1.0만 말하는 피어는 아예 호출할 수 없었다. SSLIOP 피어
둘, 다른 ORB의 인코더 둘, 같은 요청에 다른 비트, 각자 자기 컴포넌트대로 행동한다.
하네스가 시간을 기록하자 "한 번도 돌지 않았다"는 탐침이 CI 푸시마다 돌면서
실패하고 no docker SKIPPED로 보고되던 것이 드러났고, 그 실패의 이유는 거짓
MSRV였다 — 1.85라 적혀 있고 1.88이 필요했다.
The wire→bridge spine (cdr→giop→idl→registry→dynamic→mcp→gen) is implemented
and measured. Rewritten 2026-08-25, because this paragraph named as stalled
two things that had moved. The operations layer is no longer uniformly still:
telemetry has been tier 1 for days, the console gained the read half of
administration (services/config/stats), the ORB gained a configuration
surface an operator can reach without a rebuild, and S3i now renders the two
authored SIDL keys — though that last one is a measured null result, since
the frozen benchmark cannot see it. What is still absent is the durable
catalog, the console's write half, and a frozen S3i benchmark to
measure the model-facing stages on at all. Storage and embeddings remain
blocked on dependency decisions (the D001/D002 discipline); the write half
is blocked on a caller model that does not exist, which is a design gap and not
a dependency one; the benchmark is blocked on nothing.
Two habits this pass is worth recording for. A row that quotes a count is a
row that will go stale in silence, because nothing compiles a number: the
trading row read 37 tests for eleven days, the idl row read 19 negative for
a corpus that had grown to 31, and two rows quoted the same AnyJSON crossing at
two different figures because each was taken at a different corpus state. Every
figure here now carries the date it was measured, and the ones this pass did
not re-run say so. And a row can be false by being invisible: two Services
rows carried a fourth cell in a three-column table, so their entire "what is
missing" halves were dropped by every markdown renderer while the source looked
complete — the same defect the console row records for 2026-08-25, found the
same way, by counting delimiters rather than by reading prose.
This file exists so those gaps stay visible instead of being implied by absence.
와이어→브릿지 축은 구현·측정되었다. 2026-08-25 다시 씀 — 이 문단이 이미 움직인
둘을 멈춰 있다고 이름하고 있었기 때문이다. 운영 계층은 더 이상 한결같이 정지해
있지 않다: 관측은 며칠째 tier 1이고, 콘솔은 운영의 읽기 절반(services/config/
stats)을 얻었으며, ORB는 재빌드 없이 운영자가 닿을 수 있는 설정 표면을 얻었고,
S3i는 저자가 쓴 SIDL 키 둘을 그린다 — 다만 마지막 것은 측정된 영(null) 결과다.
얼어붙은 벤치마크가 그것을 볼 수 없기 때문이다. 아직 없는 것은 영속 카탈로그,
콘솔의 쓰기 절반, 그리고 모델 대면 단계를 잴 얼어붙은 S3i 벤치마크다.
저장소·임베딩은 여전히 의존성 결정 문서(D001/D002 규율)에 막혀 있고, 쓰기 절반은
존재하지 않는 호출자 모델에 막혀 있으며(의존성이 아니라 설계의 공백이다), 벤치마크는
아무것에도 막혀 있지 않다.
이번 패스가 남길 습관 둘. 수치를 인용하는 행은 조용히 낡는다 — 숫자를 컴파일하는
것은 없기 때문이다: 트레이딩 행은 열하루 동안 37 tests였고, idl 행은 31개로 자란
음성 코퍼스를 19 negative라 적고 있었으며, 두 행이 같은 AnyJSON 교차를 서로 다른
수치로 인용했다(각각 다른 코퍼스 상태에서 잰 것이다). 이제 모든 수치는 측정 날짜를
달고, 이번에 다시 돌리지 않은 것은 그렇다고 적는다. 그리고 행은 보이지 않음으로써
거짓이 될 수 있다 — 서비스 표의 두 행이 3열 표에서 네 번째 칸을 들고 있어서
"무엇이 없는가" 절반 전체가 모든 마크다운 렌더러에서 사라진 채 원본만 멀쩡해
보였다. 콘솔 행이 기록한 것과 같은 결함이며, 산문을 읽어서가 아니라 구분자를 세어
찾았다.
이 파일은 그 공백이 부재로 암시되는 대신 눈에 보이게 한다.