Skip to content

Distinct nominal product types silently inhabit a direct-call argument - #9608

Merged
briansrls merged 3 commits into
mainfrom
session/bold-bear-763
Aug 28, 2026
Merged

briansrls merged 3 commits into
mainfrom
session/bold-bear-763

Conversation

@gunbai-bot

@gunbai-bot gunbai-bot Bot commented Aug 28, 2026

Copy link
Copy Markdown
Contributor

DESIGN section 4b floor: "values inhabit declared types". Measured on a
compiler built from this tree, each fixture its own entry compile, every
one of these reported ZERO blocking diagnostics:

Alpha at a Beta parameter distinct constructors
Wrapper at a Wrapper parameter one constructor, distinct
type arguments, K phantom
Alpha at a Beta parameter neither applied, actual a call

THE BRIEF FRAMED THIS AS GENERICS DEFEATING THE CHECK. IT IS NOT. The
third case has no type parameters anywhere and is admitted for the same
reason as the first two, and the case that DID refuse -- Alpha{...} at a
Beta parameter -- refused because its actual is a record LITERAL, not
because it is non-generic. Read off the producers rather than inferred
from the silence: structured_application_site_type_mismatch is the only
judgment that ever compared product heads here and it fires solely for an
ExprRecordLit actual; kernel_value_declared_type_mismatch returns false
at its first gate for a non-kernel actual; coproduct_payload_where_parent
_required leaves its body unless the formal's declaration is a coproduct.
A call actual at a product formal was judged by nothing. Root-cause count:
ONE, and one arm closes all three.

THE ARM lives in declared_type_inhabitance, the single decision procedure,
and consumes v1.compiler.type_head_exposure rather than deriving a second
head view. Two new refusal reasons. Every guard fails open: containers,
callables, optionals, anonymous records, unresolvable names, arity
mismatch and type-variable arguments are all admitted.

node_type_shape now renders an application's arguments. Without it the
type-argument refusal printed declared 'Node(Wrapper)', produced
'Node(Wrapper)' -- typed and located and carrying nothing actionable.

FALSE-POSITIVE CENSUS, because this relation runs on EVERY module: the
direct-call TYPE judgment's v2.* exemption (module_skips_direct_call_arg
_check) does not cover this path. src/v2/compiler/00_compile.dag, the
widest required entry, is 0 blocking / 2167 advisory with the arm on --
byte-identical to the same binary with the arm disabled by env var, a
one-variable A/B rather than two builds. dag/std/abi.dag, src/v1/04_infer
.dag and src/v1/compile.dag are 0 blocking.

TWO CUTS OF THIS ARM PRODUCED 15 FALSE POSITIVES ON THAT ENTRY and both
refutations are recorded in the annotation rather than quietly fixed:
asking whether an argument's NAME resolves in the type environment is not
a type-variable test, because type PARAMETERS are bound there and S and R
answer exactly as KindA does; and the exposure's constructor_identity is
keyed on the REFERENCING file, so one Measure read from two modules
yields two identities and can never establish agreement across modules.
The test that works is that a type variable resolves to nothing.

Five witness arms: three discriminating REDs against the prior compiler
and two anti-refuse-everything controls, all verified by execution. The
enrolled expected-red roster needs no edit -- the peano arm is untouched
by construction (declared_realizes_as_kernel_numeric runs ahead of this
predicate) and measured unchanged.

Co-Authored-By: Claude Opus 5 noreply@anthropic.com

Files

  • src/v1/04_infer.dag — the arm in declared_type_inhabitance, two new InhabitanceRefusalReason members, and the guards
  • src/v1/04_types.dag — node_type_shape renders an application's arguments
  • dag/test/claim/declared_type_inhabitance_direct_call_witness_test.dag — three discriminating REDs, two controls
  • src/v1/stage0/src/v1_compiler_infer{,_types}.rs — regenerated mirrors (claim_executor --required-regen, not hand-written)

Measurement table

Every refusal is DeclaredTypeNotInhabited at the exact direct-call argument, caret on the actual.

fixture before after
Alpha<String> at Beta<String> 0 1 — declared Node(Beta<Primitive(String)>), produced Node(Alpha<Primitive(String)>)
Wrapper<KindB> at Wrapper<KindA> (no brands) 0 1 — declared Node(Wrapper<Product(KindA)>), produced Node(Wrapper<Product(KindB)>)
same, actual a variable not a call 0 1
Alpha at Beta, non-generic, actual a call 0 1
Alpha{...} record literal at Beta 1 2 (pre-existing type mismatch + this arm)
bare "plain" at Beta<String> 1 1 (unchanged)
matching applied type 0 0 (unchanged)
type-variable type argument 0 0 (guard control)

What this does NOT close

  • A record LITERAL of a distinct constructor at an APPLIED formal (Alpha { a: "z" } at Beta<String>) is still admitted. That is structured_application_site_type_mismatch's territory — it peels the formal but the literal carries no type arguments — and it is a separate seam with its own evidence, not silently folded in here.
  • The arm judges ONE level of type argument. A nested application argument is left unjudged and says so.
  • Ten of the twelve DeclaredTypePosition members still have no obligation producer; this changes the relation, not the wiring.

Brian Searls and others added 3 commits August 28, 2026 15:24
DESIGN section 4b floor: "values inhabit declared types". Measured on a
compiler built from this tree, each fixture its own entry compile, every
one of these reported ZERO blocking diagnostics:

  Alpha<String>  at a Beta<String>  parameter   distinct constructors
  Wrapper<KindB> at a Wrapper<KindA> parameter  one constructor, distinct
                                                type arguments, K phantom
  Alpha          at a Beta          parameter   neither applied, actual a call

THE BRIEF FRAMED THIS AS GENERICS DEFEATING THE CHECK. IT IS NOT. The
third case has no type parameters anywhere and is admitted for the same
reason as the first two, and the case that DID refuse -- Alpha{...} at a
Beta parameter -- refused because its actual is a record LITERAL, not
because it is non-generic. Read off the producers rather than inferred
from the silence: structured_application_site_type_mismatch is the only
judgment that ever compared product heads here and it fires solely for an
ExprRecordLit actual; kernel_value_declared_type_mismatch returns false
at its first gate for a non-kernel actual; coproduct_payload_where_parent
_required leaves its body unless the formal's declaration is a coproduct.
A call actual at a product formal was judged by nothing. Root-cause count:
ONE, and one arm closes all three.

THE ARM lives in declared_type_inhabitance, the single decision procedure,
and consumes v1.compiler.type_head_exposure rather than deriving a second
head view. Two new refusal reasons. Every guard fails open: containers,
callables, optionals, anonymous records, unresolvable names, arity
mismatch and type-variable arguments are all admitted.

node_type_shape now renders an application's arguments. Without it the
type-argument refusal printed declared 'Node(Wrapper)', produced
'Node(Wrapper)' -- typed and located and carrying nothing actionable.

FALSE-POSITIVE CENSUS, because this relation runs on EVERY module: the
direct-call TYPE judgment's v2.* exemption (module_skips_direct_call_arg
_check) does not cover this path. src/v2/compiler/00_compile.dag, the
widest required entry, is 0 blocking / 2167 advisory with the arm on --
byte-identical to the same binary with the arm disabled by env var, a
one-variable A/B rather than two builds. dag/std/abi.dag, src/v1/04_infer
.dag and src/v1/compile.dag are 0 blocking.

TWO CUTS OF THIS ARM PRODUCED 15 FALSE POSITIVES ON THAT ENTRY and both
refutations are recorded in the annotation rather than quietly fixed:
asking whether an argument's NAME resolves in the type environment is not
a type-variable test, because type PARAMETERS are bound there and S and R
answer exactly as KindA does; and the exposure's constructor_identity is
keyed on the REFERENCING file, so one Measure read from two modules
yields two identities and can never establish agreement across modules.
The test that works is that a type variable resolves to nothing.

Five witness arms: three discriminating REDs against the prior compiler
and two anti-refuse-everything controls, all verified by execution. The
enrolled expected-red roster needs no edit -- the peano arm is untouched
by construction (declared_realizes_as_kernel_numeric runs ahead of this
predicate) and measured unchanged.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…r arm twice

Requested by the lane that found the original defect, whose objection to
omitting it was correct and which I had answered wrongly.

THE TWO FIXTURES FAIL ON DIFFERENT BUGS, which is the test for whether a
second control earns its place. The Wrapper arm's two candidate arguments
are KindA and KindB -- distinct records with distinct fields -- so any
comparison that reaches representation at all sees a difference. Here the
two candidates are representationally IDENTICAL (DemandKey and WorkKey are
both a single String field) and the parameter they occupy is PHANTOM: K
appears in the parameter list and in no field. The instantiations differ
only nominally, at every level below the type.

The bug this catches and the other does not: "a type argument that does
not affect layout cannot affect inhabitance" is a reasonable-sounding
optimization that would leave the Wrapper arm green while reopening this
class whole.

It is also the production shape. product.fabric's demand/work/attempt/
grant/offer/receipt identities are non-interchangeable ONLY because of a
phantom K in FabricIdentity<P,K>, and there is no runtime fallback:
fabric_identity_eq compares principal and key, so a wrong-kind identity
that reaches a parameter is not merely uncaught downstream -- the equality
CONFIRMS it.

Measured: 1 blocking, at the direct-call argument, caret on the actual --
declared 'Node(FabricIdentity<Primitive(String),Product(DemandKey)>)',
produced 'Node(FabricIdentity<Primitive(String),Product(WorkKey)>)'.

Witness file only; no compiler change, so no mirror regeneration.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…ed argument

The floor lane refused strict preparation with 60 inhabitance diagnostics on
the live corpus, of which 58 were false positives from this branch's new arm.

Root cause, and it refutes the test this branch shipped with: the previous
guard treated "the resolved argument name is empty" as the type-variable
test, generalised from a four-entry census. Over the whole 4181-module
subject, `Measure<Time,S,Nat>`, `MembershipPlan<M,At>`, `ContainmentPath<N>`,
`PackageLockNode<..>` and `Appropriation<Q,S>` resolve their parameters to
nodes that KEEP the authored name, so each parameter rendered as a nominal
head (`Primitive(M)`) and the concrete argument instantiating it was refused
as a distinctness violation -- the substitution that was supposed to happen.
An entry-based census is not a substitute for the floor's subject; it reaches
modules no entry compiles.

The corrected test is structural rather than nominal:
`applied_type_argument_identity_known` now requires the resolved declaration
to be a `Conj` or `Disj` with children. A type parameter's bare-leaf binding
carries no structure, so it is left unjudged; kernel types are admitted by
name as before. This also covers the empty-head class -- an unresolved
declared head answers false and produces no refusal, rather than rendering
ignorance as a confident red.

Measured on the floor's own subject: strict-preparation state=completed,
modules_resolved=4181, 0 inhabitance diagnostics.

Two of the sixty were the wall firing correctly, on
dag/examples/nominal_distinctness_witness -- a RED/GREEN twin pair authored
for exactly this class, consumed by nothing, whose RED half had been
compiling clean. Since strict preparation compiles every module, a module
that must not compile stops the line, so the pair is deleted; a disposition
annotation records that its discrimination now lives in the enrolled fixture
arms, which are re-measured every floor run.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@briansrls
briansrls merged commit fb9fa04 into main Aug 28, 2026
1 of 3 checks passed
@briansrls
briansrls deleted the session/bold-bear-763 branch August 28, 2026 18:13
gunbai-bot Bot pushed a commit that referenced this pull request Aug 29, 2026
…nd its shape rendering: repair two at the source, re-key the spine's frontier row to the shape the renderer now spells

- pid_control_program: curve_points_agree compared ZoneDemandValue through the
  Measure comparator; ZoneDemandValue is deliberately not a Measure, so it gets
  its own agreement fn over the same exact_decimal_equal rule.
- fabric_terminal_contract witness: the positive control's receipt was the one
  inline literal in the file, so P inferred String against the grant's
  NonEmptyStr; hoisted to an annotated data like every sibling.
- unresolved_method_frontier: the spine row's key was measured before #9608
  added the Node(name<args>) arm to node_type_shape; identical receiver, new
  spelling. Re-keyed to the observed shape, which narrows the admission.
  Attribution by run bisection and #9608's own diff, recorded on the row.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KH3G4UzMgvQobyNgHYy4GU
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