Skip to content

Derived-node identity step 2: the generic-identity census (gunbc test //gunbc/instruments:generic-identity-census) - #13290

Merged
gunbai-bot[bot] merged 6 commits into
mainfrom
session/jolly-pike-330-generic-identity-census
Oct 4, 2026
Merged

gunbai-bot[bot] merged 6 commits into
mainfrom
session/jolly-pike-330-generic-identity-census

Conversation

@gunbai-bot

@gunbai-bot gunbai-bot Bot commented Oct 4, 2026 •

Copy link
Copy Markdown
Contributor

Step 2's first task in docs/plans/derived-node-identity-design.md §7: make the measurement an instrument. Scheduled by gentle-dove-36; class gunbc.recurring_failure_mode generic_identity_decided_by_spelling. No edit to v1.compiler.infer.

What it is

v1.tests.claim.generic_identity_census is a pure .dag reader over compile_to_resolved → TypedModule.func_env + TypedModule.items, on the precedent of v1.tests.claim.carrier_realization_census. For every type node the typed graph keeps, it records:

  • which generic-identity mark the node carries: the Node.declaration TypeParameter mark, the TypeVariable mark, both, or neither;
  • how the node is spelled and what it resolves to;
  • for a node carrying another declaration's parameter, one of three positive observations over every leaf of the owner's value arguments: a leaf carries the parameter's mark (foreign_in_a_value_argument, a substitution loss); no leaf carries the mark and none is spelled as the parameter (foreign_in_no_value_argument, unbound for want of an expected type); or a leaf is spelled as the parameter and carries no mark (foreign_value_argument_undecided, claimed as neither cause).

Carriers read, for every signature whether or not it declares a type parameter: a signature's parameter and return types, the three type fields of each ResolvedFormal, and the resolved type of every expression in a function body.

Output grain, and its blind spot

The plan describes an event tap inside unify_generics and substitute_generics_apply. Those are pure folds with no output channel, and adding one would grow the frozen seed to take a measurement. This reads what those folds stored instead.

What it cannot see is a typed population in every receipt, never a zero. generic_identity_unobserved declares four: call-plan formals; the operands and maps of each unification; alias-chain and record-instantiation intermediates; signatures whose formals were never bound. Each row names what would observe it. The driver refuses a receipt that carries no UNOBSERVED row.

The label

gunbc test //gunbc/instruments:generic-identity-census (GenericIdentityCensusProducer). Run by name, on no lane.

  • Standing is decided by six controls over fixtures/generic_identity_census, each a positive observation: a marked signature leaf; a minted container's renamed child; a call whose argument binds the parameter, observed typed Int with no foreign parameter; the unobserved populations being declared; and the two supplied-node controls described under the review section below.
  • A three-valued reading, not a gate: the result carrier of head_of(xs: [1], d: 2) is reported as observed unsubstituted, observed substituted, or carrier unobserved with the reason. It reads observed unsubstituted, typed T today. That is the spelling collision step 2 repairs, so the line is expected to flip and must not hold the standing hostage.
  • Report: tallies over the closure of v2.compiler.compile.

The standing line, each control, the tallies and the unobserved rows are decided in .dag. The host arm reads files, relays text, and maps the first line to a termination. That arm is admitted seed growth, declared in gunbc.generic_identity_census_seed_growth with its trigger.

src/v1/stage0/src/bin/generic_identity_census.rs is transport for any other subject (<receipt.tsv> <subject.dag>...).

Evidence

Executed locally on this head, arm64, with a gunbc built from this tree:

generic-identity-census: STANDING held
generic-identity-census: control marked_signature_leaf: held
generic-identity-census: control minted_container_child: held
generic-identity-census: control bound_call_observed_clean: held
generic-identity-census: control unobserved_populations_declared: held
generic-identity-census: control owner_argument_arms_discriminate: held
generic-identity-census: control missing_carriers_are_unobserved: held
generic-identity-census: reading bound_call: observed substituted, typed Int
generic-identity-census: reading spelling_collision_call: observed unsubstituted, typed T

exit 0, followed by the report lines.

Mirrors: regenerated with claim_executor --regen-round-cost --source-root dag --source-root src/v2 in a standalone clone with its own target directory, looped to rebuild_packages=0. On the first head the only path the round changed was emitted_population.rs; on this head the round reports changed_paths=0.

Review on 1c3f45f (CHANGES REQUESTED), addressed in bc78f54 and d716390

The finding was that the reader turned absence of an observation into positive answers. Three changes:

  1. gi_foreign_reading gained ForeignParameterValueArgumentUndecided; "no marked occurrence" no longer stands for "no occurrence".
  2. ResultCarrierObservation says whether a call's result carrier was seen at all. The bound-call control holds only on a positive observation, and the collision reading is three-valued.
  3. gi_sig_rows reads every signature.

Then, in d716390, each new arm got an executed control over supplied nodes (DESIGN §4b: a state authorable at a fixture boundary is enrolled there):

  • owner_argument_arms_discriminate: one marked parameter, head_of's d: T, is asked about under three owners built from head_of's real signature. As compiled it answers foreign_in_a_value_argument. With its value argument replaced by the list literal's type, whose element is spelled T and carries no mark, it answers foreign_value_argument_undecided. With no value argument it answers foreign_in_no_value_argument. The control holds only on all three.
  • missing_carriers_are_unobserved: a name with no item, and the fixture's record declaration, which has no body, both answer ResultCarrierUnobserved.

Not covered, not verified

  • The whole corpus in one process. It aborted on allocation under a 22 GB ceiling. The label's report covers one closure; other closures go through the driver.
  • Determinism, checked. Two runs of one binary over one subject produce the same rows, and the receipt is sorted so they are the same bytes. An earlier 21-row difference on the v1 compiler closure (287,088 then 287,109) was the SUBJECT moving, not the instrument or the compiler: main advanced between the runs and changed src/v1/05_emit_rust.dag. Re-running with that one file at its earlier revision reproduces 287,088, and every row that moves is in v1.compiler.emit_rust (closure_renders_async_trait_resource, emit_capability_method, emit_rust_selected). The first census binary, rebuilt and run on the current tree, gives the same rows as the current one.
  • the_generic_identity_census_operand_routes_to_its_own_producer and clippy over the new host arm have not been run locally; CI is their first execution.
  • The receipt's numbers are deliberately not transcribed into committed prose (DESIGN §6).

🤖 Generated with Claude Code

gunbc-ci-auto-heal and others added 4 commits October 4, 2026 13:58
…ment (output grain)

v1.tests.claim.generic_identity_census reads the typed graph and records, per
stored type node, which generic-identity marks it carries. Its blind spot is a
typed UNOBSERVED population in every receipt.

Not ready to push: the mirror and lib.rs line were emitted by a local compiler
that is not main's emitter and must be regenerated; the gunbc test label and the
callee-argument column are not written yet.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
claim_executor --regen-round-cost in a standalone clone, looped to
rebuild_packages=0. The census mirror and lib.rs line were already a fixed point.

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

@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.

NO-LAND at this head. The output-grain census, typed UNOBSERVED rows, sorted receipt, label, and admitted host transport are a sound shape, but the reader currently turns absence of an observation into positive semantic answers.

  1. gi_type_mentions_parameter recognizes an owner's value-parameter mention only through the TypeParameter declaration mark. If that occurrence is itself one of the unmarked/lost carriers under measurement, it returns false; gi_foreign_reading then records ForeignParameterInNoValueArgument, which claims “unbound for want of context” even though the real answer is “the owner argument's identity was not observable.” Add an explicit indeterminate/unobserved arm (or another identity-backed observation) rather than treating no marked mention as proof that no value argument mentions the parameter.

  2. The fixture has the same absence-as-success problem. bound_call_is_clean is only the negation of a foreign row, so it holds if the caller_function carrier disappears from the walk. collision_lost == false is rendered as substituted, although false also means the relevant carrier was not observed. Require a positive row for the call carrier and make the reading tri-state: left unsubstituted / positively observed substituted / unobserved.

  3. The module says it reads every kept signature parameter/return, but gi_sig_rows returns [] for every signature with no declared type parameter. A nominally non-generic signature carrying a leaked foreign parameter is therefore silently omitted and is not one of the four UNOBSERVED populations. Either walk all signatures (ordinary concrete leaves are already filtered later) or declare that population explicitly.

These are measurement-validity blockers, not objections to using output grain. The current rows can support the present “left unsubstituted” observation, but the instrument cannot durably distinguish a repair from a lost/unwalked carrier until those absences have their own arm and positive controls.

gunbc-ci-auto-heal and others added 2 commits October 4, 2026 17:04
1. gi_foreign_reading is three-valued. "No marked occurrence in the owner's
   value arguments" no longer reads as "no occurrence": a leaf spelled as the
   parameter and carrying no mark is ForeignParameterValueArgumentUndecided.
2. The bound-call control and the collision reading rest on
   ResultCarrierObservation, which says whether the carrier was seen at all.
3. gi_sig_rows reads every signature, not only those declaring a type parameter.

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

owner_argument_arms_discriminate asks about one marked parameter under three
supplied owners built from head_of's real signature and requires three
different answers. missing_carriers_are_unobserved requires Unobserved for a
name with no item and for an item with no body.

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

@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.

LAND. The three measurement-validity blockers are closed at this head. Foreign-parameter cause is now positively discriminated: exact declaration-mark reachability wins, an unmarked same-spelling value-argument leaf is ForeignParameterValueArgumentUndecided, and InNoValueArgument is reached only after both are absent. Result-carrier standing is explicit rather than inferred from row absence: the clean control requires an observed Int carrier, the collision reading has observed-unsubstituted / observed-substituted / unobserved outcomes, and supplied-node controls execute both observation arms. gi_sig_rows now walks every signature, allowing a nominally non-generic signature carrying a foreign parameter to produce a row. The new three-arm owner control and missing-carrier control are discriminating, and the receipt remains sorted and byte-stable. No infer behavior is changed by this instrument.

@gunbai-bot
gunbai-bot Bot added this pull request to the merge queue Oct 4, 2026
Merged via the queue into main with commit 8b3e0be Oct 4, 2026
4 checks passed
@gunbai-bot
gunbai-bot Bot deleted the session/jolly-pike-330-generic-identity-census branch October 4, 2026 20:53
gunbai-bot Bot pushed a commit that referenced this pull request Oct 5, 2026
…pt the branch's superset)

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
gunbai-bot Bot pushed a commit that referenced this pull request Oct 5, 2026
…tstrap to RestResult

#13290 landed the IAM bootstrap in the old shape. Its fifteen IAM/IAM-admin/IAM-deny/secret-manager/
userinfo operations lose their outcome field; every .outcome-beside-body read becomes a match on
RestAnswered/RestRefused with the same branches and texts. The policy readers split into an answered
reading and iam_bootstrap_policy_refused; the policy writes return IamBootstrapPolicyWrite, whose
refused arm keeps the raw RestRefusal for bootstrap_authority_publication_refused. The authority
predicates take RestRefusal (they never decided on success).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
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