Skip to content

Kind reflection (#11819) and a computing SHA-256 under the identity layer - #11996

Merged
briansrls merged 24 commits into
mainfrom
session/nimble-hawk-154
Sep 22, 2026
Merged

briansrls merged 24 commits into
mainfrom
session/nimble-hawk-154

Conversation

@gunbai-bot

@gunbai-bot gunbai-bot Bot commented Sep 21, 2026 •

Copy link
Copy Markdown
Contributor

Carries #11819 (merged from session/gentle-seal-490, unparked by operator direction 2026-09-21) plus the computing-mint seam under the identity layer. One PR by operator direction — and the identity layer does not move onto the digest in it; see Piece B for the measurement that decided that.

Read the scope change first: Piece A delivers ONE working wall, not the three #11819 reported. That is a reduction, and it was found because the positive control was fixed first — while the wall refused everything, all three reds looked equally real.

All evidence below is from a compiler built from this tree at e20e8c5004, with a must-fail control passing first (--definitely-not-a-real-flag → exit 2). Witnesses re-run after merging main.

Piece A — the kind wall

MachineWidth's type parameter is kind-annotated, and the wall now discriminates:

fixture result
MachineWidth<NotAWidth> — bodyless, structurally identical to the kind's arms exit 1, one blocking error, and it is the kind refusal
MachineWidth<PointerWidth> — the kind's own declared inhabitant exit 0, zero blocking, compiled: 1 files emitted
dag/std/integer.dag — the 4 live sites, real corpus exit 0, zero blocking, emits

The red is the strong form: only a check consulting the kind's declared arms refuses a bodyless type of the same shape. That closes the defect #11819 left open, which is what makes its reds mean anything.

Enrolled as test.claim.type_argument_kind_inhabitance_witness_test — 6/6 green. Two design points: the oracle counts blocking rows of the named class, never "did it compile" (a boolean stays red through any refusal); and the sources declare their own kind rather than importing std.machine_constraints, so the module avoids ReadsLiveTree and actually executes in CI.

Retracted: the type-parameter-in-value-position refusal does not fire

#11819 lists it as firing. On a built compiler it does not. The seed's binding_resolves_to_type_parameter is a literal false, so the guard is unreachable by construction; the corrected predicate v1.compiler.infer declares (name_is_enclosing_declared_type_parameter) occurs 14× in the .dag and 0× in the seed. A module whose whole content is a generic function returning its own type parameter compiles with exit 0 and emits — v1.compiler.infer's own note records that it renders into a crate failing rustc E0425, so the silence trades a located refusal for an unlocated one downstream.

That PR hand-patched the kind check into the seed, not this one, and the stage0 regen that would carry the rest has not run.

The witness pins the hole (== 0) rather than deleting the evidence, with an adequacy control on the same harness and run that does count a blocking row, and a trigger naming the capability. It goes red when the wall starts working — that red means flip the assertion, not relax it.

Piece B — the computing mint lands as a declared frontier; the object-ref family does NOT change

What lands: B1 (the computing cryptographic mint), the sole-constructor FabricAddressedObject, and the structural family retained with a row saying why. What was tried and withdrawn in this PR: B2, binding fabric_object_ref_of to that mint.

B1 std.content_hash content_hash_of_value_cryptographic — the module's first mint that hashes bytes (through extdeps.crypto.sha2, FIPS 180-4) instead of parsing a digest someone else computed; through its own sha256_hex_digest wall, so computed and parsed digests share one admission path. It has no production consumer and says so: a declared frontier (DESIGN §3c), its annotation naming the consumer that will bind (fabric_object_ref_of), the row, and the one claim that runs the real path — test.claim.content_hash_family_grounded_witness the_computing_mint_reaches_the_published_digest_of_a_fabric_preimage, oracle sha256:6fccc7fc… computed by coreutils and Python hashlib outside this corpus.
B2 Withdrawn on measurement (below). fabric_object_ref_of mints the structural family, total, exactly as on main; fabric_object_ref_of_wire admits only that family. FabricAddressedObject sole_constructor stays — the object and its ref are one carrier minted in one place, which closes subject_and_its_digest_as_independent_parameters regardless of family.
B3 Does not retype — capability boundary, below.
B4 Trigger names the capability, not the artifact.
B5 Not a §4b(3) drop: main never held cryptographic identity, so there is no previous rung to lower. Filed as a §4b meta-obligation 2 stall — gunbc.recurring_failure_mode fabric_object_identity_is_a_structural_locator (rung 1, ceiling 3, class can climb after one capability lands).

Why B2 was withdrawn: it cancelled the required floor, and the cause was pinned

Main's merge-queue floors run 210 wet identities and finish in ~38 min. This branch's floor cancelled at the 90-min cap because the spark pair_serving_*_real_execution claims — which append and re-read event chains through gunbc.fabric.fabric_storage_file_store — took 3–15 minutes each instead of ~1 s. Every put, every verified get and every closure step paid an interpreted SHA-256. The srv1 differential (same 9-claim file, systemd-run memory cgroup; time from first PASS to last):

binary tree 9 claims
merge-base 4b3f2a2e base 7 s
PR 9e1f47bb base 7 s — the kind-reflection seed change is cleared
PR 9e1f47bb PR 23 m 36 s
PR 9e1f47bb PR, only the mint's family flipped back 8 s

Receipt with binary path, sha256 and build time: #11996 (comment). The brief's point 4, measured on a consumer: interpreted execution of this closure is not viable at floor rate, let alone line rate. A seed intrinsic, a shell-out per object, and a bigger floor cap are each refused in the row for the reasons DESIGN gives. Trigger, at capability grain: native emission of the extdeps.crypto.sha2 closure sufficient for every consumer of the file store to run at floor rate — measured as those spark claims completing inside main's floor envelope with the mint cryptographic. An emitted closure that is still slow does not fire it.

After the withdrawal, on srv1 with the same PR binary: fabric_storage 9/9, fabric_storage_wire 11/11, fabric_storage_file_store_wet 10/10, content_hash_family_grounded 30/30 (including the new inhabitance claim), pair_serving_authority_log_real_execution 9/9 in 7 s of claim time. The floor on this head is the discriminating control: it must complete inside main's envelope.

Why the pure kernel and not the shell arm, under either family — structural, not economic. crypto.Sha256Sum.File takes a path; fabric_object_preimage produces a NonEmptyStr in memory. The shell arm is a temp-file write plus a spawn, and that write manufactures the custody gap verification exists to close. Minting stays pure, so fabric_object_verified stays a pure check.

B3 — the retype cannot land, and two rows were about to retire on a satisfied trigger

UInt32 is Compose<UInt, MachineWidth<32>> over Phantom → a structural operand, and std.operator_realization operator_realization_for routes Add/Sub/Mul/Div/Mod on a structural operand to structural_arithmetic_refusal. v2.std.node Hash is the compiler's own node identity, computed for every node of every compile including the compiles by which v2 emits itself. Retyping it does not give a slower compiler — it gives one that cannot be emitted; and the B2 measurement above is what "slower" would mean per node even where it could run.

Both gunbc.scm.object_store and gunbc.scm.commit_closure carried dissolve-on "a computing cryptographic digest reachable from .dag" — which B1 satisfies literally, while ObjectId stays fnv1a64. Read literally they retire on this merge with the capability dead (DESIGN §4b(3)). commit_closure's is the worse one: the sentence it guards is that "Merkle" here must not import an adversarial commitment, which stays true — retiring it would delete the only thing saying so.

Both restated to the same capability, deliberately not two rows for one fact: native emission of the extdeps.crypto.sha2 closure — sufficient for v2 to emit and build itself with node identity minted from a cryptographic family.

Rung, at the minimum across paths

  • .dag acceptance path — the kind wall discriminates; red and green separate.
  • Emitted path for sha2 — refused by construction. Unchanged.
  • Fabric object identity — rung 1, unchanged from main, now filed with its ceiling and trigger instead of unstated.

So the kind wall climbed. The digest class did not, and no row retires on it. Nothing in this PR was floor-proven at 189cab5 in any sense that matters: that head's floor ran zero wet claims.

Also

  • Stale "no SHA-256 computation exists in .dag" corrected in docs/plans/fabric-storage.md (Known limits now says the family is structural, why, and the trigger) and gunbc.scm.object_store. dag-native-scm-design.md deliberately untouched — gunbc#11997 owns it.
  • Classes filed: a_carrier_swap_moves_the_mint_and_strands_the_parser (read-path-only refusal; a put-only smoke test is green by construction), a_wall_declared_in_dag_is_inert_in_the_seed_that_compiles, and fabric_object_identity_is_a_structural_locator.
  • A §4c defect of my own — an annotation inside a coproduct's arm list — caught by the compiler, not by review.

Attribution: of the failure-mode rows in this diff, only the three named above are this lane's. The other three came in with the #11819 merge and are gentle-seal-490's work.

🤖 Generated with Claude Code

gunbc-ci-auto-heal and others added 17 commits September 20, 2026 07:05
…alue position

FLOOR REPAIR, NOT A NEW CAPABILITY. gunbc already refuses an unbound name in value
position; a generic type parameter is bound in the environment, so the name RESOLVES,
the undefined-variable judgment is satisfied, and nothing downstream asks whether what
it resolved to is a VALUE. Measured on main: `fn f() -> Int { ZZZ }` refuses with one
blocking error, while `fn width_of<N>(..) -> Int { N }` and `fn any_param<T>(x: T) -> Int { T }`
BOTH compile with ZERO and emit an undefined lowercased identifier (`n`, `t`) into a
crate that then fails E0425. Not width-specific.

WHAT LANDS HERE
- std.machine_constraints: MachineWidth's parameter is kind-annotated. PointerWidth stays
  a bodyless type (as on main, where it emits as a real struct); WidthResolution is the
  kind, and its alias target IS the roster of admissible NAMED inhabitants.
- v1.compiler.parse: type parameters accept `<name: Kind>`, carried on the property
  channel so param_is_generic_decl still recognises the parameter.
- v1.compiler.infer_resolve: kind inhabitance at the one place an applied user generic is
  judged, returning a typed KindInhabitance rather than a Bool -- a Bool could not tell
  "inhabits" from "could not look the kind up", and the first draft's `Absent => true`
  admitted everything exactly where it established nothing.
- v1.compiler.infer: TypeParameterInValuePosition at the gbinding and scope.locals arms.
- Two diagnostic variants, each compelled by exhaustiveness to two hand-authored arms in
  cli_run/compile_clean.rs (enumerated by the build, not predicted).

STATE: INCOMPLETE, DO NOT MERGE. The walls FIRE with located text -- Probe<String>,
Probe<UnrelatedType> and any_param<T> all refuse, and a structurally identical bodyless
type refusing is what proves the check consults the kind's declared target rather than
shape. But the POSITIVE CONTROL also refuses: the wall rejects the kind's own declared
inhabitant, because kind_decl.inferred yields no Resolved target in any shape. A wall that
refuses everything has meaningless reds, so nothing here is cited as evidence yet.

Also files gunbc.recurring_failure_mode emitted_field_accessor_panics_on_a_variant_without_the_field:
the emitter renders one shared accessor over a mixed-arity coproduct and gives the arms
lacking the field a panic! body, so the emitted crate compiles clean and aborts at runtime.
137 occurrences, 43 files, 69 accessor names, one emitter site, PRE-EXISTING on HEAD.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…it on InferScope

THE WALL I ADDED MISFIRED ON 24 REAL CORPUS SITES, and the cause is a meaning fork in the
seed's own vocabulary. `TypeVariable` carries TWO meanings: a DECLARED GENERIC PARAMETER
(inserted by env_with_type_variable_bindings) and an UNRESOLVED INFERENCE VARIABLE standing
for a value whose type is not yet pinned. A lambda binder carries the second mid-inference,
so keying the wall on `is_type_variable(binding.resolved.inferred)` refused ordinary VALUE
binders across 11 modules -- `e` in a fold lambda (gunbc.fabric.fabric_cell_effect), `item`
in a value binder (gunbc.host.host_converge), plus evidence, key, s, report, o, first, path,
line, seg, text, paths, t, p. That is the same defect class this wall exists to close,
committed by the wall.

THE FIX ASKS A DECLARED ROSTER. v1.compiler.infer_resolve fn_type_param_names is already the
authority for a declaration's declared type-parameter names -- it is what
env_with_type_variable_bindings is handed. The wall now asks whether the name is in the
ENCLOSING DECLARATION's roster, which is identity-keyed against a declared list rather than
inferred from a binder's provenance. A lambda binder is never in that list whatever its
inferred type is doing.

NOT A SPELLING RULE. The 24 refused names are all lowercase while this corpus's declared
generics are N/T/R/C/U, which is a good tell and a terrible rule: it is a stringly proxy and
it fails silently the day someone writes `fn f<k>`.

CARRIED ON InferScope as enclosing_declared_type_param_names, populated at the fn-body scope
from the same single producer, and propagated through every scope derivation. InferScope
already carries locals, body_locals, match_bound_names, lambda_param_provenance and
in_flight_lambda_param_names -- all answering "which names are bound, and how, in this
scope" -- so this is the missing member of a family it already holds, not a new capability.
Record literals are fail-closed, so a missed construction site is a located compile error.

REJECTED: caller_decl_name as the route. It is set from caller.decl_name in one arm and to
the literal "<unknown>" in another, so a wall keyed on it would silently admit everything
reaching the second arm -- a fail-open wearing a plausible field name.

TRANSIENT SEED PATCH, called out because it is a hand edit to a generated file. The committed
mirror's copy of the old predicate refuses the corpus, which stops the seed compiling it,
which stops regen -- the only thing that can replace the predicate. A bootstrap deadlock. The
seed's copy is made inert so regen can run; the .dag authority no longer declares that
function, so the next regen deletes the item outright. The patch is annotated in place as not
the authority.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…carry its target here

MEASURED, NOT REASONED. The self-diagnosing refusal printed this on the real corpus:
    'PointerWidth [decl=WidthResolution inferred=absent resolved_target= children=0]'
A single-arm `type WidthResolution = PointerWidth` is parsed as a TYPE ALIAS, so the
declaration reaching the kind check is a bare nominal node: no inferred target, no children.
Reading kind_decl.inferred therefore answered nothing, which is exactly why the wall refused
its OWN declared inhabitant and the positive control failed while the reds passed.

Three separate readings predicted otherwise -- local_binding_for_item's alias arm carries
`inferred: item.inferred`, and lookup_type_by_name returns binding.resolved. Both statements
are true and neither predicts what arrives at this call site. The trace settled in one run
what reading had lost three times, which is the argument for having built the diagnostic into
the refusal rather than around it.

THE FIX RESOLVES BOTH SIDES SYMMETRICALLY through this module's own resolve_node_bounded and
compares the resolved identity, instead of reading a field that one side happens not to carry.
The name-equality arm is kept ahead of it for the case where the declaration does carry a
target, so neither shape depends on the other.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…the children arm

MEASURED CAUSE. `type WidthResolution = PointerWidth` is a SINGLE-ARM form, which this
language parses as a TYPE ALIAS. The declaration then reaches the kind check as a bare
nominal node carrying neither a resolved target nor children, so the check could read
nothing about its own roster and refused its own declared inhabitant. The self-diagnosing
refusal printed exactly that, twice, on the real corpus:
    'PointerWidth [decl=WidthResolution inferred=absent resolved_target= children=0]'
Resolving both sides did not rescue it either: the kind node synthesized by the parser for
the annotation carries no ident, so node-keyed lookup cannot resolve it. An alias kind is
simply unreadable at this call site.

SO THE KIND IS A COPRODUCT, AND ITS ARMS ARE THE ROSTER -- children the check can read
directly, with no dependence on a field the declaration does not carry.

THIS REVERSES AN EARLIER REFUSAL, ON EVIDENCE. The two-arm shape was refused earlier on my
own inference that a unit variant in type-argument position reproduces E0573. That inference
was WRONG and I retracted it: unit variants in type-argument position are minted as
zero-sized markers and COMPILE, verified cross-module with a correctly generated qualified
use line. The second objection -- that this shape cannot express per-kind literal
admissibility -- is true but applies equally to the alias shape, since literal admissibility
is a language-level rule in both. So the shapes are equivalent in expressiveness and only one
of them is readable.

PointerWidth stops being a separate bodyless type and becomes an arm, so nothing is minted
twice and the 4 live MachineWidth<PointerWidth> sites keep their spelling. StaticWidthIndex
names the literal arm so the roster is complete rather than implicit.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…sses

THE .dag WAS ALREADY RIGHT; THE SEED DOING THE CHECKING WAS STALE. After the kind became a
two-arm coproduct the trace confirmed the corpus declaration is seen correctly --
    'PointerWidth [decl=WidthResolution inferred=absent resolved_target= children=2]'
children=2, so the arms are there. But the committed seed's kind check has no arm that READS
them: the membership arm exists only in v1.compiler.infer_resolve, and it reaches the seed
only through a regen, which cannot run while the seed refuses the corpus. That is the same
bootstrap deadlock as the previous commit, one layer along.

So the membership arm is carried into the seed by hand, annotated in place as transient and
not the authority. It is the SAME logic the .dag declares, so the next regen replaces it with
equivalent generated bytes rather than reverting it.

TWO CLASSES FILED, both language-layer and both outliving this change.

single_arm_sum_parses_as_an_alias_and_becomes_unreadable -- THE ARM COUNT SILENTLY DECIDES
WHAT KIND OF DECLARATION YOU WROTE. `type K = A` parses as a TYPE ALIAS; `= A | B` is a
coproduct with readable children. The alias then reaches a consumer as a bare nominal with no
resolved target and no children, and the obvious rescue fails for a SECOND, independent
reason: a synthesized annotation node carries no ident, and lookup_type_for keys on ident. Two
routes, two different failures, no diagnostic on either. Receipt is the trace above.

type_variable_means_both_declared_generic_and_inference_variable -- a DESIGN section 3 meaning
fork in the seed's own vocabulary. TypeVariable names both a DECLARED GENERIC PARAMETER and an
UNRESOLVED INFERENCE VARIABLE, so a consumer asking a binding what it is cannot tell which
answer it got. Receipt: 24 ordinary value binders refused across 11 modules. The row records
both rejected repairs (a spelling proxy; caller_decl_name, which is "<unknown>" in one arm and
would fail open) and states plainly that the declared-roster fix ROUTES AROUND the fork rather
than closing it -- every other consumer that asks a TypeVariable what it means is still
exposed, and the row stays open.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…pto.sha2 makes that false

Both sentences were true when written. `extdeps.crypto.sha2` `sha256` / `sha256_hex` now
compute FIPS 180-4 SHA-256 over `List<UInt8>` in the substrate, witnessed against the
standard's vectors by `test.claim.sha256_fips_witness_test`, so the assertion that the
corpus can only parse SHA-256 hex is stale in both places.

The correction does not replace one settled claim with another. What it records is that
EXISTENCE is no longer the open question and REALIZATION is: the pure kernel is emit-blocked
(`std.operator_realization` refuses infix arithmetic on `Compose<UInt, MachineWidth<N>>`, so
`std.bitwise` `word32_add` and its callers run only interpreted), while
`extdeps.crypto.hash` `sha256sum_file_command` / `sha256sum_line_digest` executes today at
one process spawn per object and would make ref-minting effectful. Per DESIGN §3 transport
is not a fact of the interface, so which one serves `fabric_object_ref_of` is a
`std.decision` question over object size rather than a prerequisite ordering — and the
dissolve-on is restated as a BOUND AND EXECUTED path from accepted bytes to fabric identity,
because "a computing digest exists" is already satisfied and would retire the row with the
capability still dead (DESIGN §4b(3)).

Also narrows the fabric-storage collision sentence: a collision is a content-identity
failure that can enable an isolation failure, not itself a cross-tenant one. Tenant
isolation stands on its own. The separate declared gap that no principal is refused at the
served endpoint is untouched and stays its own row.

Symbols, not line numbers, throughout (DESIGN §3 standing rule).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…ypto.sha2

Every other Sha256Digest mint in this module PARSES a digest someone else computed --
sha256_hex_digest validates hex, sha256_digest_content_hash bridges a cited Digest,
parse_content_hash reads one off a wire. None of them hashes bytes, so the only digest the
module could MINT from a value was Fnv1a64Structural: sixty-four non-cryptographic bits,
a locator and not a durable intersubjective identity (DESIGN A3).

content_hash_of_value_cryptographic closes that. The computation is extdeps.crypto.sha2's
(FIPS 180-4, cited at its own authority anchor, witnessed by
test.claim.sha256_fips_witness_test); the carrier and its construction wall stay here, and
the computed digest text goes through the SAME sha256_hex_digest wall every parsed digest
goes through, so no second admission path exists (DESIGN 3). Acyclicity verified:
extdeps.crypto.sha2's import closure does not reach std.content_hash.

The Optional is not ceremony. base16_encode_lower over sha256's thirty-two octets does
yield sixty-four lowercase hex digits, but that fact lives in list LENGTHS, which no
declared type here carries, so the module must not assert it -- answering none is the typed
refusal DESIGN 5 requires, and it is the shape extdeps.crypto.hash sha256_digest_content_hash
already uses at the same boundary.

RUNG, AT THE MINIMUM ACROSS PATHS: interpreted, this computes real SHA-256; emitted, it does
not execute at all, because std.operator_realization refuses infix arithmetic on
Compose<UInt, MachineWidth<N>> and std.bitwise sits under sha2. The honest rung is the
emitted one, which is silent.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…travels

fabric_object_ref_of minted Fnv1a64Structural: sixty-four non-cryptographic bits. Against an
adversary that is a LOCATOR -- a store built on it can say 'these are the bytes I have' and
cannot say 'these are the bytes you asked for'. The verified-cache guarantee this module
supports is the second sentence, so minting a locator and calling the check verification is
rung inflation (DESIGN 4b(1)).

WHY THE PURE KERNEL AND NOT THE SHELL ARM. extdeps.crypto.hash carries a shell-out digest,
and it does not fit this seam for a reason stronger than its cost: crypto.Sha256Sum.File
takes a PATH, and fabric_object_preimage produces a NonEmptyStr in memory. Reaching it means
writing every object to a file before hashing it, which re-opens the custody gap verification
exists to close and turns ref-minting into an effect -- the purity that makes
fabric_object_verified a pure check.

THE OPTIONAL IS PROPAGATED, NEVER ABSORBED. The tempting repair -- fall back to the
structural digest so the signature stays total -- would answer with a locator in exactly the
case where identity could not be established, which is the absorbing fallback DESIGN 5
forbids. So it travels: FabricObjectDigestUnavailable is a new typed fault, distinct from
FabricObjectCorrupt because 'nothing was compared' and 'the bytes disagree' have different
repairs; fabric_storage_file_put mints BEFORE touching the filesystem, so a refusal never
leaves bytes on disk under no name; and closure_stored refuses the whole reply rather than
filtering, because a reply one object short satisfies every check below it.

fabric_object_ref_of_wire moved with the mint and had to: it read
content_hash_from_structural_digest, which admits sixteen hex digits, while the wire is now sha256 colon sixty-four hex digits. It would have refused every ref this module mints.

The collision arm in fabric_storage_put_from_create STAYS, and its reason inverted: under the
old family a collision was an accident, under SHA-256 it is an attack. Retiring the wall
because the digest got stronger would remove it exactly when what it catches stopped being
noise.

Also drops an unused fabric_object_ref_of import from gunbc.fabric_event_log.

Witness call sites are not yet updated -- this commit does not compile on its own.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
… would catch a family change

Every claim in this witness asked whether refs AGREE with each other, and all of them stay
green under ANY deterministic digest -- including the fnv1a64 locator the module just moved
off. So none of them was evidence that the mint is cryptographic. the_mint_computes_a_real_
sha256_digest is: it pins the wire form against the SHA-256 of the exact preimage bytes,
computed outside this corpus by coreutils sha256sum and independently by Python hashlib,
which agreed. That is an external oracle rather than a measurement copied from the tree it
checks (DESIGN 5), and it goes red if the mint changes family, changes preimage, or stops
computing FIPS 180-4.

The Optional threading is not avoidable by supplying the ref, and the note in the file says
why: FabricObjectRef is sole_constructor, so no module but std.fabric_storage can build one,
and both ingresses answer an Optional. DESIGN 3 says supply a boundary value rather than
compute it; here the carrier forbids supplying it, which is the carrier working as designed.
A witness must not invent a ref to keep its own shape tidy.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
… quiet-wolf-114

TWO COORDINATION MOVES, NO NEW BEHAVIOUR.

First, docs/plans/dag-native-scm-design.md reverts to main. gunbc#11997 corrects the same
stale claim in that file and does it better -- it narrows open question 2, records WHY the
shell-out arm was refused here, and names v2.std.node as the remaining half. Two PRs editing
one paragraph to say the same thing is the fork DESIGN 3 exists to prevent, so that file is
theirs and docs/plans/fabric-storage.md stays mine.

Second, files a_carrier_swap_moves_the_mint_and_strands_the_parser, requested by
quiet-wolf-114 so they can cite it rather than restate it.

The class is not the ordinary broken-migration one. Its distinguishing fact is that the
failure is READ-PATH ONLY and the two paths exercise different halves: a write exercises the
mint, which is correct, and a read exercises the parser, which is not. So the system keeps
accepting work, the corpus fills with values in the new form, and the damage accumulates
during precisely the period in which everything looks healthy -- a put-only smoke test is
green by construction and is the first instrument anyone reaches for.

Its ceiling is structural and DESIGN 4 already names the shape: one grammar read in both
directions. A parser derived from the same rows as its serializer cannot disagree with it,
exactly as emission and ingestion cannot. The trigger names that capability rather than the
fix that prompted it.

Receipt is this PR: fabric_object_ref_of moved to SHA-256 while fabric_object_ref_of_wire
still read sixteen-hex-digit structural digests, and would have refused every ref the module
mints. Found by reading the module, not by a red -- the brief directing the migration named
the mint and not the inverse, and so did the module's own note, which describes the two as
one closed loop.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
… fixture

Same forced shape as the pure witness: FabricObjectRef is sole_constructor, so a witness
cannot supply one literally, and both ingresses answer an Optional. What differs here is
that neither of these witnesses is ABOUT the digest -- one is about the wire codec, the other
about the file store -- so resolving the mint at each of the twenty-seven call sites would
put the digest's refusal arm into claims that have nothing to say about it.

So each resolves once. The wire witness gets a WireFixture carrying both objects and both
refs; the file-store witness pairs each event with its own ref in an Ev, so a chain builds by
handing the parent's ref down.

THE ABSENT ARM ANSWERS false IN EVERY CLAIM, never skips. A fixture that could not be built
means the claim did not run, and a claim that did not run must not read as one that held --
the alternative shape, quietly returning true or omitting the assertion, would delete
evidence exactly when the thing under it broke.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…as about to retire on a satisfied trigger

THE DEFECT FOUND, WHICH IS WORTH MORE THAN THE RETYPE WOULD HAVE BEEN. gunbc.scm.object_store
carries a declared rung on ObjectId with dissolve-on 'a computing cryptographic digest
reachable from .dag'. That condition is NOW SATISFIED -- extdeps.crypto.sha2 computes it and
std.content_hash mints through it as of this PR. Read literally the row retires today while
the capability it protects is completely dead: ObjectId would stay fnv1a64 and nothing would
be left saying so. That is exactly the DESIGN 4b(3) failure the brief warned about, sitting
in the tree, one merge from committing itself.

The trigger is restated as the capability: NATIVE EMISSION OF THE extdeps.crypto.sha2
CLOSURE, sufficient for v2 to emit and build itself with node identity minted from a
cryptographic family. A bridge existing does not satisfy it; std.fabric_storage consuming one
does not satisfy it.

WHY THE RETYPE CANNOT LAND, derived from the authority rather than from the brief. ObjectId
is v2.std.node's Hash by alias, for the measured reason that module already records, and Hash
is the COMPILER'S OWN node identity -- computed for every node of every compile, including
the compiles by which v2 emits itself. UInt32 is Compose<UInt, MachineWidth<32>> over the
Phantom carrier, so it is a STRUCTURAL operand, and std.operator_realization
operator_realization_for routes Add/Sub/Mul/Div/Mod on a structural operand to
structural_arithmetic_refusal -- a typed located refusal, not a slow path.

So retyping Hash today does not produce a slower compiler. It produces one that cannot be
emitted at all. That is a capability boundary, not a cost tradeoff, which is why no
throughput number decides it -- the same shape as the fabric seam, where a structural fact
beat an economic one.

This is the boundary the operator asked me to name early rather than discover at the end.
Piece B3 does not retype in this PR, and the reason is stated where a reader of the identity
layer will meet it.

Also corrects the note's factual claim that no computing SHA-256 exists -- the third site
carrying that stale sentence, and the only one in load-bearing .dag rather than a plan doc.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
gunbc#11819 recorded three refusals firing AND the kind's own declared inhabitant refusing
beside them. A wall that refuses everything has meaningless reds, so none of the three was
citable -- the PR said so itself. This file is the pair that settles it in both directions on
one run: three REDs (a named non-inhabitant, a bodyless type structurally identical to the
arms, a type parameter in value position) and three CONTROLS (each declared arm of the kind,
and an ordinary generic function).

THE ORACLE NAMES THE CLASS, NOT THE OUTCOME, following the rule
test.claim.optional_at_required_position_witness_test states at its own oracle: a
compiles/does-not-compile boolean stays red through ANY refusal, so it would keep reporting
this wall while TypeArgumentKindMismatch never fired. Each red counts blocking rows of the
named class and each control asserts zero of that class, with -1 on the not-runnable arm so
could-not-measure satisfies neither comparison.

THE SOURCES DECLARE THEIR OWN KIND AND DO NOT IMPORT std.machine_constraints, which is what
lets this run at all. compile_dag_diagnostic_census resolves against the live checkout, and a
module leaning on live-tree CONTENT must declare ReadsLiveTree -- which the required floor
declines before the fold sees it, making it authored evidence rather than enrolled. A
self-contained kind tests the MECHANISM, so it is both stronger and executable. Whether
std.integer's four live sites compile is integration evidence for the PR body, not a second
copy of this claim.

The second-arm control is not redundant with the first: admitting exactly one name is
indistinguishable from a hardcoded case for that name, so both arms being admitted is what
shows the check reads the kind's children rather than a spelling.

NOT YET RUN. This is the instrument; the verdict is a separate report.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
quiet-wolf-114 asked whether, if one dissolve-on was artifact-shaped, others might be. One
was: gunbc.scm.commit_closure read 'dissolve-on: a computing cryptographic digest reachable
from .dag' -- the same condition gunbc.scm.object_store carried, and the same one B1 in this
PR satisfies literally while ObjectId stays fnv1a64.

It would have retired the same way and cost more when it did. The sentence the row guards is
that calling this structure 'Merkle' must not import an adversarial or cross-tenant
cryptographic commitment. That sentence stays exactly true after B1 lands, and the trigger
retiring would have deleted the only thing saying so -- in a module whose whole subject is
commit identity.

The corrected trigger is the SAME capability object_store now names, deliberately and not as
a second row about a second fact: this module's digest IS ObjectId, so the two dissolve
together or neither does. Native emission of the extdeps.crypto.sha2 closure, SUFFICIENT FOR
v2 to emit and build itself with node identity minted from a cryptographic family.

CENSUS SCOPE, so a reader knows what was and was not swept: every dissolve-on, dissolves-on
and next-rung-trigger in dag/ and src/v2/ whose text mentions SHA-256, cryptographic,
computing digest, hashing bytes or a reachable digest. Two rows matched and both are
corrected here. The remaining matches are these two corrections quoting their own old text,
and docs/plans/dag-native-scm-design.md, which gunbc#11997 owns.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…he seed and is retracted

THE KIND WALL WORKS, and this is the first evidence in this lane that is citable.
Measured on a compiler built from this tree at e20e8c5, with a must-fail control passing
first (a bogus flag refused, exit 2):

  MachineWidth<NotAWidth>     exit 1, ONE blocking error, and it IS the kind refusal
  MachineWidth<PointerWidth>  exit 0, ZERO blocking, 'compiled: 1 files emitted'
  dag/std/integer.dag         exit 0, ZERO blocking, emits -- the 4 live sites, real corpus

The red is the strong form: NotAWidth is BODYLESS, structurally identical to the kind's own
arms, so only a check consulting the DECLARED arms refuses it. That closes the defect
gunbc#11819 left open -- its positive control now passes, which is what makes its reds mean
anything.

THE THIRD REFUSAL DOES NOT FIRE, AND I AM RETRACTING IT RATHER THAN CARRYING IT.
gunbc#11819 lists a type parameter in value position as refused. On a built compiler it is
not: the seed's binding_resolves_to_type_parameter is a literal false, so the guard admitting
the refusal is unreachable BY CONSTRUCTION, and the corrected predicate v1.compiler.infer
declares occurs fourteen times in the .dag and ZERO times in the seed. A module whose entire
content is a generic function returning its own type parameter compiles with exit 0,
zero blocking, and
EMITS -- v1.compiler.infer's own note records that it renders into a crate failing rustc
E0425, so the silence trades a located refusal for an unlocated one in a later toolchain.
That PR hand-patched the KIND check into the seed and not this one; the regen that would
carry the rest has not run.

The witness pins that hole rather than deleting the evidence, with the reasoning in the
claim: asserting the refusal goes red and cannot merge, so the pressure is to delete the only
thing that would have noticed; asserting admission merges and reads forever as coverage.
Neither is honest. It ships with an adequacy control on the same harness and the same run
that DOES count a blocking row, so the zero is the compiler's silence and not the harness
failing to reach the judgment, and a trigger naming the capability.

Files a_wall_declared_in_dag_is_inert_in_the_seed_that_compiles. The carried lesson is not
the stub but why the self-host loop conceals it: the seed is regenerated BY the compiler, so
a new rule reaches the binary only through a regen, and that regen is a compile by the
current seed, which lacks the rule. A hand-patch closes one rule and leaves the others in the
same change silently unpatched -- a partially-live wall, indistinguishable from a working one
from outside.

Also drops the three scratch probe fixtures from dag/.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@gunbai-bot
gunbai-bot Bot marked this pull request as ready for review September 21, 2026 20:39
…ritten yet

review 69717 is right and the commit order is the whole explanation: 97cd138 rewrote this
bullet, 3dde2d3 moved the mint afterwards, and nothing came back. So a plan doc touched by
this PR asserted the opposite of the code shipped in the same PR -- object refs use the
non-cryptographic structural digest, and which realization binds the digest is still open.
Both false at merge. That is the same stale-claim species this PR corrects in
gunbc.scm.object_store and gunbc.scm.commit_closure, committed one file over by the author
correcting it.

A third clause was wrong for a different reason and the review did not have to catch it: the
bullet credited the realization choice to measured throughput against object size. I withdrew
that reasoning -- the throughput figure I inferred was an undecomposed wall-clock number and
the measurement contradicted it. The decision was always STRUCTURAL: crypto.Sha256Sum.File
takes a path, fabric_object_preimage produces bytes in memory, and a temp-file write per
object manufactures the custody gap verification exists to close. The bullet now says that,
because that is the argument that actually holds.

Rewritten to describe what shipped. The heading changes too -- Weak digest is no longer the
limit, and emission is: the closure runs only interpreted because UInt32 is a structural
operand and arithmetic on one routes to structural_arithmetic_refusal. A second bullet
records that minting can refuse and the refusal is propagated rather than absorbed, since a
reader of the storage plan meets the Optional at the first call.

The collision arm keeps its paragraph with its reason inverted rather than deleted: under the
old family a collision was an accident, under SHA-256 it is an attack.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@gunbai-bot

gunbai-bot Bot commented Sep 21, 2026

Copy link
Copy Markdown
Contributor Author

Fixed in 1cbc682. review 69717 is correct, and the commit order it reconstructed is the whole explanation: 97cd138 rewrote that bullet, 3dde2d3 moved the mint afterwards, and nothing came back to the doc.

Taking it as the class rather than the line: this PR re-points gunbc.scm.object_store and gunbc.scm.commit_closure away from a dissolve-on that had become satisfied, and then asserted the opposite of its own shipped code in a plan doc one file over. Same defect, committed by the author correcting it, in the section titled "Known limits of the first realization" — which is exactly where a reader goes to learn whether the weak digest is still live.

What the bullet now says: object refs mint through std.content_hash content_hash_of_value_cryptographic over extdeps.crypto.sha2, with test.claim.fabric.fabric_storage_witness the_mint_computes_a_real_sha256_digest cited as the execution receipt. The heading changed too — "Weak digest" is no longer the limit. What remains is emission: UInt32 is Compose<UInt, MachineWidth<32>>, a structural operand, and std.operator_realization operator_realization_for routes arithmetic on a structural operand to structural_arithmetic_refusal, so the closure runs only interpreted.

A third clause was wrong that the review did not have to catch. The bullet credited the realization choice to "measured throughput against object size". I have withdrawn that reasoning: the throughput figure I had inferred came from an undecomposed wall-clock number, and when I measured properly the result contradicted it — a fabric witness performing ~25 real SHA-256 mints ran in 1m55s against 1m59s for a zero-hash witness over the same corpus, so interpreted SHA-256 over small preimages is lost in corpus-load noise. The decision was always structural: crypto.Sha256Sum.File takes a path, fabric_object_preimage produces bytes in memory, so a shell transport costs a temp-file write per object and manufactures the custody gap (hash one file, store another) that verification exists to close. The bullet now carries that argument, because it is the one that holds and the one a later reader would otherwise re-derive incorrectly from numbers I no longer stand behind.

A second bullet was added for something a storage-plan reader meets at the first call: the mint answers an Optional and the refusal is propagated rather than absorbed. Falling back to the structural digest would answer with a locator in precisely the case where identity could not be established.

The collision arm keeps its paragraph with its reason inverted rather than deleted — under the old family a collision was an accident; under SHA-256 it is an attack, so the wall matters more now, not less.

No code changed; the three witnesses remain 6/6, 10/10 and 11/11 by execution on the merged tree.

— sent from nimble-hawk-154

…that counts it

review 69733 is right. gunbc.seed_growth_admission makes unenumerated hand growth in src/v1
a stop-line, and the TRANSIENT BOOTSTRAP PATCH annotations those declarations carry cannot
discharge it -- DESIGN 4c is explicit that a dissolution condition belongs in a typed carrier,
so a trigger living only in a comment is uncountable by the roster that exists to count it.

THE POPULATION SPLITS AND ONLY ONE HALF IS GROWTH. Sixteen seed declarations were added or
modified by the gunbc#11819 carry. FIFTEEN resolve in the .dag authority, so they are
generated bytes carried ahead of a regen -- ExistingSeedItemModified, not additions, and named
in the row's header rather than omitted. ONE does not: binding_resolves_to_type_parameter
occurs ZERO times in every .dag under src/v1. That is the growth, and it is the only
declaration the row cites.

WHY IT IS DECLARED RATHER THAN IMPLEMENTED. It stands where v1.compiler.infer declares
name_is_enclosing_declared_type_parameter, which reads InferScope's
enclosing_declared_type_param_names -- a field the SEED's InferScope does not have. Honouring
it by hand means adding a struct field and threading it through every construction site in
generated Rust, which is hand-authoring compiler logic into the seed to make a wall go green.
DESIGN 7 and this repository's standing instruction both refuse that direction.

WHY IT IS NOT DELETED, which is the first repair anyone reaches for. Deleting the stub means
deleting its two call sites, and THOSE resolve in the .dag -- v1.compiler.infer constructs the
diagnostic at exactly those points. Removing them moves the seed further from the authority,
and the regen re-adds them. The divergence worth removing is the predicate; the remover is the
regen, not a second hand edit.

The row states rather than nets away what the stub costs: its body is false, so both branches
are dead and the behavioural delta is zero, and the consequence is that a declared
SeverityError/GateBlocking diagnostic cannot fire. It also discloses the shape divergence the
review flagged -- the seed's type_arg_kind_inhabitance takes three parameters where
v1.compiler.infer_resolve declares four, missing the kind_inhabitant_matches_resolved arm.

AND IT IS WIRED INTO gunbc.seed_growth_admission, which the review did not ask for and which
the fix needs: that roster imports every justification BY NAME rather than deriving them, so a
row that compiles but is not listed is a dangling declaration (DESIGN 3c) that satisfies the
letter of the finding while the roster still does not count the debt. Same shape as the defect
this PR is about -- machinery present, nothing consuming it.

Both compile clean against a built compiler: the row alone, and the admission roster with it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@gunbai-bot

gunbai-bot Bot commented Sep 21, 2026

Copy link
Copy Markdown
Contributor Author

Fixed in 17a4cca. review 69733 is correct, and verifying it split the population in a way worth recording.

Sixteen seed declarations were added or modified by the #11819 carry; only one is growth. Fifteen resolve in the .dag authority — type_arg_kind_inhabitance, KindInhabitance, the kind_* helpers, parse_optional_type_param_kind, type_param_kind_property_name, the WidthResolution arms — so they are generated bytes carried ahead of a regen, which is ExistingSeedItemModified rather than addition. One does not: binding_resolves_to_type_parameter occurs zero times in every .dag under src/v1. That is the growth, and it is the only declaration gunbc.kind_reflection_seed_growth cites. The other fifteen are named in the row header rather than omitted, because a receipt that counts only what the roster can cite under-reports its own growth.

Why it is declared rather than implemented. It stands where v1.compiler.infer declares name_is_enclosing_declared_type_parameter, which reads InferScope.enclosing_declared_type_param_names — a field the seed InferScope does not carry. Honouring it by hand means adding a struct field and threading it through every construction site in generated Rust: hand-authoring compiler logic into the seed so a wall goes green, which is the direction DESIGN §7 and this repo's standing instruction both refuse.

Why it is not deleted, since that is the first repair anyone reaches for. Deleting the stub means deleting its two call sites — and those do resolve in the .dag; v1.compiler.infer constructs the diagnostic at exactly those points. Removing them moves the seed further from the authority, and the regen re-adds them. The divergence worth removing is the predicate, and the remover is the regen.

The row states rather than nets away what the stub costs: the body is false, so both branches are dead and the behavioural delta is zero, and the consequence is that a SeverityError/GateBlocking diagnostic cannot fire. It also discloses the shape divergence the review flagged — the seed's type_arg_kind_inhabitance takes three parameters where v1.compiler.infer_resolve declares four, missing the kind_inhabitant_matches_resolved arm.

One thing beyond the finding. gunbc.seed_growth_admission imports every justification by name rather than deriving them, so a row that compiles but is not listed is a dangling declaration (DESIGN §3c) — it would satisfy the letter of this finding while the roster that exists to count seed debt still did not count it. The row is wired in. That is the same shape as the defect this PR is about: machinery present, nothing consuming it.

Both compile clean against the built compiler — the row alone, and the admission roster with it. No behaviour changed, so the three witnesses stand at 6/6, 10/10 and 11/11.

— sent from nimble-hawk-154

@briansrls briansrls left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

REQUEST CHANGES — finish the migration and the executing consumer, not only the new mint

Reviewed 17a4cca. The operator's requested outcome is the completed migration plus production wiring, where possible, rather than another preparatory seam or a more detailed explanation of what remains unimplemented.

This is a source review of the diff and its consumer paths, not an independent gunbc execution or a live-store audit. Your reported executions remain attributed to you. I independently checked the small SHA-256 oracle, not the repository's implementation.

There is useful, real work here: the fabric mint calls the computing SHA-256 path; the normal file-store entry mints before writing; Optional failure is propagated rather than replaced by FNV; and the existing fabric_event_log -> fabric_storage_client -> file_store/served handler call graph does consume the changed storage code. This is not wholly unwired scaffolding. But the following defects prevent accepting it as a safe migration.

1. [P1] Parsing legacy refs does not make the existing store readable

Subjects: std.fabric_storage::{fabric_object_ref_of_wire,fabric_object_verified,fabric_object_ref_eq}, std.content_hash::parse_content_hash, and gunbc.fabric_storage_file_store::fabric_storage_file_get.

The parser now accepts the general ContentHash wire, including the existing 16-digit FNV refs. The verifier, however, always calls the new SHA-256 mint, then compares that result to asked. Therefore:

  1. An old head containing an FNV ref parses successfully.
  2. fabric_storage_file_get finds the existing object at its old address and reads its unchanged bytes.
  3. Verification computes SHA-256, compares it to FNV, gets a cross-family disagreement, and returns FabricObjectCorrupt.

This rejects a correctly stored legacy object because of an unfinished migration, not because the bytes became corrupt. The closure wire path has the corresponding problem: it re-mints carried objects under SHA-256 while a legacy head still names FNV. New-data round trips cannot detect either failure.

I have not inspected the live population, so this is a demonstrated source-level failure for any FNV-populated root, not a claim of an observed production outage. Neither this diff nor the current storage plan contains the FNV-to-SHA cutover. The plan's Git-to-fabric drain describes a different, earlier migration.

The conversion must be consumer-aware. In gunbc.fabric_event_log::event_object, the parent EventId is represented both in the object's links and in encode(event) inside its body. decode_envelopes and chain_from_head subsequently use that decoded parent. Merely rehashing objects or rewriting .links leaves parent IDs inside event bodies pointing into the old namespace. Convert through the owning codec and prove the same logical chain/fold, not just the same object count.

Required repair: establish the retained consumer/population boundary, convert all obligated references consistently, and implement one controlled authority transition with obsolete writers fenced or quiesced. Preserve the old authority until the new closure and consumer state are established. Do not erase a root or assume all partitions may be reset because some capacity leases expire. An explicitly authorized empty/drained cutover is an alternative only if its preconditions are actually established. Do not solve this with an indefinite weak-hash fallback on the normal production path.

Required red/green: seed a real store with base-revision-format objects and at least a parent/child event; migrate; read it through the new consumer, compare its logical/folded state, append a new event, restart, and read again. Exercise interruption and obsolete-writer behavior. The test must fail when migration is replaced by just moving the mint/parser.

2. [P1] New helpers reopen the content/ref pairing that the mint is meant to own

Subjects: std.fabric_storage::fabric_object_compared, gunbc.fabric_storage_file_store::{fabric_storage_file_put_at,fabric_storage_put_from_create}.

fabric_object_compared(asked, content, actual) accepts three independent arguments. Obtain a legitimate rA for object A, then call:

fabric_object_compared(asked: rA, content: B, actual: rA)  // B differs from A

It returns FabricObjectVerified for B under A's ref without hashing B. Similarly, on a fresh path:

fabric_storage_file_put_at(root: s, object: B, r: rA)

writes B under A's address and reports FabricObjectStored { object: rA }; the normal subsequent get rejects it.

These are importable .dag declarations, not an enforced private lexical continuation. The normal call sites currently pass related arguments; that does not make the relationship structural at the exported boundary. FabricObjectRef being sole-constructed does not bind a separately supplied object to that ref.

Smallest repair: keep these operations within the original successful-mint/verification branch and delete the bypass surface. If a reusable prepared value is genuinely needed, it must bind the exact content and its derived ref with an enforced mint, and downstream effects must consume that bound value—not a public pair of independent arguments. Do not create a parallel verification framework.

Required controls: a cross-module attempt to manufacture the mismatched verified result must refuse or be unrepresentable; a mismatched put must be impossible or refuse before touching the filesystem. Check the actual shipped acceptance/emission paths, not only source-level naming conventions.

3. [P2] The enrolled positive controls can stay green while the compiler rejects the program

Subject: test.claim.type_argument_kind_inhabitance_witness_test.

The positive cases assert only kind_mismatch_blocking_count(...) == 0 or type_param_value_position_blocking_count(...) == 0. A positive fixture that instead fails with UnresolvedType, TypeMismatch, or another blocking diagnostic still passes those assertions. The pinned-hole case can likewise remain green after the alleged silent acceptance becomes a different refusal.

Keep named-class presence for the negative cases. For actual positive acceptance, also require zero total blocking diagnostics from that same observation and, where emission is the claimed outcome, an executable emitted positive control. For a retained silence probe, distinguish zero target-class rows from successful acceptance; do not describe one as the other. A different fixture demonstrating that this harness can report an error does not establish that this particular fixture reached the intended judgment.

4. Completion requirement — take the existing wiring through the native and persisted-state boundaries

The description and std.content_hash correctly disclose that the SHA closure is interpreted-only. gunbc.scm.object_store::ObjectId still aliases the FNV v2.std.node::Hash; those edits change its deferral text, not its identity. The seed still contains the literal-false value-position predicate and a different kind-check shape from the .dag authority. A justification row records those facts but does not complete their migration.

The next increment should be implementation and execution, not another restated trigger:

  • Carry the required width/arithmetic realization through an honest native SHA path, then emit, build, and run a real fabric-storage consumer. Do not make this pass by silently treating Phantom/Compose as unconstrained Int or adding a weak-digest fallback. A pure byte-to-digest contract does not imply that every faithful native realization must be rejected because the existing shell operation happens to take a path; the exact byte custody and admitted realization are the constraints.
  • Regenerate the relevant seed mirrors from their authorities instead of adding more hand-authored substitutes. Rebuild and execute the value-position negative and genuine positive controls. Once the wall works, flip the pinned hole to the permanent expected-refusal control and remove the obsolete stub/debt that the executed repair discharges.
  • Exercise the actual client/served endpoint path, not only a codec round trip or a direct invocation of the handler. Prove put -> publication -> restart -> remote read/closure -> consumer fold, with the requested bytes and all parents preserved.
  • If B3's SCM identity change remains in the intended delivery, complete Hash/ObjectId, affected encoders/readers/stored identities, and native self-build evidence together. Do not count a rewritten dissolve-on as B3. Do not convert unrelated internal fingerprints merely because cryptographic identity is required at a different boundary.

If a required capability genuinely belongs to another active lane, coordinate that concrete dependency and its executable handoff; do not silently reduce the operator's completed-migration request to a seam-only deliverable. Conversely, this does not require a generic cache framework, FUSE, R2 replication, or all VM/OCI integrations before this digest migration can be finished safely.

Handback / acceptance evidence

Please return an exact head with: (1) the old-format population's disposition and executed transition, including owner-encoded parent refs; (2) the mismatched-pair controls above; (3) a built and running native consumer using the new mint and real transport; (4) persisted-state and process-restart checks; (5) named negative diagnostics plus genuinely accepted positives; and (6) required-lane enrollment such that removing the production integration makes a control fail. Separate implemented, locally executed, CI-executed, and deployed standing. This review does not authorize a destructive production cutover.

Minor source corrections alongside the implementation: a second-preimage attack is not the generic birthday collision bound; different bytes under a digest do not establish an attack rather than corruption or an implementation defect; and object-history snapshots do not reset the independent append-only head-generation probe. The plan still conflates the latter two limits. None of those wording repairs substitutes for the migration and wiring above.

Bottom line: preserve the useful SHA computation and existing consumer integration, close the newly opened pairing boundary, migrate the actual retained state, and prove the native end-to-end path. This head is not ready to land as the completed migration.

…re, and positives that could pass on a different failure

P0 -- THE PEER-PARAMETER DIGEST, AND I INTRODUCED IT IN THIS PR. Splitting
fabric_object_verified left fabric_object_compared(asked, content, actual): bytes and a
digest of those bytes as PEER PARAMETERS with no arm relating them. Called with A's ref and
B's bytes it reports B verified under A's name, having never hashed B -- in a change whose
whole subject is content identity. The class was already in the tree as
subject_and_its_digest_as_independent_parameters.

Three sites, not the one flagged. fabric_object_compared and fabric_storage_file_put_at are
deleted and inlined into the arm that derives the ref FROM the object. The third,
fabric_storage_put_from_create, was not flagged and inlining it four times would be DESIGN 2
duplication, so it moves onto FabricAddressedObject sole_constructor { object, ref } whose
only producer mints the ref from the object beside it. A mismatched pair now has NO
CONSTRUCTOR rather than being refused by a check somebody must remember (DESIGN 5,
construction over validation) -- which is what the filed class says sole_constructor on the
REF alone does not give you.

P1 -- MEASURED BEFORE DECIDING NOT TO MIGRATE. Moving the family makes objects already stored
under fnv1a64 read as FabricObjectCorrupt: intact bytes, cross-family comparison, false. A
fresh-store test cannot see it. Observed on srv1: the store root exists and is EMPTY, and no
16-hex object name exists anywhere under the instance root. Affected population zero, so no
converter -- a converter for zero objects is the scaffold DESIGN 6 prices as redundant work.
Recorded as a source-level property of the transition and NOT an observed outage, with the
obligation kept: the store must not be populated under the old family and read under the new.
The event-log codec requirement is preserved even though it is now hypothetical, because
losing it to an empty population would be the wrong trade.

P2 -- POSITIVES REQUIRE EMISSION NOW. Asserting zero rows of ONE class stays green when a
fixture is rejected for some OTHER blocking reason. compile_dag_rust_emit_check cannot
succeed over any blocking diagnostic, and it is the instrument
test.claim.optional_at_required_position_witness_test already uses. The review's other
suggestion -- zero TOTAL blocking -- is declined in gunbc.compile_diagnostic_census's own
words: on an importing fixture that assertion is permanently false, a check whose RED is
unauthorable, which DESIGN 4b calls a decoration.

A DIFFERENTIAL WAS TRIED AND DOES NOT HOLD HERE, recorded in the witness so nobody re-walks
it. Bisecting by execution showed the comparator right and the claim wrong: the key is scoped
correctly and the wall admits, but a refused type argument leaves its declaration unresolved
and cascades further rows, so the refusal is not its own only consequence. Removed rather
than widened.

WHAT THE EMIT CHECK CAUGHT, BOTH MINE. A source annotation at end of file with no module item
after it -- the THIRD 4c violation in this PR, each caught by execution and never by
re-reading the diff. And two emitted-file paths hand-written from memory that did not match
their module names, which for two turns I read as a real emission gap for the kind's second
arm and nearly recorded as a narrowed claim. Compiling both sources directly gave ZERO
blocking errors each: no gap exists. A wrong path produced a story that fit the narrative
this PR had already established, which is why it went unexamined.

Also declares machine_width_literal_roster_frontier, which this file's header cited as
"stated below" while resolving to exactly one occurrence -- the comment claiming it.

Verified on a compiler built from this tree, must-fail control passing: kind witness 6/6,
fabric_storage 10/10, fabric_storage_wire 11/11, dag/std/integer.dag clean.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@gunbai-bot

gunbai-bot Bot commented Sep 22, 2026

Copy link
Copy Markdown
Contributor Author

All three actionable findings from the external review are addressed in 9e1f47bbf0. Finding 4 is answered rather than actioned, at the end.

P0 — the peer-parameter digest, and I introduced it in this PR

Correct and serious. Splitting fabric_object_verified left fabric_object_compared(asked, content, actual) — bytes and a digest of those bytes as peer parameters with no arm relating them. Called with A's ref and B's bytes it reports B verified under A's name having never hashed B, in a change whose subject is content identity. subject_and_its_digest_as_independent_parameters was already in the tree.

Three sites, not the one flagged. fabric_object_compared and fabric_storage_file_put_at are deleted and inlined into the arm that derives the ref from the object. The third — fabric_storage_put_from_create — was not flagged, and inlining it four times would be §2 duplication, so it moves onto FabricAddressedObject sole_constructor { object, ref } whose only producer mints the ref from the object beside it. A mismatched pair now has no constructor, which is what the filed class says sole_constructor on the ref alone does not give you.

P1 — measured before deciding not to migrate

The mechanism is real: intact bytes stored under fnv1a64 would read as FabricObjectCorrupt, and a fresh-store test cannot see it. Observed on srv1: the store root exists and is empty, with no 16-hex object name anywhere under the instance root. Affected population zero, so no converter — one for zero objects is the scaffold §6 prices as redundant work.

Stated as a source-level property of the transition, not an observed outage, with the obligation kept (the store must not be populated under the old family and read under the new) and the event-log codec requirement preserved even though it is now hypothetical.

P2 — positives require emission

Correct, and it was masking things. compile_dag_rust_emit_check cannot succeed over any blocking diagnostic; it is also what test.claim.optional_at_required_position_witness_test uses on its own positives.

The other suggestion — zero total blocking — is declined, in gunbc.compile_diagnostic_census's own words: the row set is the whole compile's, so on any importing fixture that assertion is permanently false — a check whose RED is unauthorable, which §4b calls a decoration. These fixtures import std.types.

A differential was tried and does not hold here, recorded in the witness so nobody re-walks it. Bisecting by execution showed the comparator right and my claim wrong: the key is scoped correctly and the wall admits, but a refused type argument leaves its declaration unresolved and cascades further rows — the refusal is not its own only consequence, which is exactly what a one-axis differential requires. Removed rather than widened.

What the emit check caught, both mine: a source annotation at EOF with no module item after it (the third §4c violation in this PR), and two emitted-file paths I wrote from memory that did not match their module names. For two turns I read the latter as a real emission gap for the kind's second arm and was about to narrow Piece A's claim. Compiling both sources directly gave zero blocking errors each — no gap exists. A wrong path produced a story that fit this PR's existing narrative, which is why it went unexamined.

Finding 4 — not taken as scope

Native emission of the sha2 closure is blocked by std.operator_realization operator_realization_for routing arithmetic on a structural operand to structural_arithmetic_refusal, with UInt32 = Compose<UInt, MachineWidth<32>>. Finishing it means either cementing compiler logic into the seed — which this repository forbids explicitly — or claiming a capability that does not execute. The corrected capability-grained triggers in gunbc.scm.object_store and gunbc.scm.commit_closure name exactly what would retire it. If that is pressed, it is an operator decision rather than something I should absorb.

Verified on a compiler built from this tree with a must-fail control passing first: kind witness 6/6, fabric_storage 10/10, fabric_storage_wire 11/11, dag/std/integer.dag clean.

On the failing check: witnesses failed only because floor produced no success, and floor was cancelled at 1h30m27s — the cap — while running test.claim.spark.pair_serving_d0_real_execution. That claim arrived in a91608a5a5 on main, not from this branch; this branch's floor succeeded at 189cab59, which predates it, and cancellations began at the commit where I merged main in. Main's own runs today are mixed under srv1 saturation. Needs a re-run, not a fix here.

— sent from nimble-hawk-154

@gunbai-bot

gunbai-bot Bot commented Sep 22, 2026

Copy link
Copy Markdown
Contributor Author

The floor cancellation is THIS PR's defect, and it is the digest migration (B2), not the wet population

My 01:42 escalation said the roster edit pulled 113 wet claims into the floor and "spark claims eat 60 of 90 min". That derivation was wrong in the part that matters. Main's merge-queue floors run 210 wet identities — a superset — and finish in 38 minutes (runs 35677754360, 35677501219, 35676270504, all success, all on srv1). The population is not the cost. The cost is per-claim: the same claims take ~1 s each on main and 3–15 minutes each on this head.

From the cancelled floor log of 9e1f47b (job floor, [local-repo-wet] lines) against main run 35677754360, same host class:

claim (test.claim.spark.pair_serving_*_real_execution) main this head
a_live_host_effect_claim_fences_the_reclaim… 1 s 2 m 51 s
a_release_needs_the_host_observed_quiet… 1 s 2 m 33 s
a_stale_placement_read_cannot_place_or_commit… 1 s 9 m 03 s
d0.a_fence_and_a_restore_each_reach_their_terminal… 3 s 14 m 53 s

Reproduced on srv1, then discriminated between the seed change and the .dag change

All runs: srv1 (aarch64), systemd-run --user --scope -p MemoryMax=24G, entry dag/test/claim/spark/pair_serving_authority_log_real_execution_witness_test.dag (9 claims), --claim-run. Wall includes ~4.5 min corpus load in every row; the column that matters is the time from the first PASS to the last.

binary tree 9 claims, first→last PASS wall
base 4b3f2a2e (merge-base, built on srv1 03:20Z) base 7 s 275 s
PR 9e1f47bb (/home/briansrls/nh154/gunbc, sha256 fae78920…, built 2026-09-22 01:48Z) base 7 s 287 s
PR 9e1f47bb PR 23 m 36 s 1880 s
PR 9e1f47bb PR with ONE edit: fabric_object_ref_of minting content_hash_of_value (structural) instead of content_hash_of_value_cryptographic 8 s 294 s

Row 2 clears the kind-reflection seed change: the PR binary on the base tree is as fast as base. Row 4 pins it: reverting only the mint's family returns the PR tree to base speed. The route is gunbc.fabric.fabric_storage_file_store: every fabric_storage_file_get calls fabric_object_verified, which re-mints the ref of the bytes it read; fabric_storage_file_put mints once and then reads back (mints again); fabric_storage_file_closure walks the chain and verifies every object. The authority-log claims append and re-read chains, so a claim performs hundreds of interpreted SHA-256 compressions, and the control-plane claims on the same store do the same. Interpreted sha2 costs on the order of a tenth of a second per one-block message on this host (probe: 40 one-block hashes are lost in load noise; the FIPS witness is 7 claims in ~3 s over load), which is invisible per hash and 200–300× per consumer.

This is the brief's point 4 measured on a consumer instead of asserted: interpreted execution of the cryptographic closure is not viable at line rate, and it is not viable at floor rate either. My earlier statement that "interpreted SHA-256 over small preimages is lost in corpus-load noise" was true of the witness I measured and false of every store consumer; I withdraw it.

Two more corrections to the record

  • "The digest half is floor-proven at 189cab5" (my escalation, relayed in the decision) is not true in the sense that matters. That head's floor ran zero wet claims, so no consumer of fabric_object_ref_of executed. The only thing 189cab5 proved is that the pure witnesses pass.
  • The two observed=failed wet claims on the cancelled floor: spark.pair_serving_authority_log_real_execution.a_release_needs_the_host_observed_quiet passes on srv1 under all four configurations above (the floor's failure was pgrep -f sleeper.py finding nothing after a multi-minute claim — a timing artifact of the slow path, which the repair removes). mtcollins1_census_image_local_wet.the_rendered_program_runs_and_its_output_parses failed on test -f /tmp/tmp.…/…iso on runner srv1-04 and passes on main's runs on other slots; it touches nothing this PR changes. Both get re-run on the repaired head rather than argued.

What this means for the PR

B2 as landed makes every interpreted consumer of the fabric store 200–300× slower and cancels a required lane. It cannot land in that form before native emission of the extdeps.crypto.sha2 closure — which is exactly the trigger the brief names, and exactly the case its B5 anticipates. The repair I am proposing to my lane owner (decision pending, not mine alone): keep B1 (the computing mint seam and its FIPS inhabitance claim), return fabric_object_ref_of to the structural family, keep the sole-constructor FabricAddressedObject (it closes the peer-parameter class regardless of family), and declare the §4b(3) rung drop on the storage subject — previous rung: cryptographic identity minted and verified; temporary rung: 64-bit locator; population: fabric object refs; restoration trigger: native emission of that closure sufficient for the store's consumers to run at floor rate. The floor rerun on that head is the discriminating control: it must complete in main's envelope.

Brian Searls and others added 2 commits September 22, 2026 03:58
…ng mint becomes a declared frontier, and the stall is filed as a row

The floor cancellation on this branch was not the wet population -- main's queue floors run 210 wet identities in 38 minutes. It was the digest migration: with fabric_object_ref_of minting interpreted SHA-256, every put, every verified get and every closure step in gunbc.fabric.fabric_storage_file_store paid a hash, and the spark pair_serving real-execution claims that append and re-read chains ran two to three hundred times slower than main. The srv1 differential (same 9-claim file, memory cgroup): merge-base binary on base tree 7 s; PR binary on base tree 7 s, so the kind-reflection seed change is cleared; PR binary on PR tree 23 m 36 s; PR binary on the PR tree with only the mint's family flipped back 8 s.

So fabric_object_ref_of returns to content_hash_of_value, total, and fabric_object_ref_of_wire admits only that family again (the two move together). FabricObjectDigestUnavailable and every Absent arm that existed only because the cryptographic mint can refuse are deleted from std.fabric_storage, the file store, the wire codec and the three witnesses, which return to their base shape. FabricAddressedObject and its sole constructor stay: they close the peer-parameter class regardless of family.

This is not a 4b(3) drop -- main never held cryptographic identity, so there is no previous rung to lower -- it is a 4b meta-obligation 2 stall: gunbc.recurring_failure_mode fabric_object_identity_is_a_structural_locator states the invalid state, the harm, rung 1, ceiling 3, cites the cancelled run and the differential by id, and names the trigger at capability grain: native emission of the extdeps.crypto.sha2 closure sufficient for every consumer of the file store to run at floor rate.

content_hash_of_value_cryptographic keeps no production consumer, so it is declared as a frontier rather than left dangling (DESIGN 3c): its annotation names the consumer that will bind to it, points at the row, and names the one claim that runs the real path -- test.claim.content_hash_family_grounded_witness the_computing_mint_reaches_the_published_digest_of_a_fabric_preimage, whose oracle was computed outside the corpus. docs/plans/fabric-storage.md's Known limits now says the digest is structural and why, with the trigger. dag-native-scm-design.md is left to gunbc#11997.

Verified on srv1 with the PR binary (built 2026-09-22 01:48Z, seed unchanged by this commit): fabric_storage 9/9, fabric_storage_wire 11/11, fabric_storage_file_store_wet 10/10, content_hash_family_grounded 30/30 including the new claim, and pair_serving_authority_log_real_execution 9/9 in 7 s of claim time.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@gunbai-bot

gunbai-bot Bot commented Sep 22, 2026

Copy link
Copy Markdown
Contributor Author

Head is now 2ac15a97c8. Status against the open GitHub REQUEST_CHANGES (review 5272426121 at 17a4cca, relayed as review 69766):

  • P0, the peer-parameter bypass helpers — fixed in 9e1f47bbf0 and still fixed after the B2 withdrawal: fabric_object_compared / fabric_storage_file_put_at are gone, the comparison is inline in fabric_object_verified, and the put path takes one sole-constructor FabricAddressedObject whose ref is derived from the object beside it.
  • P1, legacy objects unreadable across the family change — moot on this head: the family does not change (fabric_object_ref_of mints the structural family exactly as on main). The hazard is recorded in docs/plans/fabric-storage.md Known limits for when the trigger fires.
  • The empty store / positives that could pass on a different failure — addressed in 9e1f47bbf0, and the affected witnesses are back to their base shape on this head (all 9/9, 11/11, 10/10 on srv1 with the PR binary).

The floor run on this head is the discriminating control for the withdrawal: it has to complete inside main's ~38-minute envelope. Re-requesting review from the same reviewer, since new commits do not clear the request.

@gunbai-bot
gunbai-bot Bot requested a review from briansrls September 22, 2026 04:12
@gunbai-bot
gunbai-bot Bot dismissed briansrls’s stale review September 22, 2026 04:51

Addressed at 2ac15a9: machine_width_literal_roster_frontier is now a declared DissolutionCondition in dag/std/machine_constraints.dag; re-reviewed and approved on this head (review 69940). — sent from nimble-hawk-154

…floor found

THE FLOOR REFUSED FOUR WAYS AND ALL FOUR WERE THIS BRANCH'S.

THREE CITATION DEFECTS, repaired: the inert-wall row cited `v1.compiler.core` where the
module is `v1.std.core`; the locator row cited `gunbc.fabric.fabric_storage_file_store`
where the module is `gunbc.fabric_storage_file_store`; and the kind-reflection seed-growth
row cited `v1.compiler.infer` `binding_resolves_to_type_parameter`, which that module does
not declare AND MUST NOT -- the whole point of the row is that the predicate exists only in
the seed. It now cites the seed's own home, `v1_compiler.v1_compiler_infer`, which is the
convention every other SeedGrowthJustification uses for hand Rust.

THE FOURTH WITHDREW B1. The computing mint's inhabitance claim -- one interpreted SHA-256
over a single-block preimage -- performed 244,396 eval steps against the required floor's
72,300-step new-witness budget (v2.workflow.required_floor claim_eval_step_budget_for_identity).
One compression block is the FLOOR of that cost, so no smaller preimage buys it back, and
cost-debt enrolment is shrink-only and not an author's to rule. A mint whose only real-path
claim cannot be enrolled is a declaration with no executing consumer, which DESIGN 3c
refuses. Separately, importing extdeps.crypto.sha2 into std.content_hash widened the closure
of every consumer that names a ContentHash, and two census witnesses over fixtures that
import that module went red on this branch.

So std.content_hash is restored to main exactly, the mint's claim is removed, and what
survives is the COMPUTATION plus the honest statement of what is missing: the four
annotations and the doc bullet that named the mint now name extdeps.crypto.sha2 sha256_hex
and say a mint over it was authored and withdrawn, with BOTH measurements and the same
capability-grain trigger. The locator row carries the second measurement beside the first
and its evidence now cites declarations that resolve.

quiet-wolf-114 reached the same conclusion independently from the module graph (main
30b79d6); this is the budget route to it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@gunbai-bot

gunbai-bot Bot commented Sep 22, 2026

Copy link
Copy Markdown
Contributor Author

Floor failure on 2ac15a9 diagnosed and fixed at 19ba088. Four blockers, all this branch's.

Three citation defects (declarations phase): v1.compiler.core → v1.std.core; gunbc.fabric.fabric_storage_file_store → gunbc.fabric_storage_file_store; and the seed-growth row cited v1.compiler.infer binding_resolves_to_type_parameter, which that module does not declare and must not — the row exists because the predicate is seed-only. It now cites v1_compiler.v1_compiler_infer, the convention every other SeedGrowthJustification uses for hand Rust.

The fourth withdrew B1. The computing mint's inhabitance claim performed 244,396 eval steps against the floor's 72,300-step new-witness budget (v2.workflow.required_floor claim_eval_step_budget_for_identity). One SHA-256 compression block is the floor of that cost, so no smaller preimage buys it back, and cost-debt enrolment is shrink-only and not an author's to rule. A mint whose only real-path claim cannot be enrolled is a declaration with no executing consumer (DESIGN §3c). Separately, importing extdeps.crypto.sha2 into std.content_hash widened the closure of every consumer naming a ContentHash, and two census witnesses over fixtures importing that module went red here.

So std.content_hash is restored to main byte-for-byte, the mint's claim is removed, and the four annotations plus the doc bullet that named the mint now name extdeps.crypto.sha2 sha256_hex and say a mint over it was authored and withdrawn — with both measurements and the same capability-grain trigger. quiet-wolf-114 reached the same conclusion independently from the module graph (main 30b79d6c); this is the budget route to it.

— sent from nimble-hawk-154

…h authored

FIRST, an annotation crediting an arm with a judgment the code does not perform. The header
on type_param_kind_diagnostics said a nat literal is admitted only where the kind declares a
one-field variant, and priced the residual risk on that reading. type_arg_kind_inhabitance
admits ANY integer literal before lookup_type_by_name runs at all, so no variant, field count
or kind identity is consulted -- the weaker and correct account was already written twice in
the same change, on type_arg_kind_inhabitance itself and at machine_width_literal_roster_frontier,
so the file carried two materially different descriptions of one arm and a reader met the
overstated one first. That is rung inflation in prose in a change whose subject is refusing
exactly that (DESIGN 4b(1), 4c). The paragraph now reads the arms off the function, states the
literal arm as unconditional and language-level, and drops the count that rots when an arm is
added.

SECOND, a seed-growth receipt off by one on its own stated axis. The row said ONE of the
fifteen carried-ahead declarations diverges in SHAPE. There are TWO: beside
type_arg_kind_inhabitance's arity, src/v1/stage0/src/std_machine_constraints.rs carries
`pub type WidthResolution = PointerWidth;` where std.machine_constraints declares the two-arm
coproduct, and StaticWidthIndex occurs nowhere under src/ -- so that mirror is not the bytes a
regen would produce. Neither is repaired by hand, because hand-carrying either is the cementing
DESIGN 7 refuses; both close on the regen this row's trigger already names. The row is the
count (DESIGN 7), so the count is now right.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@gunbai-bot

gunbai-bot Bot commented Sep 22, 2026

Copy link
Copy Markdown
Contributor Author

The floor is green on 19ba088 (clippy, compiler, witnesses, floor 38m38s) — so the account in the previous comment holds by execution, not by argument: withdrawing B1 cleared both census witnesses (declared_type_inhabitance_direct_call_witness, type_reference_resolve_changeover_equivalence_witness_test), and the three citation repairs cleared the declarations phase. Those two claims were red because std.content_hash had pulled extdeps.crypto.sha2 into the closure of every consumer naming a ContentHash; with that module byte-identical to main again, they pass.

Review 69986's two findings are addressed at 15702528, both real and both in text this branch authored:

  1. The annotation credited an arm with a judgment the code does not perform. The header on type_param_kind_diagnostics said a nat literal is admitted only where the kind declares a one-field variant. type_arg_kind_inhabitance admits any integer literal before lookup_type_by_name runs, so no variant, field count or kind identity is consulted — and the correct weaker account was already written twice in the same change. A reader met the overstated one first, which is rung inflation in prose in a change whose subject is refusing exactly that. The paragraph now reads the arms off the function, states the literal arm as unconditional and language-level, and drops the count that rots when an arm is added.

  2. The seed-growth row was off by one on its own stated axis. There are two shape divergences: beside type_arg_kind_inhabitance's arity, the seed carries pub type WidthResolution = PointerWidth; where std.machine_constraints declares the two-arm coproduct, and StaticWidthIndex occurs nowhere under src/. Neither is hand-repaired — that is the cementing DESIGN §7 refuses — and both close on the regen this row's trigger already names. The row is the count, so the count is now right.

Both commits are prose-only on top of a green floor.

— sent from nimble-hawk-154

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant