Repository navigation
MOTHERBOARD-MVP-0: grounded board interfaces with first SPICE and Verilog emission - #8129
Conversation
…ckage authorities, and a SPICE slice projection that refuses unmodelled parts
Establishes the layers of the .dag hardware-compiler program that sit ABOVE geometry, so
nothing here waits on the signed exact-coordinate ruling (std.measure's Millimeter is
Measure<Length, Milli, Nat> — unsigned integer millimetres, so a board-local frame with an
interior origin is not representable today; that decision is deliberately not taken here).
extdeps, every locator fetched live on 2026-08-11 and its facts quoted from the response:
- extdeps.ipc.dpmx — IPC-DPMX (IPC-2581), purpose quoted verbatim. No revision recorded,
because the consortium page states none.
- extdeps.ucamco.gerber — Gerber, publisher and revisions read from Ucamco. Records a
correction: X3 is the newest revision, not X2, and Standard Gerber has been obsolete
since May 2014.
- extdeps.cpu.ampere_altra_package — Altra package magnitude from the public brief
(4926-pin FCLGA, 8 channels DDR4-3200 ECC, up to 16 DIMMs, 128 PCIe Gen4 lanes), and the
typed refusal that the per-contact map, power sequencing and admitted memory topologies
are NDA-only.
The two upstreams carry a shared carriage roster answering one question — which manufacturing
semantics survive a projection — so 'we emitted Gerbers' is mechanically distinguishable from
'the board has been described': connectivity survives IPC-DPMX and does not survive the Gerber
layer projection, proven by execution.
product.compute_board:
- intent — the machine contract as capabilities with quantities, never realizations.
Omitted expansion estates are absent from the type rather than carried as enabled:false.
AuthoredConstraint<T> makes an unsourced constraint value unwritable.
- composition — the earned-element fold. Three refusal families kept as separate types
because their owners differ (upstream collateral / our unfinished design / the selected
factory); readiness is their product, derived, with no settable ready flag.
- spice_projection — a selected slice as a SPICE deck, refusing when an element has no
cited vendor model rather than substituting an ideal part.
Green by execution, with three independent falsifications run and reverted, each redding
exactly its intended witness and nothing else: removing the contact-count join reds only the
population claim; disabling duplicate detection reds only the duplicate claim; dropping the
manufacturing family from the readiness fold reds only the manufacturing control.
DEFECT FOUND AND NOT REPAIRED HERE: v2.extdeps.formats.spice's spice_emit returns an
unwrapped Optional variant where its signature declares String, reproducible on that module's
own constructors with no code from this lane involved. The byte-exact SPICE receipt is
therefore owed, not delivered; this change asserts deck structure and the refusal wall, and
claims nothing about emitted text.
NOT in this change, and named so the gaps stay visible: no geometry, placement or routing; no
Verilog emit rows (the ingest vocabulary exists, the translation rows do not); no
manufacturing artifact emission; no Ampere contact data. The public-repository question for
NDA collateral remains an unresolved operator decision and blocks the Altra path independently
of NDA access.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KSgptzakBpXAereZvbdvVD
…tent Review 51039 raised this and dropped it as "a scaffold for later constraint carriers rather than dead code worth blocking on". Taking the finding rather than the disposition: a new artifact with no final consumer is one of DESIGN §6's named scaffold tells, and §5 is explicit that the default landing state is the final construction — a type carried for a consumer that does not exist yet is future work merged early, and nothing in this change reads it. The carrier is right and will return with its first real consumer, which is the constraint set a route or stackup is admitted against. That work is not in this change, so neither is the type. Re-adding it beside its consumer costs nothing; leaving it here would have made an unconsumed generic look load-bearing to the next reader. All 13 witnesses re-run green after the deletion. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KSgptzakBpXAereZvbdvVD
…ference BOARD collateral is NDA
The previous revision declared the Altra per-contact map, power sequencing and admitted memory
topologies to be NDA-only and refused on that basis. That was false. It was reasoned from the
true fact that Ampere's reference board collateral is under NDA, and never checked against the
datasheet this repository was already citing.
Read from the cited Altra Max Rev A1 datasheet (Issue 1.15, text extracted from the PDF), and
cross-checked against Issue 1.00 on a second host:
- section 3 Mechanical Data, 77.080 mm x 67.000 mm 4926-pin FCLGA
- section 5 "Pin Assignment - Sorted by Pin Number", Table 4, 35 sheets of PIN # to SIGNAL
NAME (A5 VSS, B54 DDR4_DATA_67, AJ81 JTAG_SOC_TDO, AU69 PCIERCA7_RX13_M, ...)
- section 6.1 Reserved Pins, all 39 named by designator
- section 6.2 Table 6 Signal Descriptions with width, direction and I/O type
- Table 5 Pin Summary: 1960 signal + 2927 power/ground + 39 reserved = 4926
- section 7 Electrical Specifications, section 9 Power Supply Sequencing (SoC and PCP)
What is actually NDA-bound is the reference BOARD collateral — the Mt. Jade, Mt. Collins, Mt.
Bonnell and Mt. Snow motherboard design packages plus UEFI, OpenBMC and CPLD source. Board
designs, not package facts.
So the standing splits in two and each lands in the family whose owner can act on it:
ContactMapPubliclyAvailableNotIngested is OUR unfinished ingestion and lands in the design
family; the reference board collateral keeps MissingUpstreamDesignCollateral. Table 5 is
carried as its three parts because it cross-foots, so a transcription that drops a group fails
the sum rather than quietly shrinking the population it claims to cover.
STILL OPEN, and it is not availability: every datasheet page is footed "Ampere Computing
Proprietary", so transcribing its tables into this public repository is a licensing question.
That is unaffected by the pinout being downloadable.
Two reading traps recorded in the module: DDR4_/DDR5_/DDR6_/DDR7_ are CHANNEL indices 4-7, not
memory generations (section 6.2 groups them as DDR[0:7] across eight DDR4 channels, shared rail
VDDQ_DDR4567); and Table 4 excludes depopulated positions, so the completeness authority is
Table 5, not a row count.
12 witnesses green, including both corrected standings asserted together and the pin-summary
cross-foot. The lesson kept in the module note: the old refusal was typed, located, executed on
the real acceptance path and defended by a passing witness — and wrong. Test discipline could
not catch it; reading the document did.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KSgptzakBpXAereZvbdvVD
Correction: the Altra pinout is public — I had this wrongThe PR as opened claimed the Altra per-contact map, power sequencing and admitted memory topologies were NDA-only, and refused on that basis. That was false. Pushed a correction. I reasoned it from the true fact that Ampere's reference board collateral is under NDA, and never checked it against the datasheet this repository was already citing. Read from the cited Altra Max Rev A1 datasheet (Issue 1.15, text extracted from the PDF), cross-checked against Issue 1.00 on a second host:
Actually NDA-bound: the reference board collateral — Mt. Jade / Mt. Collins / Mt. Bonnell / Mt. Snow motherboard design packages, plus UEFI, OpenBMC and CPLD source. Board designs, not package facts. Conflating the two produced the error. What changedThe standing splits, and each half lands in the family whose owner can act on it:
Table 5 is carried as its three parts rather than the total, because it cross-foots — a transcription that drops a group fails the sum instead of quietly shrinking the population it claims to cover. Still open, and it is not availabilityEvery datasheet page is footed "Ampere Computing Proprietary", so transcribing its tables into this public repository is a licensing question. Unaffected by the pinout being downloadable. The public-repo decision therefore survives the correction — with a different reason attached, and a much narrower one. Two reading traps, recorded in the module
Why I'm flagging this loudly rather than quietly fixing itThe old refusal was typed, located, executed on the real acceptance path, and defended by a passing witness — and false. Every mark of a grounded verdict, none of the grounding. No amount of test discipline catches that class; reading the document did. The module note keeps the incident, because the failure mode is more reusable than the fact. 12 witnesses green, including both corrected standings asserted together and the pin-summary cross-foot. — sent from zesty-heron-736 |
…t the SPICE diagnosis to what execution shows
Review 51051 (REQUEST_CHANGES), all three findings taken:
- the datasheet ExternalAuthority was minted a second time here while extdeps.cpu.ampere
already owns that exact URI. Now imported and consumed.
- altra_fclga_4926_declared_contact_count re-authored 4926 beside the CpuSocket row that
already grounds it, so admit_package_contact_map was joining against a denominator free to
drift from the socket fact. The count is now derived: altra_declared_contact_count() reads
altra_max_m12830_catalog.socket.contact_count.
- AltraPackageFamily/AltraFcLga4926 was a parallel package identity beside CpuSocket /
LandGridArray, and the module had no import edge to the CPU catalog it claimed to bound —
the floating-island tell, correctly named. The enum is deleted; PackageContactMap.socket is
a CpuSocket.
Counts move Nat -> Int to join the socket fact without a cross-representation straddle.
SPICE: the diagnosis is corrected for the second time, and both earlier versions are kept in
the note because the sequence is the point. First report blamed spice_emit outright. Second
blamed this lane for building the deck body as a native list literal where the module folds a
Cons/Empty free monoid — that WAS a real bug here and is fixed in this commit — and concluded
the byte receipt was therefore deliverable. It is not. Measured: v2.extdeps.formats.spice's own
golden fixture spice_rc_tran_deck passes both its witnesses untouched, and that same fixture
returns a value string_contains rejects as a Variant when spice_emit is called on it from this
module. Same input, same function, different calling module, different result — a cross-module
seam, which is a more serious finding than either earlier description and is not repaired here.
The witness therefore asserts deck structure, and the byte-exact receipt stays owed. It was
briefly claimed as delivered in a working revision that never merged; it is not claimed now.
15 witnesses green.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KSgptzakBpXAereZvbdvVD
review 51051 — all three findings taken, pushedAll three were correct §3 violations I introduced, two of them in the NDA-correction commit itself.
The floating-island characterisation was exactly right — a pin-map module with no import edge to the CPU catalog it claims to bound, joining its completeness check against a denominator free to drift from the socket fact. There is now a real import edge and one authority for the population. Counts move SPICE: my diagnosis was wrong twice, and review 51047 repeated the first version backWorth flagging directly, because the second approval (review 51047) records my The sequence, all by execution:
Same input, same function, different calling module, different result. So the seam is cross-module, not the deck and not the emitter's logic — materially different from, and more serious than, either earlier description. Not repaired here; it wants its own change with someone who knows the resolution path. The witness asserts deck structure. The byte-exact receipt stays owed — it was briefly claimed as delivered in a working revision that never merged, and it is not claimed now. 15 witnesses green. — sent from zesty-heron-736 |
… ideal part Side-channel review found the defect and it is real: admission checked only that a model card was PRESENT, and every admitted element then emitted as an ideal lumped passive regardless. So the module performed the exact substitution it was written to forbid — citing a vendor model and shipping "C1 n1 0 100n", which has no ESR, no ESL and no bias or temperature dependence — while the type said a vendor model stood behind it. The absent-model wall was real; the positive arm quietly downgraded. Fidelity is now explicit and the unemittable arm refuses. ModelFidelity is IdealLumped or VendorSubcircuit; a vendor subcircuit would have to travel as a .SUBCKT or .MODEL binding, this projection emits neither, so such an element yields ModelFidelityNotEmittable naming the part and the model card rather than being silently flattened. A smaller capability honestly bounded instead of a larger one asserted. Also from the same review: the witness cited Ucamco's Gerber page as the electrical-model authority for a passive — a real page about an unrelated subject, which is a fixture citation that reads as grounded and is not. Replaced with a deliberately unresolvable synthetic locator that says what it is. New witnesses assert the three-way discrimination together — absent model, unemittable fidelity, emittable fidelity must land on three different outcomes — because any two collapsing is the defect, and a witness on one arm alone would have passed under the old model too. 17 witnesses green. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KSgptzakBpXAereZvbdvVD
…ral its own standing type Two defects from the side-channel review, both real, both mine. QUANTITY ERASURE. Satisfaction matched a component's justification tag against a requirement's capability tag and never read the requirement's quantity, so a one-core part justified as Compute satisfied a 128-core requirement. The intent module's own note says a requirement is the capability PLUS the quantity that makes it checkable, and the fold discarded exactly that — the contract contradicted its stated reason for existing, and every witness passed because they all asserted at tag grain too. CapabilityProvision is now separate from ComponentJustification, because WHY a component is earned and WHAT it supplies are different questions and a component can get one right while getting the other wrong. Numeric requirements sum provisions and compare; boolean-shaped ones demand every declared sub-facility, so a management device with power control and console but no recovery path no longer satisfies a contract that asked for recovery. The discriminating pair is 127 versus 128 cores — same justification, same part label, one core apart. Falsified: restoring the tag-matching arm reds that witness and nothing else. STANDING FUSION. altra_reference_board_collateral_standing returned MissingUpstreamDesignCollateral AS a PackageContactMapStanding, so reference-board absence rendered as a statement about the package contact map. That is the same fusion this lane corrected in prose two commits ago and then re-committed at the type level one function down. ReferenceBoardCollateralStanding is now its own type projecting into the collateral family by its own route; neither subject can inhabit the other's type. The Rev A1 datasheet also joins the module's ExternalModelScope further_citations. It is the authority for the corrected pin, signal and sequencing facts, and leaving it out left those facts grounded in prose while the scope cited only the brief and the NDA portal. Already closed in the previous commit, noted since the same review raised it: the SPICE model wall no longer admits a cited vendor model and emits an ideal part. Not taken: the manufacturing-medium grain finding. Gerber layer data, job metadata and drill already travel as distinct CarriedInCompanionArtifact rows rather than one yes/no roster, so the split the review asks for is present at carriage grain. A finer artifact-set model is real future work but is not a defect in what is here. 19 witnesses green. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KSgptzakBpXAereZvbdvVD
Four side-channel findings: three taken, one declined with reasons1. Quantity erasure in the satisfaction fold — taken, and it was the worst of the setSatisfaction matched a component's justification tag against a requirement's capability tag, and never read the requirement's quantity. A one-core part justified as What makes this worse than an ordinary bug:
Discriminating pair: 127 vs 128 cores — same justification, same part label, one core apart. Falsified — restoring the tag-matching arm reds that witness and nothing else. 2. Reference-board collateral fused into the package standing — taken
That is the same fusion I corrected in prose two commits ago and then re-committed at the type level one function down. The Rev A1 datasheet also joins the module's 3. SPICE model wall — already closed in the prior commit
4. Manufacturing-medium grain — declined, with reasonsGerber layer data, job metadata and drill already travel as distinct 19 witnesses green. — sent from zesty-heron-736 |
…at/Int Review 51062, hard blocker, correct. A core count IS a hardware thread count and std.measure.HardwareThreadCount already exists for it — extdeps.cpu.types CpuModelCatalogRow types its threads field with it. Typing the requirement as bare Nat and the provision as bare Int was the parallel representation DESIGN §3 forbids, with no marker to justify a raw scalar. The paired type-safety consequence was real too: ProvidesCores.cores: Int could represent -5, and the sum then applied that against a Nat minimum, so a negative provision could reduce a board's apparent core count below what its other components supply. Both sides of the comparison now carry HardwareThreadCount and the fold runs in Nat. Network and boot-storage paths move Int -> Nat on the same reasoning at smaller scale: there is no negative number of paths. The review did not raise these; the argument that condemned the core field condemns them identically, and fixing only the flagged instance would leave the class half-closed. 19 witnesses green. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KSgptzakBpXAereZvbdvVD
review 51062 — hard blocker taken, third finding declined with reasons1 & 2.
|
… blocker reaches the answer Review 51066, correct and the sharpest finding on this PR. The previous commit split ReferenceBoardCollateralStanding into its own type and gave it a projection, then never composed that projection into board_readiness — the fold the module's own comment calls the single "may this be ordered" answer. So the Ampere NDA blocker rendered as READY through the only consumer surface, while the refusal sat beside it correctly typed and unreachable. That is worse than not having modelled it: the types then advertise coverage the fold does not deliver. The witness that should have caught it called project_reference_board_standing DIRECTLY and passed, proving the projection rather than its reachability. That is the defect in miniature — a claim about a fold has to travel the fold, and one asserted against a helper tests a function the product may never call. The replacement asserts through board_readiness and pins both directions: under-NDA yields exactly one collateral refusal and blocks readiness, available yields none. board_readiness now takes the reference-board standing as a fourth argument, so a future standing cannot be added as an unreached sibling — it has no way into the type without a call site. 19 witnesses green. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KSgptzakBpXAereZvbdvVD
… the byte-exact SPICE receipt
THE ROOT, after three wrong diagnoses that each grew more confident:
1. "spice_emit returns an unwrapped Optional where its signature declares String."
2. "No — this lane passed a native list literal where the module folds a Cons/Empty free
monoid." That WAS a real bug here and is fixed, but it was not the cause.
3. "The seam is cross-module: the module's own fixture emits correctly inside its own test and
returns a Variant when called from another module." Also wrong.
The actual cause is two lines: spice_text_concat was defined as list_append — v2.std.algebra's
FREE MONOID append, signature FreeMonoid<T> x FreeMonoid<T> -> FreeMonoid<T> — applied to
String. A String is not a free monoid in the realization, so every concatenated result came
back a Cons/Empty structure that string_contains correctly refused as a Variant. The builtin
concat was available the whole time. Measured, not reasoned: list_append(left: "a", right: "b")
returns a Variant while concat("a", "b") returns a String, and spice_ident_spelling — the one
helper that concatenates nothing — was the single probe that passed throughout.
Fixed at the root in v2.extdeps.formats.spice. That module's own ngspice goldens still pass
untouched, so the repair is behaviour-preserving where it already worked and behaviour-restoring
where it did not. A corpus sweep found no other list_append site applied to a String; every
other call operates on genuine lists, so the misuse was confined to these text helpers.
CONSEQUENCE — merge-bar item 7 delivered rather than owed: the board article now emits real
SPICE bytes, witnessed from OUTSIDE the spice module, including a contiguous byte run that pins
source-before-elements ordering, which a contains-only assertion cannot see.
Review 51068's finding is resolved by the same change rather than by marking the exported reader
unusable: projected_deck_text now returns a deck string on the projected arm.
21 witnesses green.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KSgptzakBpXAereZvbdvVD
Merge-bar items 8, 9 and 10. The board's interlock is expressed as signals and equations that name no target language, and a projection derives the Verilog. NO body_lexeme, and not by discipline: DigitalControlIntent has no constructor for a target string. An equation is a tree over signal references and three boolean operators, so "assign power_permit = conditioned_good & ~fault;" cannot be authored upstream — it can only be derived. Every Verilog token for this article appears in exactly one module, the projection. Signal identity is a branded SignalName rather than a substrate Symbol, and that is a departure from the sibling SPICE module on purpose. v2.extdeps.formats.spice carries a hand-written Symbol-to-spelling table covering exactly r1/c1/v1/n1/n2, because the substrate has no general projection from a Symbol to its text — so anything it emits is bounded by a five-entry lookup. A branded identifier carries its own spelling and needs no table. This is also why the profile does not yet route through v2.extdeps.languages.verilog's VerilogModule AST: that AST names every construct with a Symbol, so binding onto it is blocked on the same missing projection, not on appetite. Named as the end state rather than quietly skipped. Sequential behaviour is unrepresentable rather than refused at emission: there is no clocked or event-controlled constructor in the intent, so an always block cannot be requested and then silently rendered as combinational logic. The refusals that DO exist cover what the intent can still express wrongly — driving a declared input, and referencing a signal never declared. Verilog would accept the latter as an implicit wire, which is precisely why the model must not. Expressions are fully parenthesised rather than leaning on Verilog's precedence table, because a projection that depends on precedence is one edit from emitting a different circuit than the tree says. Witnessed byte-exactly against the whole module as one contiguous string — a substring probe passes on wrong port order, duplicated assigns, or a missing endmodule. The differential control flips fault polarity in the ARTICLE and asserts the emitted bytes change, so the emission cannot be decorative. 26 witnesses green. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KSgptzakBpXAereZvbdvVD
…hygiene walk CI refusal on 7e8c534, and the diagnostic was exact: dag/test/claim/ compute_board_spice_projection_witness_test.dag::list_count was a plain fn unreachable from every test fn — silent de-enrollment. It went orphaned when the byte-exact emission test replaced the structure-count test that had been its only caller. WHY MY LOCAL LOOP DID NOT CATCH IT, since that is the reusable part: every local run in this session invoked claim_batch with an explicit --functions list, which executes exactly the named witnesses and never performs the pre-plan naming walk. So the local signal was structurally incapable of reporting this class no matter how many times it went green — the same shape as a witness that asserts against its own model. The tree-wide discovery path (--roster-from-discovery --scan-dir) is what runs the walk. Swept the other two witness modules in this PR for the same class; no further orphans. 25 witnesses green. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KSgptzakBpXAereZvbdvVD
…e firmware, physical and manufacturing boundaries Merge-bar items 1, 4, 5 and 6. ITEM 1 — one article identity. BoardPowerInterfaceArticle holds the analog slice, the digital control, the bonding topology, the firmware contract and the physical and manufacturing standings as fields of ONE root, and both projections are handed a field of it rather than a fixture of their own. The witness derives SPICE bytes and Verilog bytes from the same value in a single claim. That property decays silently otherwise: two projections each holding a private copy agree until someone edits one, and nothing reports the divergence because there was never a shared referent to disagree with. ITEM 4 — electrical references are not one Ground. Power return, signal reference, chassis bond, protective earth and shield termination are five distinct relations with different currents, different safety consequences and different physical realizations, routinely drawn with the same triangle and discovered to be different during bring-up or after an incident. The discriminating claim is that a power return and a signal reference sharing the same physical terminal stay distinguishable — exactly what a single Ground value destroys. The mounting-hole case is stated explicitly: a standoff bonds board to chassis only when a declared relation says so, because mechanical support and electrical bonding are separate facts about the same hole and inferring the second from the first decides a safety property by accident. ITEM 5 — the firmware contract states its unknowns rather than defaulting. There is no "signed" arm at all, so the state that would claim production signing has no constructor rather than an unset field a later reader takes for true; write protection is declared unmodelled rather than defaulted to protected. Both are asserted by witness, because a contract that stayed silent on either would read as safe. Board-level start preconditions are kept distinct from what firmware then does: a released reset line must never imply a booting machine. ITEM 6 — the physical and manufacturing boundaries EXIST and both refuse with named causes. The physical cause names std.measure's unsigned integer millimetres explicitly, so the blocker is addressable rather than folklore. An omitted interface would have read as "not needed". The local orphan sweep ran BEFORE this push rather than after CI reported it, and caught three predicates whose only use was by-reference. They are now called at unambiguous sites rather than left to depend on how the gate's reachability treats a bare function reference. 29 witnesses green. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KSgptzakBpXAereZvbdvVD
…mits The board article held a BoardSlice imported FROM the SPICE projection: the supposed authority was shaped by one of its own emission targets, so "the article feeds every interface" was true only because one interface had already decided what the article looked like. product.compute_board.analog now carries the language-neutral electrical intent — component instances, terminals, nets and typed magnitudes on std.measure's existing Resistance/Capacitance/ElectricPotentialDifference quantities. '10k' and '100n' are gone from the model; the SPICE projection derives the suffix, because a scale suffix is target syntax. Terminals and nets are identities rather than prose, so "is this terminal on that net" is a decidable join instead of a spelling coincidence between two independently authored strings. The model-standing carriers moved with it. ModelFidelity, ElectricalModelStanding and ComponentModelBinding lived in the SPICE projection, which made the article import a type from a target to declare its own field — the same inversion surviving in the half nobody looked at. Nothing in them names a deck or an element letter: a fidelity is a claim about how faithfully a model represents a part, and an IBIS or Verilog-AMS projection needs the same fact. What stays in the projection is which fidelities that target can render. Green by execution: 22/22 witnesses across the article, SPICE and Verilog claims, including full-byte-equality decks and the refusal controls. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…es linear Nothing in the product joined an article to an emission. The only place it happened was a witness that hand-paired article_identity_label(article) with article.analog, so "one article feeds both emissions" was a convention at a call site rather than a property of the types — and every caller supplied the identity and the circuit as SEPARATE arguments, which made one article's identity paired with another article's circuit perfectly constructible. The receipt would then name a board it does not describe. project_article_to_spice and project_article_to_verilog take the whole article, so both facts are read out of one value. The receipts carry BoardArticleIdentity rather than a flattened "name rev N" label: the label made the join a string comparison over a spelling this repository happened to choose, and let a caller pass any string at all where the source article belonged. article_identity_eq compares both fields, because two revisions of one board share a name and that is exactly when a stale emission is least visible. first_duplicate_designator re-counted the whole list per element — 4926 x 4926 comparisons at the real package population. Two linear passes replace it. The maps carry membership only and are never iterated, so the answer's ORDER still comes from the designator list, which preserves the exact prior semantics: the first designator in source order whose value occurs more than once. The witness pinning A1 stays green. Green by execution: 36/36 witnesses across the article, SPICE, Verilog and board-readiness claims. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
#8129 landed `Memory` as a `BoardCapability` variant and `unsatisfied_capabilities` in `product.compute_board.composition`. Both names already existed corpus-wide — `std.measure` `Memory` and `gunbc.auth.github_credential` `unsatisfied_capabilities` — so under namespace-only resolution the bare references stopped resolving uniquely and the whole-tree compile went red in four files: dag/gunbc/ci_budget_tree.dag unresolved type 'Memory' dag/product/budget_tree.dag unresolved type 'Memory' dag/test/claim/budget_tree_witness_test.dag dag/test/claim/github_app_registry_witness_test.dag 'unsatisfied_capabilities' not found It was invisible on main because every push run there was cancelled, so no floor run completed on the merge commit. The newer, narrower names move: `Memory` -> `MemoryCapability`, `unsatisfied_capabilities` -> `unsatisfied_board_capabilities`. The long-standing authorities keep their names. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
main has been red since #8129 and every branch that merges it inherits the red. Two failures, one root cause, and the cause is NOT where it looks. `unresolved type 'Memory'` fired in dag/product/budget_tree.dag, dag/gunbc/ci_budget_tree.dag and dag/test/claim/budget_tree_witness_test.dag -- files dating to #6165 that were compiling. The obvious suspect was #8091, which added 213 lines to dag/std/measure.dag where `Memory` lives. That was a red herring: measure.dag's `Memory` lines are untouched. #8129 added a SECOND declaration spelled `Memory` -- a variant of BoardCapability in dag/product/compute_board/intent.dag -- and adding a name to the flat namespace is a corpus-wide edit, so `Memory` became ambiguous and every module importing it from std.measure as a type stopped resolving. The same PR added `unsatisfied_capabilities` to dag/product/compute_board/composition.dag, where gunbc.auth.github_credential already declared one, which is the second failure. THE NEWER NAME YIELDS, per the single-authority rule: `Quantity.Memory` in std.measure is the older and broader authority with consumers across the tree, and `unsatisfied_capabilities` in the auth module has five declaration sites and two consumers in heal_push_plan. So BoardCapability.Memory becomes MemorySubsystem -- accurate on an axis whose sibling constructor is already MemoryCapacity -- and the board fold becomes unsatisfied_board_capabilities. Nine call sites across four files; no behaviour changes and no emitted seed file mentions either name. Green by execution: compute_board_readiness, github_app_registry and budget_tree witnesses all pass here and all three fail on the base. WHAT THIS DOES NOT DO, stated so the repair is not mistaken for the wall. There is no mechanism preventing the next PR from doing exactly this again: the class is a flat-namespace homonym, which is decidable and therefore a construction wall waiting on the namespace lane's containment authority, not on diligence. Until that lands, this class recurs whenever two lanes add the same bare name, and it will keep arriving as an unrelated-looking build break in a third file. Co-authored-by: gunbc-ci-auto-heal <gunbc-ci-auto-heal@users.noreply.github.com> Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Main landed #8151, which fixes the same two #8129 flat-namespace collisions my 3ea09c1 fixed. Both conflicts are that duplicate, and both resolve to main's side: - intent.dag: main chose MemorySubsystem, I had chosen MemoryCapability. Main's landed, so mine dissolves. - compute_board_readiness_witness_test.dag: same rename, following. composition.dag needed no resolution — both sides independently picked unsatisfied_board_capabilities. My 3ea09c1 is therefore now redundant work; what survives from this branch is PRESS-0 step 1 and the claim_executor budget-diagnostic ordering, both verified present after the auto-merge. The four generated artifacts main touched (stage0 crate partition, stage0 emit plan, ci.yml, ci_spec) all resolved to main's side byte-for-byte. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
target_text_seq concatenates exactly, so every space and newline in an emitted
module is declared in verilog_emit_layout_transforms or does not exist. None of
it may return to product.compute_board, which is the point of the exercise.
The header and expression parentheses are separate token classes because a
transform is keyed by token class, and one paren class cannot render " (\n"
around a port list and "(" inside an expression. The #8129 oracle needs both.
Splitting the class is what makes the layout expressible; the two classes lex
the same literal, which is a REPARSE question rather than an emission one and
is tracked as such.
Hand-assembling the profile against the oracle reproduces it exactly, including
the parenthesised binary expressions and the two-space assign indent. That is
arithmetic on paper, not an executed emission; the emission is next.
Compiles at the 33-diagnostic baseline.
…n merge CI on press-0 failed with `unresolved type 'ArtifactIdentity'` in three files this branch never touched — realize_kernel_test, hermetic_fixture_realization_test, reconcile_in_process_cache_test. The cause is a §3 name collision, the same class as #8129's `Memory`. `dag/std/cache_interface.dag` has carried `ArtifactIdentity<T>` for a long time; #8153 added a bare `ArtifactIdentity` in `src/v2/compiler/self_host/generation.dag`. Under namespace-only resolution the long-standing bare references stopped resolving uniquely, so the consumers of the ORIGINAL type broke while the new declaration compiled fine. main still carries it and no open PR is fixing it, so this is not a duplicate of someone else's repair — I checked first, having duplicated #8151 earlier today. The newer, narrower name moves: `SelfHostArtifactIdentity`. The cache-interface authority keeps its name and its consumers resolve again. Swept its three files together — the declaration, both self_host witnesses, and the field and function signatures that referenced it. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…n merge CI on press-0 failed with `unresolved type 'ArtifactIdentity'` in three files this branch never touched — realize_kernel_test, hermetic_fixture_realization_test, reconcile_in_process_cache_test. The cause is a §3 name collision, the same class as #8129's `Memory`. `dag/std/cache_interface.dag` has carried `ArtifactIdentity<T>` for a long time; #8153 added a bare `ArtifactIdentity` in `src/v2/compiler/self_host/generation.dag`. Under namespace-only resolution the long-standing bare references stopped resolving uniquely, so the consumers of the ORIGINAL type broke while the new declaration compiled fine. main still carries it and no open PR is fixing it, so this is not a duplicate of someone else's repair — I checked first, having duplicated #8151 earlier today. The newer, narrower name moves: `SelfHostArtifactIdentity`. The cache-interface authority keeps its name and its consumers resolve again. Swept its three files together — the declaration, both self_host witnesses, and the field and function signatures that referenced it. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
main is broken, not this branch. #8176 added dag/gunbc/self_host_artifact_materialization.dag importing ArtifactIdentity from v2.compiler.self_host.generation; #8177 then renamed that declaration to GeneratedArtifactIdentity without updating this consumer. My copy of the file is byte-identical to main's, so the compile-clean failure here is inherited rather than introduced. Swept the import, the return type, and both witness files together. THIS IS THE THIRD MAIN BREAKAGE OF THIS CLASS TODAY — Memory (#8129), ArtifactIdentity (#8153), and now the incomplete repair of that same rename. Two were new bare names colliding with existing ones, and this one is a rename that moved a declaration without its consumers. All three share a mechanism: a change is checked against the module it edits, while the breakage appears in modules it does not. Under namespace-only resolution that is mechanically decidable from the containment tree — a symbol that resolved before an edit and does not resolve after it is a whole-tree fact a lens can compute. Worth a lens rather than a fourth repair. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
… fixture echo OUTCOME A: the generic target path composes a bounded Verilog module with no Verilog branch in the compiler. The module frame, the recursive port list, the nested expressions and every space and newline derive from the target's productions and layout profile. TWO MECHANISM FACTS COST MOST OF THE WORK, both settled by execution. A bound terminal only takes its binding from the emitted slot when its binding symbol is ^grammar_free_name_slot or ^grammar_lexeme_stamp_slot — that is what formal_binding_is_emitted_atom_from_slot tests. Custom binding symbols take the ExactBinding arm, which expects no emitted slot, so every production silently failed to match its own exemplar and derivation answered grammar_relation_production_not_found. grammar_formal_terminal_free is the constructor that means what this needed. AND THE FIRST GREEN WAS A FIXTURE ECHO. Row derivation walks the emitted tree and INLINES every nested nonterminal's tokens, so a derived row already carries the whole program's token sequence and serialization replays it. Passing a DIFFERENT module to a model holding the exemplar's row re-emitted the exemplar: byte-exact, entirely insensitive to its input. The perturbation control is what caught it — the positive control alone was green throughout and would have shipped a fixture as a compiler. So emission derives its row from the program being emitted. The rules population is not a fixed table that programs select from; composition happens at derivation, and that is the path all six witnesses now exercise. Controls, 6/6 green by execution: full byte equality against the exact #8129 oracle; every relation row derives, so the empty-row fallback is proven unreachable rather than trusted; dropping the negation changes the expression bytes and leaves the port region byte-identical; a two-port module emits through the same productions with the assignment region intact; and deriving against the REVERSED production population is byte-identical, so selection is structural rather than first-match. That last control replaced one asserting a production count, which proves nothing about order and was a literal measured from the tree it checked.
…ssions (#8132) * PRESS-0 step 3: dispatch admission asks whether a PROCESS is running, not whether a tmux name exists A retained remain-on-exit pane — the exact artifact the spawn path creates — answered "live" to belt_node_session_live, so a click returned DispatchAlreadyLive through the accepted band: a positive acknowledgement, no agent, and no refusal anywhere to count. That is worse than the selection refusal beside it, because an accepted no-op has zero observable frequency by construction (DESIGN §5, the absorbing fallback wearing an idempotence label). No new observer was built. The pane probe, the pane-to-evidence projection (worker_process_from_attempt_pane) and the per-session join (worker_process_for_session) already existed and already carried the refusal semantics, and the sessions UI route was already using them via belt_sessions_observe_for_instance. The defect was that the two ACTUATION sites — belt_tick_for_instance and belt_dispatch_node_for_instance — read the ls-only observation whose worker evidence is the placeholder whose own reason reads "tmux name presence does not observe pane liveness or exit". One concept had two answers in one module (§3); this routes both actuation sites through the answer that observes. Liveness is four-valued, not Boolean: Running may suppress a spawn; StaleTerminal is refused and never suppresses the new turn; LivenessUnobserved refuses rather than spawning blind, because a spawn under unknown liveness is how one node acquires two workers; Absent is the ordinary spawn path. Collapsing Unobserved into either pole is the state-space conflation that produced this defect one level down. Witnesses: the prior witness_node_session_live_true asserted exactly the behaviour that is now wrong, so it is replaced rather than kept beside the new arms. The discriminating red is witness_retained_dead_pane_is_not_live, which returned true under the old check. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * Fix: render the exit code with to_string, the idiom this module and its sibling already use int_to_string exists only in src/v1/02_parse.dag, takes value: rather than n:, and no module under dag/gunbc/ calls it — the sole occurrence of int_to_string(n:) in that directory was the line this fixes. dispatch_process_cleanup_separation_note's own retained-pane message renders the same field as to_string(exit_code). Also restructures the liveness filter to the let-then-first form worker_process_for_session uses, rather than an inline parenthesized pipe-then-method that appears nowhere in the corpus. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * Admit the two new dispatch arms into the wire-roster size pin dispatch_button_terminal_states maps over belt_dispatch_all_status_labels, so button totality and the terminal count both follow the roster by derivation and needed no edit. What failed is the third clause of witness_dispatch_button_states_total_over_wire: the absolute roster size, pinned so a new arm cannot enter the wire vocabulary without a conscious update to the surface that renders it. The subject genuinely grew by two (stale_session, session_liveness_unobserved), so the pin moves 8 -> 10. The pin is doing exactly its job here and is not being loosened: it is a controlled fixture over a hand-authored closed roster, and it stopped a wire-contract change from landing silently. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * Announce a population budget refusal before the work that can swallow it A budget exhaustion killed the CI floor and said nothing. Run 31474198106 on PR #8132 ran the floor step for exactly 55m01s against gunbc_ci_ordinary_floor_budget_minutes = 55, exited 1, and emitted no OVER-BUDGET line anywhere in the attempt log. The step's own output stops mid-measurement three minutes before the process dies. The wall itself is correct and is not being weakened here — only its announcement is. A fail-closed mechanism whose diagnostic is missing is worse than a loud one: the refusal still fires, but readers cannot see why, so they reach for whatever number is nearby. In this incident that was a cgroup peak RSS reading sitting next to the silence, and the failure was diagnosed as a memory kill that never happened — by me, in the PR thread, before the operator corrected it. That is the cost being paid: the silence does not just withhold a cause, it manufactures a wrong one. TWO CANDIDATE LOSS MECHANISMS, both closed by this ordering without having to decide between them. The receipt write ran first and its path was interpolated into the message, so a stalled write under a loaded runner delays the only announcement past process death. And the message took an explicit stderr lock guard held across the write — a lock the main thread holds while streaming its own receipt phases, which is precisely what the floor was doing in the window this fired in. A watchdog must never wait on a resource held by the thread it polices. So the announcement now goes first: a ::error:: annotation on stdout so the cause reaches the run summary rather than only line ~7000 of a step log, the detail line on stderr, both flushed, and the receipt write afterwards where failing to write it can no longer suppress the diagnosis. NOT ESTABLISHED, stated so the next reader does not inherit a guess: which of the two mechanisms actually lost the message. Neither was reproduced. The fix is ordering that makes both harmless, not a repair of a located defect. Also merges origin/main (d3203de). Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * Heal main's two §3 name collisions from #8129 (compute_board) #8129 landed `Memory` as a `BoardCapability` variant and `unsatisfied_capabilities` in `product.compute_board.composition`. Both names already existed corpus-wide — `std.measure` `Memory` and `gunbc.auth.github_credential` `unsatisfied_capabilities` — so under namespace-only resolution the bare references stopped resolving uniquely and the whole-tree compile went red in four files: dag/gunbc/ci_budget_tree.dag unresolved type 'Memory' dag/product/budget_tree.dag unresolved type 'Memory' dag/test/claim/budget_tree_witness_test.dag dag/test/claim/github_app_registry_witness_test.dag 'unsatisfied_capabilities' not found It was invisible on main because every push run there was cancelled, so no floor run completed on the merge commit. The newer, narrower names move: `Memory` -> `MemoryCapability`, `unsatisfied_capabilities` -> `unsatisfied_board_capabilities`. The long-standing authorities keep their names. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * PRESS-0 step 1: dispatch reads back the deployed revision before it acts gunbc.fleet_desired_observe fleet_revision_standing already decided the revision cell three ways, and its only consumer was gunbc.fleet_converge_cli. So convergence knew srv1 was drifted while the served page dispatched against the drifted tree anyway and said nothing — a worker spawned from code the fleet never admitted, reported as an ordinary spawn. The standing is now consulted FIRST in belt_dispatch_node_for_instance, before node lookup and before session observation, because the roster, the signoff and the command all come from that tree; refusing later would mean refusing after acting on it. Both non-converged arms refuse: DispatchRevisionDrifted carries BOTH revisions (the membership-diff `from` ruling — a catch-up needs the prior) and DispatchRevisionUnobserved carries its cause. Neither reaches the ok band. Serve maps drift to 409 and unobserved to 503. The repo path is instance.repo_root, not the srv1_gunbc_repo_root constant converge uses — hardcoding srv1 would mint a second path authority and answer for the wrong host on any other instance. SCOPE: the new witnesses are projection-grain — never-ok, loud band, drift names both revisions, unobserved names its cause. Whether the gate FIRES on a live drifted srv1 is a wet fact and is owed a wet receipt; it is not established here. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * Extend the band-fold vocabulary pin to the four new dispatch labels Run 31528394784 red batch 4: witness_band_fold_pins_operator_vocabulary pinned belt_dispatch_label_band_rows().length() == 8, and dashboard_instance_dispatch_contract_keystone_holds failed only as its conjunct. The four arms added by step 3 (stale_session, session_liveness_unobserved) and step 1 (deployed_revision_drifted, deployed_revision_unobserved) each get their band assertion; all four are loud, so the ok set is unchanged and witness_ok_labels_derive_from_band_fold still holds. This is the second roster I missed after adding arms — I fixed d4_wire_contract_labels and the button wire pin but not this one. Swept the class this time: the only other pin over these rosters is roadmap_sandbox_witness_test line 75, which is relative (all_status_labels().length() - 1) and self-adjusts. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * Extract the revision-admission fold, refuse duplicate sessions, delete the Boolean collapse Handback items 2, 3 and 5 from review artifact 51237. EXTRACT THE GATE (item 4). The revision check lived inline in dispatch, so every witness constructed DispatchRevisionDrifted directly and asserted its label, band and JSON — which would stay green if the fleet_revision_standing match were deleted outright. A test that survives removal of the thing it tests is not testing it. belt_revision_admission is now a pure FleetRevisionStanding -> BeltRevisionAdmission fold with the effectful read left at the call site, driven through all three arms by witness. It is also the shared seam the tick path will consume. 0/1/MANY (item 3). belt_node_session_liveness filtered by node id and took first(), so two sessions claiming one node resolved to whichever tmux listed first: [running, stale] answered running, [stale, running] answered stale. Same observation, two orders, two verdicts, no signal a choice was made. It now refuses with BeltSessionMultiplicityConflict naming every session, routed to a new dispatch arm, loud band, 409. The discriminating witness asserts order-independence directly. DELETE THE BOOLEAN (item 5). belt_node_session_live answered Bool over a four-state lifecycle, mapping StaleTerminal, LivenessUnobserved and Absent all to false though the module's own note says those three have different remedies. It had no production caller. Its four witnesses are re-pointed at belt_node_session_liveness and are STRONGER for it: each now asserts the exact arm rather than mere falsity. Rosters swept together — exemplars, d4_wire_contract_labels, the band-fold pin and the button wire pin all read 13. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * Make the scheduled belt tick lifecycle-aware and revision-gated Handback P0s 1 and 2 from review artifact 51237: the dead-pane fix and the revision gate reached the click path only, and the timer is a dispatch actuator exactly as much as the button is. The tick passed the whole observed session list into belt_reconcile, which maps EVERY present row to an observed member and counts live.length() as occupied. So a retained dead pane stayed a member (desired + present = Unchanged, no respawn) AND consumed a capacity slot. It also never consulted the deployed revision, so a drifted host refused a click while spawning on a timer. Both paths now consume belt_revision_admission. A refused revision blocks spawn and reap ONLY — verification and publication continue, because they read independently bound receipts and already run when tmux observation refuses; stopping them would widen a spawn-admission refusal into a work stoppage. Cleanup precedes start, deferred one tick. A stale pane leaves observed membership, so its node becomes a spawn candidate — but spawning it this tick would collide with the tmux session name that still exists, because spawn runs before teardown. So the stale node's spawn is withheld and its teardown planned; the next tick observes it Absent and spawns normally. That is why this is a partition plus a deferral, not a filter. LivenessUnobserved and MultiplicityConflict are neither cleaned nor spawned. An unobserved pane might be running, so teardown could kill live work and spawning beside it could double-run the node. Witnessed at the belt grain rather than on a helper Boolean: the partition the tick feeds to belt_reconcile puts running, stale and unobserved in three different buckets. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * Fix the witness import block I split in the wrong place (review 51277) CONFIRMED, and it was the worst possible failure for this PR. My edit inserted the fleet_desired_observe import in the MIDDLE of the roadmap_belt_actuate import list, closing that block one line early. So DispatchRevisionDrifted, DispatchRevisionUnobserved, belt_dispatch_result_ok, belt_dispatch_status_label, belt_dispatch_result_json_value, the label/band/exemplar roster accessors and the preflight symbols all sat inside `import gunbc.fleet_desired_observe { … }` while being defined in roadmap_belt_actuate.dag. The module could not resolve its imports, so NONE of the new evidence would have run — the revision-admission fold, the multiplicity conflict, the tick partition, all of it. A PR whose entire claim is "these gates are proven by execution" would have merged with zero executing witnesses. That is specification-without-execution exactly, and it is the failure mode I have been quoting at other people all session. fleet_desired_observe now exports only RevisionConverged / RevisionDrifted / RevisionUnobserved. The belt_actuate symbols are back in their own block, and I merged the two rather than leaving a split import authority for one module. Every imported symbol re-verified against its defining module by name. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * Make the tick honor its own lifecycle note (review 51285) BOTH FINDINGS CONFIRMED, and the first is the worse kind: the note described behaviour the code did not have. `classified.refused` was partitioned and never read again, so only stale node ids were withheld from spawn. A ready node with an unobserved-but-present session therefore read as ABSENT to belt_reconcile and could be spawned beside a pane that might be running — the exact double-run the click path's DispatchSessionLivenessUnobserved arm exists to prevent, and exactly what my own note said was prevented here. The deferral set is now every non-running class. MULTIPLICITY IS A NODE FACT, NOT A SESSION FACT, which is why the first cut missed it: belt_tick_classify_sessions read each row's process state in isolation, so two rows for one node (one running, one stale) landed in two different buckets and the reconciler acted on an ambiguity dispatch refuses. The partition is now node-aware — more than one row for a node is a conflict whatever the rows say individually — so both actuation paths refuse the same ambiguity. Witnessed: an unobserved session yields no running row and no spawn candidate; two rows for one node classify neither as running. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * Heal main's ArtifactIdentity collision (#8153), inherited via the main merge CI on press-0 failed with `unresolved type 'ArtifactIdentity'` in three files this branch never touched — realize_kernel_test, hermetic_fixture_realization_test, reconcile_in_process_cache_test. The cause is a §3 name collision, the same class as #8129's `Memory`. `dag/std/cache_interface.dag` has carried `ArtifactIdentity<T>` for a long time; #8153 added a bare `ArtifactIdentity` in `src/v2/compiler/self_host/generation.dag`. Under namespace-only resolution the long-standing bare references stopped resolving uniquely, so the consumers of the ORIGINAL type broke while the new declaration compiled fine. main still carries it and no open PR is fixing it, so this is not a duplicate of someone else's repair — I checked first, having duplicated #8151 earlier today. The newer, narrower name moves: `SelfHostArtifactIdentity`. The cache-interface authority keeps its name and its consumers resolve again. Swept its three files together — the declaration, both self_host witnesses, and the field and function signatures that referenced it. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * Sweep GeneratedArtifactIdentity into the consumer #8177 left behind main is broken, not this branch. #8176 added dag/gunbc/self_host_artifact_materialization.dag importing ArtifactIdentity from v2.compiler.self_host.generation; #8177 then renamed that declaration to GeneratedArtifactIdentity without updating this consumer. My copy of the file is byte-identical to main's, so the compile-clean failure here is inherited rather than introduced. Swept the import, the return type, and both witness files together. THIS IS THE THIRD MAIN BREAKAGE OF THIS CLASS TODAY — Memory (#8129), ArtifactIdentity (#8153), and now the incomplete repair of that same rename. Two were new bare names colliding with existing ones, and this one is a rename that moved a declaration without its consumers. All three share a mechanism: a change is checked against the module it edits, while the breakage appears in modules it does not. Under namespace-only resolution that is mechanically decidable from the containment tree — a symbol that resolved before an edit and does not resolve after it is a whole-tree fact a lens can compute. Worth a lens rather than a fourth repair. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * Pin HTTP status for all five new dispatch arms (review 51368) CONFIRMED. roadmap_serve.dag maps the five new arms explicitly — stale session, revision drift and multiplicity conflict to 409, liveness unobserved and revision unobserved to 503 — but witness_dispatch_status_total still pinned only the original eight. A regression mapping any new arm to 200 would have stayed green. Worse than an absent pin, because status_map_red_note claims each refusal variant maps to ITS status so a collapse reds exactly one row. A reader checking whether the mapping is guarded found a sentence saying yes while the assertion had stopped covering half the arms. WHY THIS ONE WAS MISSED when the label, band, exemplar and button rosters were all swept: it lives in a different witness module and is keyed by CONSTRUCTED variants rather than by a roster the compiler counts, so nothing goes non-exhaustive when an arm appears. That asymmetry is the real defect and it is recorded on the note — until this assertion derives from the exemplar roster the way the label set does, it has to be swept by hand beside it. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> --------- Co-authored-by: gunbc-ci-auto-heal <gunbc-ci-auto-heal@users.noreply.github.com> Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> Co-authored-by: Brian Searls <11205878+briansrls@users.noreply.github.com>
…arget renders This closes the §3 violation the lane exists to remove. product.compute_board carried a second Verilog authority beside v2.extdeps.languages.verilog: the keyword roster, the forbidden-character set, the punctuation, the layout and a full render_module/render_ports/render_assign/render_expr printer. All of it is gone. What remains is a fold from board vocabulary onto the target AST — no keyword, no punctuation, no layout — and the target renders it through the generic serializer. A grep for Verilog syntax in the product module now returns nothing outside its own prose. The module note claimed routing through the target AST was blocked on a general Symbol-to-text projection. That premise was wrong twice: no such projection is needed, because a target resolves a Symbol through its own binding spellings — which is why VerilogBinding now carries the symbol beside the spelling — and the emitter was never the obstacle, because rows derive from formal productions and emission is their backward reading. Identifier admissibility moved to the target too. What may be a Verilog identifier is a fact about Verilog, and a second target consuming this grammar now inherits it rather than re-deriving it from the same specification. What stays in the product is what the BOARD can get wrong: driving a non-output, referencing an undeclared signal, a signal with no binding or two, a spelling the target refuses. THE ORACLE IS UNCHANGED, which is the point. w_interlock_emits_exact_verilog_bytes still pins the exact #8129 source, so this is a replacement proven byte-identical rather than a rewrite that moved the goalposts. 9/9 product Verilog witnesses and 7/7 article witnesses green by execution.
…ed token class
The header and expression parentheses were separate token classes lexing the
SAME literal. That was fine for emission, where the production names the class,
and a hazard for reparse, where the lexer sees "(" and cannot know which class
it is looking at. Two classes over one literal is a lexical ambiguity waiting
for the parser to arrive.
The fork existed only because a transform is keyed by token class, so one class
could not render " (\n" in the header and "(" inside an expression. A BOUND slot
takes its spelling from its emitted atom instead of from a class transform, so
the header parens carry their own layout and the expression parens stay bare.
One class, no lexical ambiguity, byte-identical output.
That is the same move direction and the operators already made — a spelling
choice is a binding — applied to layout rather than to a name.
15/15 green by execution: the six emission witnesses and the nine product
Verilog witnesses, including the exact #8129 byte oracle, unchanged.
…terlock (#8147) * VERILOG-ROWS-0: the bounded emission grammar for the board interlock First increment of the compositional-emission proof: the lexical vocabulary and the formal productions the interlock module needs, authored as grammar rather than as a printer. WHY THIS SHAPE. Rows are DERIVED from formal productions plus an exemplar (derive_grammar_relation_row_node), not hand-authored token lists, so the authored artifact is the grammar and emission is its backward reading. The template is TypeScript's PR3 target, which already exercises every mechanism this needs: grammar_formal_terminal for concrete tokens, grammar_formal_terminal_bound for bound names, and grammar_formal_nonterminal_occurrence for composition — plus two productions sharing one lhs, which is how alternation and optionality are expressed. COMPOSITION IS THE POINT, so the productions are recursive where a fixture would have been flat. The port list is right-recursive rather than three hard-coded slots, so two ports and four ports are the same grammar; expressions nest through expr_primary -> expr_unary -> expr_conjunction so a negation inside a conjunction derives from structure. A row whose token list happened to spell the whole module would only be the hand serializer moved into data, which is explicitly not the deliverable. Verified: compiles with ZERO new diagnostics. Baseline and this head both report the same 33 pre-existing annotation diagnostics in unrelated test files, so the comparison is against a measured baseline rather than an absent one. Not yet done, and this commit claims none of it: the exemplar emitted nodes, the translation-rules wiring, the product mapping from DigitalControlIntent, byte equality, bounded reparse, and the perturbation/reorder/ambiguity controls. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * Carry port direction as a binding, because alternation cannot choose a spelling Fixes the defect flagged on this PR: `direction -> kw_input` and `direction -> kw_output` were unselectable. Productions sharing an lhs are selected by UNIQUE STRUCTURAL MATCH against the emitted node (formal_production_unique_lhs_exact_match), and a concrete terminal contributes NO child to that node. Both alternatives therefore emit an empty Conj, match indistinguishably, and refuse as grammar_relation_row_backward_selection_ambiguous. TypeScript's params_opt alternation works only because its alternatives differ in child count — empty versus one nonterminal occurrence. So alternation expresses OPTIONALITY and SHAPE, never a choice between two spellings. A spelling choice is a binding. Direction is now one bound terminal over a single lexical class that spans both keywords via ChoicePattern, which also means the one production reparses either direction rather than needing a variant per keyword. The constraint is recorded beside the productions rather than in a commit message alone, because the next author reaching for alternation will reach for it the same way I did. Verified: compiles with zero new diagnostics against the same measured baseline (33 pre-existing annotation diagnostics in unrelated test files, unchanged). Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * Give fixed tokens the emit transform, and make every alternative selectable Three corrections, all resolved by execution rather than by asking. SELECTABILITY. Every alternative now differs by CHILD COUNT, which is what the backward selector actually compares. The earlier expr chain had `expr_unary -> expr_primary` and `expr_unary -> ~ expr_unary` both carrying one child, which is the same defect the direction pair had; conjunction against disjunction would have been worse, identical in both count and kind. Operators are therefore BOUND terminals over operator token classes: one expr production per arity — 1 child for a signal reference, 2 for a unary application, 3 for a binary one — and the operator spelling is a binding, exactly as direction is. That collapses conjunction and disjunction into one production instead of two that could never be told apart. Port and assignment lists keep their one-versus-cons alternation, which is selectable because the arities differ. LAYOUT HAD NO SOURCE AT ALL. target_text_seq concatenates exactly, and a fixed token's spelling is its lexeme, so the committed grammar would have emitted `moduleboard_power_interlock(`. The transform map that could have said otherwise was consulted for BOUND tokens only. That asymmetry is the defect, and it is target-neutral: a transform describes how a TOKEN CLASS renders, and nothing in that depends on whether the lexeme arrived from a binding or from the lexer. So the fixed arm consults the same map rather than growing a second layout mechanism beside it. Tokens stay lexically atomic — `(` still lexes as `(` — so reparse is unaffected and layout is target-owned rather than smuggled into a lexeme. Every existing target sets the map empty and is byte-identical by construction. Verified rather than assumed: semantic_decl_serialize_parity_test 7/7 green, which pins emitted bytes against goldens through the changed path. Whole-module compile shows the same 33 pre-existing annotation diagnostics as baseline. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * Declare the layout profile, so every emitted space is target-owned target_text_seq concatenates exactly, so every space and newline in an emitted module is declared in verilog_emit_layout_transforms or does not exist. None of it may return to product.compute_board, which is the point of the exercise. The header and expression parentheses are separate token classes because a transform is keyed by token class, and one paren class cannot render " (\n" around a port list and "(" inside an expression. The #8129 oracle needs both. Splitting the class is what makes the layout expressible; the two classes lex the same literal, which is a REPARSE question rather than an emission one and is tracked as such. Hand-assembling the profile against the oracle reproduces it exactly, including the parenthesised binary expressions and the two-space assign indent. That is arithmetic on paper, not an executed emission; the emission is next. Compiles at the 33-diagnostic baseline. * Emitted-node constructors whose arity is what the selector matches One slot per variable position, because ARITY is what backward selection compares. The expression forms carry one, two and three slots rather than sharing a shape and relying on a discriminant nothing compares: a signal reference carries its name, a unary application its operator and operand, a binary one its operand, operator and operand. Concrete punctuation contributes no slot, which is why the parentheses and keywords are invisible in these constructors and live entirely in the layout profile — the same fact read from the other side. Compiles at the 33-diagnostic baseline. * The interlock exemplar, its spellings, and the derived relation row The binding symbol in a derived row comes from the EMITTED atom (FormalBindingConjSlotEmittedAtom reads node_atom_identity_optional on the slot), not from the production's binding symbol — the production only says WHICH slot is bound. Verified by reading the derivation rather than assumed, because the whole port list depends on it: it is what lets four ports share one production and still carry four different names. That also lets one signal atom serve as a port name, an assignment target and an expression reference at once. Those are the same identity spelled the same way, so forking them would have been three names for one thing. Rows are DERIVED from a production plus the exemplar, never authored as token lists — an authored token list would be the product-local printer wearing a different hat. A CONTROL ON THE EVIDENCE ITSELF. An import edit silently failed to apply and the module still reported the 33-diagnostic baseline, which made "compiles clean" suspect for every increment so far. Planting a reference to a function that does not exist moved the count to 34 with a located diagnostic, so the compile does check and the earlier claims stand. The names were resolving as bare cross-module references; they are now imported explicitly, because DESIGN's import-strip thread records that bare resolution succeeds by pool-membership coincidence rather than by the reference binding its target. * Emit the interlock byte-exact through the generic fold, and catch the fixture echo OUTCOME A: the generic target path composes a bounded Verilog module with no Verilog branch in the compiler. The module frame, the recursive port list, the nested expressions and every space and newline derive from the target's productions and layout profile. TWO MECHANISM FACTS COST MOST OF THE WORK, both settled by execution. A bound terminal only takes its binding from the emitted slot when its binding symbol is ^grammar_free_name_slot or ^grammar_lexeme_stamp_slot — that is what formal_binding_is_emitted_atom_from_slot tests. Custom binding symbols take the ExactBinding arm, which expects no emitted slot, so every production silently failed to match its own exemplar and derivation answered grammar_relation_production_not_found. grammar_formal_terminal_free is the constructor that means what this needed. AND THE FIRST GREEN WAS A FIXTURE ECHO. Row derivation walks the emitted tree and INLINES every nested nonterminal's tokens, so a derived row already carries the whole program's token sequence and serialization replays it. Passing a DIFFERENT module to a model holding the exemplar's row re-emitted the exemplar: byte-exact, entirely insensitive to its input. The perturbation control is what caught it — the positive control alone was green throughout and would have shipped a fixture as a compiler. So emission derives its row from the program being emitted. The rules population is not a fixed table that programs select from; composition happens at derivation, and that is the path all six witnesses now exercise. Controls, 6/6 green by execution: full byte equality against the exact #8129 oracle; every relation row derives, so the empty-row fallback is proven unreachable rather than trusted; dropping the negation changes the expression bytes and leaves the port region byte-identical; a two-port module emits through the same productions with the assignment region intact; and deriving against the REVERSED production population is byte-identical, so selection is structural rather than first-match. That last control replaced one asserting a production count, which proves nothing about order and was a literal measured from the tree it checked. * Supply produced_decl_support, and import TargetModel rather than resolving it bare CI red at 1338af5: two TargetModel literals were missing produced_decl_support, a field main added to the type after this branch was cut. The local compile did not catch it because the branch had never been merged with main — the type it was checking against was the older one. The literals now supply ProducedDeclUnwired, matching every other target, and TargetModel is imported explicitly instead of resolving as a bare cross-module reference. The second half is the more useful repair: bare resolution succeeds by pool-membership coincidence rather than by the reference binding its target, so the type this module checked against depended on what else happened to be in the pool. That is the class DESIGN's import-strip thread records, and it is exactly how a field addition upstream stayed invisible here. Verified after merging main: 73 hard diagnostics, all annotation diagnostics in unrelated files, none from this module; the six emission witnesses green; the seven-witness serializer parity control green. * Delete the product-local Verilog printer; the product now maps, the target renders This closes the §3 violation the lane exists to remove. product.compute_board carried a second Verilog authority beside v2.extdeps.languages.verilog: the keyword roster, the forbidden-character set, the punctuation, the layout and a full render_module/render_ports/render_assign/render_expr printer. All of it is gone. What remains is a fold from board vocabulary onto the target AST — no keyword, no punctuation, no layout — and the target renders it through the generic serializer. A grep for Verilog syntax in the product module now returns nothing outside its own prose. The module note claimed routing through the target AST was blocked on a general Symbol-to-text projection. That premise was wrong twice: no such projection is needed, because a target resolves a Symbol through its own binding spellings — which is why VerilogBinding now carries the symbol beside the spelling — and the emitter was never the obstacle, because rows derive from formal productions and emission is their backward reading. Identifier admissibility moved to the target too. What may be a Verilog identifier is a fact about Verilog, and a second target consuming this grammar now inherits it rather than re-deriving it from the same specification. What stays in the product is what the BOARD can get wrong: driving a non-output, referencing an undeclared signal, a signal with no binding or two, a spelling the target refuses. THE ORACLE IS UNCHANGED, which is the point. w_interlock_emits_exact_verilog_bytes still pins the exact #8129 source, so this is a replacement proven byte-identical rather than a rewrite that moved the goalposts. 9/9 product Verilog witnesses and 7/7 article witnesses green by execution. * One paren class: carry the header's layout on bound slots, not a forked token class The header and expression parentheses were separate token classes lexing the SAME literal. That was fine for emission, where the production names the class, and a hazard for reparse, where the lexer sees "(" and cannot know which class it is looking at. Two classes over one literal is a lexical ambiguity waiting for the parser to arrive. The fork existed only because a transform is keyed by token class, so one class could not render " (\n" in the header and "(" inside an expression. A BOUND slot takes its spelling from its emitted atom instead of from a class transform, so the header parens carry their own layout and the expression parens stay bare. One class, no lexical ambiguity, byte-identical output. That is the same move direction and the operators already made — a spelling choice is a binding — applied to layout rather than to a name. 15/15 green by execution: the six emission witnesses and the nine product Verilog witnesses, including the exact #8129 byte oracle, unchanged. * Read the same rows forward: the token spine rebuilds the emitted node The rows that render the module also recover it. Taking the derived row's token spine and running FORWARD selection rebuilds the emitted node, and it equals the node emission started from — one grammar read in both directions, discharged by execution. A separately authored parser could agree with an emitter by coincidence; a round trip through one row population cannot. WHAT THIS IS NOT, stated because the difference is easy to overclaim: this is a round trip over the TOKEN SPINE, not over emitted BYTES. Nothing in .dag lexes text today — v2.compiler.ingest begins at a ParseTree and takes no source string — so the step from "module ... \n" back to tokens is not executable here and is not claimed. What is proven is that the token sequence the row renders is the sequence the grammar reads back to the same structure. Byte-level reparse waits on a .dag lexer, which is a substrate capability rather than a Verilog gap. A TOOLING NOTE WORTH KEEPING. Importing v2.compiler.ingest first failed with 'dependency_resolution_facts not found in scope' — which is a host builtin the seed registers, not a missing function. The prebuilt claim_batch predated the main merge that added it, so the binary did not know its own builtin. Rebuilding the one bin fixed it, and every suite was then re-run against the fresh binary rather than trusting results a stale one had produced. 33/33 green by execution across all four suites: emission 7, product Verilog 9, article 7, SPICE 10. * A resistor rated in nanofarads has no constructor: kind and value are one coproduct * No projection reads an unadmitted board: whole-article admission, collected causes * The constructor decides the element letter: SPICE names carry their own spelling * Delete the product-local SPICE printer: the format module owns every byte * Real tools accepted the emitted bytes: iverilog simulated the interlock, ngspice solved the deck * The frontier rows say what ran: SPICE and Verilog have both been executed * The firmware contract names the signals it rides on, and admission checks the join * Five firmware roles, three cited upstreams, and two that say why they are not * std.bmc already spells OpenBmc: the cited project takes its own name * Delete the projections second opinion: drivenness is an article fact, refused once * One refusal gate, both SPICE paths: the run deck can no longer bypass it * The host boundary refuses in types: no entry answers a refusal with prose --------- Co-authored-by: Brian Searls <briansearls1@gmail.com> Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Opens the
.daghardware-compiler program at one board subassembly: a two-rail power-good and reset interlock. One article is the authority, and SPICE and Verilog are projections of it — both emitting byte-exact source, both refusing before emitting a byte when the intent expresses something the target cannot honour.The shape
The article imports no projection module. An earlier revision had it holding a
BoardSliceimported from the SPICE projection — SPICENodeRefs, substrate symbols andvalue_lexeme: "10k"— so the supposed authority was shaped by one of its own emission targets, and "the article feeds every interface" was true only because one interface had already decided what the article looked like.product.compute_board.analognow carries the language-neutral electrical intent, and the model-standing carriers (ModelFidelity,ElectricalModelStanding,ComponentModelBinding) moved with it — they name no deck and no element letter, and an IBIS or Verilog-AMS projection needs the same facts.Both emissions consume the article itself, not its extracted parts. Every earlier caller passed the identity and the circuit separately, so one article's identity paired with another's circuit was constructible and the receipt would name a board it does not describe. Receipts carry
BoardArticleIdentity, not a flattened"name rev 0"label — the label made the join a string comparison over a spelling this repository happened to choose.What is grounded
std.measure's existingResistance/Capacitance/ElectricPotentialDifference."10k"and"100n"are gone from the model; SPICE derives the suffix, because a scale suffix is target syntax. No new quantity authority was needed.Ground— power return, signal reference, chassis bond, protective earth, shield termination. A mounting hole bonds to chassis only when a declared relation says so; a chassis bond isProposedorObserved{observer}, never authored as unqualified fact.HardwareThreadCount/ByteSizeon both sides of the comparison inrequirement_met.Evidence
36 witnesses green by execution: article 7, readiness 14, SPICE 7, Verilog 8.
Both decks/modules are pinned by full byte equality, not
string_contains, each with a discriminating RED that perturbs exactly one typed value and requires the bytes to change. Refusal controls cover: vendor subcircuit fidelity this profile cannot render, a missing binding (distinct from an absent model), a terminal on no net, driving an input, an undeclared signal reference, a reserved-word spelling, and a forbidden character.The terminal-on-no-net refusal is the one worth naming: a floating terminal and a grounded one are different circuits, and defaulting the first to the second emits a deck that simulates cleanly and describes a different board.
Fixes carried
spice_text_concat:list_append(free-monoid onString) →concat. This is what made the byte-exact receipt deliverable; an earlier revision of this PR body recorded it as owed.first_duplicate_designatorwas quadratic — 4926² compares at the real package population. Now two linear passes, membership-only maps that are never iterated, so ordering still comes from the designator list and the prior semantics (first designator in source order whose value repeats) are preserved exactly.Not in this change — named so the gaps stay visible
module,output wire,assignandendmoduleare rendered inproduct.compute_board.verilog_projection;dag/extdeps/languages/verilog/holds onlysubject.dag. That is a second Verilog authority beside the target layer and a real §3 violation. Identifier admission and theVerilogBindingspelling table are done; moving the bounded AST and serializer into the target layer is not.boot_storage,reset_owner,consoleandrecovery_pathare still bare strings, there is no plane split (system firmware / TF-A / UEFI / BMC / CPLD), and no pinned public Jade source standings.RequiresSpatialAffineSubstrateLanded,RequiresExactSubMillimeterCoordinateQuantum,RequiresSpatial3ShapeTopology.Correction to an earlier version of this body
Three claims it made are false against this head and are withdrawn: that the byte-exact SPICE receipt was owed, that no Verilog emission existed, and that a coordinate-carrier signedness decision was open for the operator. Signedness is not open — SPATIAL-2 (#8091) decided it:
Millimeterstays a nonnegative magnitude,SignedMillimeterComponentis the signed component on an oriented axis, and the coordinate/displacement distinction lives inPointvsVectorand their operations. The blocker is that the carrier has not landed, which is a different fact with a different remedy.The one operator decision that remains:
gunb-ai/gunbcis PUBLIC. NDA collateral cannot land in it and the publication thread records the Stage-0 placement gate as deleted with no replacement ontology selected. That blocks the Altra contact path independently of NDA access — signing is not sufficient.🤖 Generated with Claude Code