Conversation
The TRI-NET node computes a 32-wide ternary dot product in pure LUT logic (popcount of agreements minus popcount of disagreements, no multipliers and no DSP) and returns a receipt whose CRC-32 tag binds the answer to the exact job bytes, the nonce and the node identity. Adds the RTL cell, an independent Python golden oracle with a receipt verifier, an iverilog testbench that drives real UART frames, and CI that gates synthesis on simulation agreement. Simulation: 128/128 jobs bit-exact against the golden oracle. Hardware: not yet measured. Also wires up CI for the gfternary MUL wrapper, which had been written but never synthesised. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> (cherry picked from commit 15cf207)
…e log The guard grepped for DSP48 anywhere in the yosys output, which matches the pass banner even when no DSP cells exist. Synthesis was clean at 429 LCs with zero DSP48; only the check was wrong. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> (cherry picked from commit f8cb61b)
The wrapper parses a six-byte frame, AA 55 fmt a b trig, but the host emitted a second fmt byte. Operands shifted by one, so the FPGA read a=0 for every job and returned zero. That agreed with the golden oracle on the seven of sixteen cases whose answer is zero, which is why the mismatch survived until the cell was first run on silicon. Hardware after the fix: 16/16 bit-exact on AX7203. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> (cherry picked from commit e0990f3)
Six Zig modules and a .tri spec that turn the ternary node cell into a network:
protocol wire format and receipt verification, agreeing byte for byte with
the RTL and the Python golden
serial physical node over the AX7203 UART at its synthesised baud
net TCP transport so a node on another developer's desk is reachable
the same way as a local one
node fpga, remote and emulated backends behind one interface, including
adversaries that skip the work, replay receipts or claim another
node's identity
ledger TRI work credit, staking and slashing, with a policy that refuses
parameters under which cheating has positive expected value
mesh dispatch, judgement and settlement kept as three separate steps
model ternary-weight inference where every matmul row is a mesh job
agent the IGLA CODER agent, with a proof bundle stating how much of its
arithmetic ran on silicon
38 tests pass, including one showing a layer computes correctly while a node in
the mesh is lying, and that the liar earns nothing.
TRI credit here is a non-transferable internal work record, not a token.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
(cherry picked from commit a801b2e)
…7 specs
Two .t27 specs in the project's own language, plus the architecture note they
summarise:
ternary_hw_verification.t27 keeps RTL written, routed, and measured on the
board as three separate tiers; records the
gfternary 16/16 hardware result, the mac32
simulation, the host frame defect, and an
open_question for the hardware run that had not
finished
settlement_law.t27 which layer answers which attack, why full audit
is affordable only for this work unit, and why
the TRI credit is not a token
Both carry scope_limits. The receipt explicitly does not establish where the
arithmetic ran, and that is recorded as an unresolved finding rather than
omitted.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
(cherry picked from commit 2ee1c93)
…al state Records the status tier of every claim, the board and toolchain facts that cost time to rediscover (a flash is 778 s not 78 s; openocd's redirected stdout is block-buffered so an empty log is not a hang; CI workflows only register from main), the frame-path bug class this project keeps producing, and the design invariants that must not be collapsed. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> (cherry picked from commit f60405b)
At threshold 4 the synthetic network's activations collapsed to the zero vector by the third layer, so every task produced an identical decision with a zero margin. Measured density per layer for a fixed input: threshold 0: 0.69 -> 0.91 -> 0.94 -> 0.91 -> 0.88 saturated, binary threshold 2: 0.69 -> 0.41 -> 0.28 -> 0.25 -> 0.31 sustained threshold 4: 0.69 -> 0.22 -> 0.03 -> 0.00 -> 0.00 dead Both ends destroy the network for different reasons: too high and it answers every input identically, too low and no trit is ever zero, which discards the third state. The usable band is narrow because a 32-wide ternary dot product has a standard deviation of about 3.4, so the constant is tied to the layer width rather than universal. Two tests pin both failure modes. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> (cherry picked from commit de7d037)
… ternary datapath trinet_mac32 flashed and verified on the AX7203: 512/512 receipts, each pass checking both the dot product against the golden oracle and the CRC-32 tag. A second, independently written host (the Zig CLI over libc serial) reaches the same board and agrees, 64/64. Also corrects a claim this session nearly made. The gfternary compute cells expand each 2-bit code into an FP32 constant, run gf_mul_param(8,23), and re-quantise — so their 16/16 verifies the format's decode/compute/quantise law and says nothing about ternary arithmetic. trinet_mac32 is the cell that carries that claim, because its datapath is popcount and subtraction with no floating-point core. Records two further findings: three mutually incompatible trit encodings coexist in this tree and will silently produce sign errors if wired together, and deep ternary networks collapse to the zero vector above a width-dependent activation threshold. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> (cherry picked from commit fb85d36)
Commit 6f3001b, titled a frame format bug fix, inserted a 0x00 byte between the AA 55 magic and the payload across the conformance corpus. That byte belongs to wrappers whose parser has a fmt field. The gf compute wrappers in fpga/vivado do not have one — they read six body bytes as op_a[7:0], op_a[15:8], op_b[7:0], op_b[15:8], trigger — so the extra byte shifted every operand one position and the core received op_a[7:0] = 0x00. Settled against the hardware description rather than by reading Python: formal/gf8_frame_regression_tb.v drives gf8_clean_ax7203 with both byte sequences and reports what the adder latches. shipped host frame AA 55 00 3c 00 41 00 00 -> core received a=00 b=00 corrected frame AA 55 3c 00 41 00 00 -> core received a=3c b=41 So every gf compute host has been computing 0+0 for each input since 2026-07-17, and the corpus cannot currently reproduce its own recorded results. The recorded Tier-E passes predate that commit and are not retroactively invalidated; the scripts that would re-check them are. The three decode hosts were broken the same way in the other direction: they append their own fmt byte on top of the inserted one, so code_lo received the format id. gfternary is the one host where the inserted byte is genuinely the fmt field, and it keeps it. Every golden self-test passed throughout, before and after — none of them exercises the wire encoding, which is the whole lesson. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> (cherry picked from commit 64c3459)
…ext-loop options Records what is measured on silicon, the two corrections made against this session's own earlier claims, nine ranked weak points, and three options with stated cost, risk and falsification criteria. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> (cherry picked from commit 8912d8a)
… works
A static check that reads the request length each host builds and the length
its wrapper's frame FSM parses, and fails when they disagree. It needs no
board, so it runs on every push — which is the difference between catching a
frame regression in minutes and catching it in two weeks.
57 hosts aligned, 0 mismatched, 44 unchecked. Unchecked is reported honestly
rather than counted as passing: a guard that invents a verdict it cannot
substantiate teaches people to ignore it.
Two parser defects were found by testing the guard against a deliberately
broken host rather than by trusting it:
- a trailing comment on the pkt line made the final term fail to match, so
gf8_add fell silently into unchecked and the regression went undetected
- the wide decoders latch through a counter, one statement covering sixteen
bytes, so counting statements understated their frame and reported three
false mismatches
CI re-breaks a host on purpose every run and requires the guard to fail on it,
because a guard that has quietly stopped guarding looks exactly like a clean
run.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
(cherry picked from commit abd81ba)
Thirteen skill files, including the two shared fragments nearly every other skill inherits, hardcoded /Users/playra/trinity-w1 — a path that does not exist here. Every command in them was silently unrunnable. Repointed to the actual repository root. fpga-synth carried a stale header and counts that had drifted from fpga/CATALOG_MATRIX_83.md, so it now says plainly that the catalog is the matrix of record. Added the environment facts a session otherwise rediscovers the hard way — no local place-and-route, tri and zig-out absent, CI workflows registering only from main, and a flash taking 778 s rather than the 78 s some notes claim — and the frame-path failure mode with the guard that now catches it. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> (cherry picked from commit 4e16342)
The first run of this workflow failed with 503 and 404 across every mirror setup-zig tries, which fails the job for a reason unrelated to the code. Fetch from ziglang.org with a retry, and try both tarball naming conventions because 0.14 changed it. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> (cherry picked from commit 4114adf)
The growth story rests on a node on someone else's machine being reachable the same way as a local one, so the test opens an actual listener, serves the same 24/15-byte framing the FPGA speaks, and checks the credit lands. 25/25 jobs credited to the remote owner. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> (cherry picked from commit f5a9da3)
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> (cherry picked from commit 1e52588)
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> (cherry picked from commit 59ce240)
Reads the 7-series factory device DNA through DNA_PORT and streams it over the same UART the compute nodes use. yosys already answers the first half: the primitive survives synth_xilinx. This cell answers the second half, whether nextpnr-xilinx will place and route it. The third half is answered in the header and does not need silicon: device DNA is an identifier, not a key. It is readable over JTAG and by any bitstream on the part, so once a value has been read, software can claim it forever. Binding a receipt to the DNA proves a bitstream asserted an identity — not that the arithmetic happened on that chip. Unforgeability needs a secret that never leaves the device, which here means eFUSE or BBRAM plus an encrypted bitstream. Simulation passes over two rounds against a DNA_PORT stub, including that the value does not change between reads. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> (cherry picked from commit 42c1ac4)
Generated by substitution and left a doubled list marker, which GitHub rejects before running anything. Nothing was learned about DNA_PORT from that failure. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> (cherry picked from commit 4f268c0)
…er conversation needs trinet bench times real jobs against the board and prints the ratio that matters: what the transport allows against what the cell could do. A network whose nodes spend every job waiting on a serial line is a serial line with a compute network attached, and the way to find that out is to measure both ends and print the gap rather than quote the good one. The brief states only measured numbers, names what is not measured so it cannot be inferred, and is explicit that the artifact's near-term value is a reproducible open-toolchain ternary baseline and a verification discipline — not throughput. It ends by asking for the answer that would be least welcome. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> (cherry picked from commit f3e6737)
SipHash-2-4 over the 26-byte receipt preimage, one round per clock, 22 cycles per tag. A CRC-32 tag detects corruption but resists no forgery — the function is keyless, so anyone can produce a valid tag for any job. A keyed PRF makes a tag something only a key holder can produce. Chosen for being add, xor and rotate with no multiplier, which keeps the zero-multiplier discipline the rest of the node holds to, and for having published vectors so three implementations can be held to one law. The RTL reproduces Zig's std.hash.SipHash64(2,4) exactly across two keys and a repeat. The header is explicit about what this does not buy: it moves the trust boundary from anyone to anyone who can read the key out of the bitstream. Since operators hold their own bitstream it does not stop an operator forging their own receipts — it stops everyone else forging on their behalf, and with per-node keys it stops one operator forging for another. Unforgeability by the operator needs a key that never leaves the device, meaning eFUSE or BBRAM plus an encrypted bitstream. This is the substrate that path needs, not a substitute. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> (cherry picked from commit 12c7da5)
Verilog, Zig and Python all reproduce SipHash-2-4, checked against the published vectors for the empty, 3-byte and 26-byte messages. The 26-byte one is the receipt preimage length, so the vector the RTL testbench asserts is the same value the hosts compute. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> (cherry picked from commit e025c74)
Same ternary datapath, receipt tag upgraded from CRC-32 to SipHash-2-4 under a key that exists only inside the bitstream, and the node id taken from the factory device DNA rather than chosen at synthesis. 6/6 keyed receipts bit-exact in simulation against the Python golden, with the id arriving from the DNA_PORT stub rather than the fallback parameter — so the DNA is reaching the receipt, not just the response. Both limits are in the header rather than left to be discovered. The key lives in the bitstream and the operator holds the bitstream, so this stops third parties forging on an operator's behalf and stops one operator forging for another; it does not stop an operator forging their own. The DNA is readable over JTAG, so it is an identifier, not a secret: it stops two boards shipping with the same id, not software claiming an id it has seen. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> (cherry picked from commit 0eb3197)
…the receipts Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> (cherry picked from commit 28955bd)
8'(MSG_BYTES) compiled under iverilog -g2012 and was rejected by yosys outside SystemVerilog mode, so the core simulated correctly and failed synthesis. A sized localparam does the same job in both. Adds the DNA probe host, which treats a value that changes between reads as a failure — an identifier that is not stable is not an identifier. Local yosys now elaborates the v2 node: 1343 LCs, one DNA_PORT, zero DSP48. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> (cherry picked from commit 6713573)
Measured on the AX7203 through openXC7: DNA_PORT places, routes, and completes its read sequence — 8/8 reads, correct framing, 57 bits reported — and DOUT is zero for all 57 of them. All bits reading zero points at the primitive not being configured by the bitstream rather than at a wrong shift sequence, which would be expected to produce offset bits rather than a uniform zero. Separating those two fully needs a vendor-toolchain build on the same board, which has not been run. Without this guard the v2 node would have derived node id 0x00000000 on every board on this flow — one measurement turning into a network-wide identity collision. It now falls back to its parameter identity when the DNA is unavailable or zero. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> (cherry picked from commit 892f69f)
…throughput Three results from working options A and B, each at the tier its evidence supports: DNA_PORT routes under openXC7 and returns an all-zero DNA on silicon — recorded as PARTIAL, with the inference stated and the comparison that would settle it named as not run the keyed receipt tag reproduces the published SipHash vectors in three implementations, with the boundary it does not move written down 202.6 jobs/s measured against a derived compute ceiling 5606 times higher — the ratio that governs every performance claim about this node Adds an open_question for TOPS/W noting that no power figure exists and that publishing an efficiency comparison before one would be fabrication. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> (cherry picked from commit e1daf5a)
…futed itself Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> (cherry picked from commit 47b2037)
… reorder the wave The next wave was ordered on the assumption that DNA_PORT was untested and throughput unmeasured. Both are now measured, and both came back constraining, so the ordering changes: demand first, then escaping the UART, and an explicit instruction not to re-run the DNA probe expecting a different answer. Adds the table of exactly what each receipt tag resists, so the reach of the keyed version is not overstated later. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> (cherry picked from commit 9b81a45)
Section 5 said whether openXC7 could instantiate DNA_PORT was untested. It has been tested: it routes, it answers, and it returns zero for all 57 bits. Also records that the keyed tag partly closes the unforgeability gap and states exactly how far. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> (cherry picked from commit 855f41d)
Verifying that the tag matches is not enough on its own — a tag computed with the wrong key must also fail, or the key is decorative and the upgrade bought nothing. The self-test asserts both directions on every vector. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> (cherry picked from commit 2ca422e)
It imported the shared golden by bare module name, so it only worked when the working directory was conformance/. Every other host in the corpus is invoked from the root. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> (cherry picked from commit 08b66b5)
…seen working trinet_node_v2 flashed and measured: 256/256 keyed receipts verified with the key, every job rejected without it. The second run is the load-bearing one — a tag that verified regardless of the key would mean the key never reached the receipt. The node reports its fallback identity rather than a DNA-derived one, which is the zero-DNA guard engaging on hardware exactly as intended. Without it every board built this way would have reported node id 0x00000000 and a network-wide identity collision would have shipped looking like a clean run. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> (cherry picked from commit 5a5c334)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Review-only. Already on
main— please close, do not merge.Every commit here is already merged into
main, one at a time, over thecourse of the 2026-08-01 session. This PR exists because that was the wrong way
to land 32 commits, and there was no single place to review the work.
The diff below is exactly this session's changes: GitHub takes it from the
merge base (
891a711a3), which is the tree immediately before the firstcommit.
Do not merge it.
mainhas moved on — the other session working in thisrepo has since edited three
gf16_*_conformance_ax7203.pyhosts on top of thiswork, and merging would risk reverting their changes. The branch is a reviewing
surface, nothing more.
What landed
A ternary compute network: a node cell whose datapath is a 32-wide balanced
ternary dot product, a verifiable receipt, a mesh that judges and settles what
nodes claim, and an agent whose model arithmetic runs on it.
Measured on silicon (AX7203, XC7A200T)
trinet_mac32— ternary dot product and CRC receipttrinet_node_v2— keyed receipt (SipHash-2-4)gfternaryMULThe bug this session was worth finding
Commit
6f3001b17(2026-07-17), titled "frame format bug fix", had inserted a0x00between theAA 55magic and the payload across the conformance corpus.The gf compute wrappers have no
fmtfield, so 32 hosts had been computinggf_add(0, 0)for every input for two weeks while every golden self-testpassed — none of them exercises the wire encoding.
Settled against the RTL rather than by reading Python
(
formal/gf8_frame_regression_tb.v):The recorded Tier-E passes predate that commit and stand; what was destroyed
was the corpus's ability to re-verify them.
conformance/frame_alignment_check.pynow guards it in CI, and each CI run re-breaks a host on purpose and requires
the guard to fail on it — a guard that has quietly stopped guarding looks
exactly like a clean run.
Two results that constrain what can be claimed
DNA_PORTplaces, routes andanswers — 8/8 reads, correct framing, 57 bits reported — and returns zero
for all 57 bits. So node identity is asserted, not proven. The guard added
in response was then seen working on hardware: v2 reports its fallback
identity, where without it every board would have claimed
0x00000000.transport ceiling of 410 and a derived compute ceiling of 2.3M. No power
figure exists, so no TOPS/W claim is possible.
Also corrected against itself
gfternaryis not a ternary datapath — it expands each 2-bit code to anFP32 constant, runs
gf_mul_param(8,23), and re-quantises. Its 16/16 verifiesthe format's decode/compute/quantise law and says nothing about ternary
arithmetic.
trinet_mac32is the cell that carries that claim.TRI is a credit, not a token
src/trinet/ledger.zigissues a non-transferable internal work record, andLedger.initrefuses to start on parameters where cheating has positiveexpected value (
p·s > r). The reasoning is in the module header and inspecs/trinet/settlement_law.t27.Note for a reviewer:
deploy/contracts/TrinityToken.solalready allocates 40%of supply to node rewards, which is inconsistent with the design recorded here.
Verification
Records:
specs/trinet/ternary_hw_verification.t27,specs/trinet/settlement_law.t27,docs/TRI_NET_ARCHITECTURE.md,docs/TRI_NET_SESSION_REPORT_2026-08-01.md.Tier-E proofs on #199.
🤖 Generated with Claude Code