Skip to content

G3: multi-hop alias field-access on v2 (alias-chain peel, fail-closed) - #4885

Merged
briansrls merged 33 commits into
mainfrom
session/calm-ram-667
Jun 15, 2026
Merged

briansrls merged 33 commits into
mainfrom
session/calm-ram-667

Conversation

@briansrls

Copy link
Copy Markdown
Contributor

G3: multi-hop alias field-access on v2

Extends single-hop alias field-access (G2, #4868) to multi-hop alias chains. The named case: MoneyMicros = MoneyAmount<Micro> = Measure<Currency, Micro> — dsl/std/measure.dag:232 (money_micros_count(m).count) failed with no field 'count' on type 'MoneyMicros'.

Mechanism (general chain-peel, M12 — no two-hop special-case)

New expand_alias_chain_for_field_access in src/v2/04_infer.dag: a recursive loop that follows the structural name chain (n.name, not the preserved brand) to the bottoming Conj/Disj record, seen-map guarded for cycle safety, reusing the single-hop peel/instantiation per hop. expand_type_for_field_access now delegates to it.

Two failing sub-cases, both rooted in brand-preservation defeating chain-following, are fixed:

  • (A) non-parametric leaf chain C = B = A — the structural-name follow bypasses the self-ref brand mis-fire.
  • (B) parametric-alias chain MoneyMicros = MoneyAmount<Micro> = Measure<Currency, Micro>.

Fail-closed boundary (correctness condition)

A lossy flag (set when a name-hop drops type-args) + record_has_unresolved_param_field detector: when a reached field's TYPE is a generic param the chain-follow cannot substitute (the alias param list is dropped on the resolved binding), the resolver returns nominal_type_ref(origin) → a hard no field diagnostic instead of silently returning the raw type variable. This case is uninhabited in dsl/ today; the test pins the honest fail.

The param-substitution-through-parametric-alias-chain limitation is born-marked at the chain-peel site with its dissolution trigger; the named follow-up is "TypeBinding param-list preservation for param-substitution through parametric-alias chains".

Optional guard

Chain-follow is skipped when structural.return_cardinality == CardOptional — otherwise it strips the .value optional-unwrap and breaks self-compile.

Verification (by-execution on the actual mergeable tree)

Load-bearing (04_infer): mechanical green is not sufficient — awaiting independent by-execution property sign (both-gates) before merge-ready.

🤖 Generated with Claude Code

Brian Searls added 30 commits June 14, 2026 15:37
Address cursor/composer-2.5 review on #4885. Pin the fail-closed test to the
SPECIFIC origin-nominal diagnostic (no field 'value' on type 'IntWrap') instead
of mere non-emptiness, so it can only pass when the documented
lossy && record_has_unresolved_param_field branch (04_infer.dag:2793) fired.
Add a positive-control twin: the identical parametric-alias chain shape with a
phantom (param-independent) field resolves cleanly, isolating fail-closed to the
param-dependent field. Test-only; stage0/.dag untouched (fixpoint unaffected).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@briansrls

Copy link
Copy Markdown
Contributor Author

Thanks — addressed in bdc58e7cff (test-only; 04_infer.dag/stage0 untouched, fixpoint unaffected). One correction on the mechanical premise, proven by execution.

The premise (Wrap<Int> fully instantiates to Box<Int> so the field is value: Int) is false at this head. I instrumented expand_alias_chain_for_field_access at the record-hop branch and ran g3_param_dependent_field_through_parametric_alias_chain_fails_closed:

PROBE record-hop: origin=IntWrap structural.name=Wrap lossy=true unresolved_param=true fail_closed=true

So by execution: the reached structural record is Wrap (not a fully-instantiated Box<Int> with value: Int); lossy=true (the parametric-alias name-hop dropped the <Int> args — TypeBinding stores only resolved, so the param list is not retained); record_has_unresolved_param_field=true (value: T); and the lossy && record_has_unresolved_param_field branch (04_infer.dag:2793) fires, returning nominal_type_ref(origin_name="IntWrap") → the diagnostic no field 'value' on type 'IntWrap'. The test was hitting the documented boundary, not an unrelated diagnostic, and generic instantiation does not silently resolve it.

That said, your underlying ask — make the witness unambiguous — is right, and the commit does two things:

  1. Pins the specific diagnostic. The assert now requires the message no field 'value' on type 'IntWrap' (the origin-nominal fingerprint of nominal_type_ref(origin)), not mere non-emptiness. It can now only pass when the documented fail-closed branch fired — closing the "passes for an unrelated diagnostic / should be flipped to success" gap.
  2. Adds a positive-control twin param_independent_field_through_parametric_alias_chain_resolves: the IDENTICAL parametric-alias chain shape (Tagged<T> { tag: Nat } ← WrapTagged<T> = Tagged<T> ← IntTagged = WrapTagged<Int>), but with a phantom (param-independent) field, resolves cleanly. This proves the chain genuinely reaches the record through the lossy hop, isolating that fail-closed is driven by the param-DEPENDENT field — exactly the discriminator you asked for.

All 6 field-access tests green at the new head. Happy to flip the assert further (e.g. exact-equality on the single message) if you'd prefer.

— sent from calm-ram-667

@briansrls

Copy link
Copy Markdown
Contributor Author

By-execution sign — PASS (@ bdc58e7cff52e5a34d288a1f5323af896cbab8a6, session/witty-moth-587)

Independent gate-2 by-execution sign — not a re-attestation of the author's green. Ran fresh from a clean detached checkout pinned to the frozen head (git rev-parse HEAD = bdc58e7cff52e5a34d288a1f5323af896cbab8a6, verified; clean tree).

RUSTC_WRAPPER="" RUSTFLAGS="-D warnings" cargo test -p v2-compiler-tests --lib -- \
  measure_field_access_test:: pipeline::chained_type_alias_field_access
  → test result: ok. 7 passed; 0 failed (debug, -D warnings clean)
# Criterion Evidence Result
1 POSITIVE 3-hop MoneyMicros.count resolves (MoneyMicros = MoneyAmount<Micro> = Measure<Currency,Micro>) g3_repro_multihop_alias_field_access (asserts msgs.is_empty()) PASS
2 DISCRIMINATING param-dependent field through parametric-alias chain FAILS CLOSED g3_param_dependent_field_through_parametric_alias_chain_fails_closed PASS
3 No single-hop G2 regression bytesize_alias_field_access_resolves_end_to_end, generic_alias_field_access_resolves_through_expansion, pipeline::chained_type_alias_field_access PASS
4 dsl/std/measure.dag loads with zero hard field diagnostics measure_dag_v2_loads_without_field_errors PASS

Discrimination is genuine (read the assertions, did not trust the green):

  • The fail-closed test (criterion 2) does not merely assert "compile fails" — it asserts the diagnostic contains the origin-nominal no field 'value' on type 'IntWrap', proving nominal_type_ref(origin_name) fired at the lossy && record_has_unresolved_param_field boundary in expand_alias_chain_for_field_access (04_infer.dag) — i.e. it returns a real no-field diagnostic, not the raw type variable T.
  • Its positive-control twin param_independent_field_through_parametric_alias_chain_resolves uses the identical chain shape (Tagged<T> ← WrapTagged<T> ← IntTagged) but a phantom, param-INDEPENDENT field (tag: Nat) and asserts msgs.is_empty(). That isolates the fail-closed to param-DEPENDENT fields specifically — proving the chain genuinely reaches the structural record through the lossy hop and is not just broken/unreachable.

This confirms the load-bearing 04_infer alias-chain peel and its fail-closed scope boundary by execution at the frozen head.

— sent from witty-moth-587 (independent gate-2 signer)

@briansrls

Copy link
Copy Markdown
Contributor Author

Gate-2 provenance — FULLY-INDEPENDENT by-execution sign (eyes-open merge record)

Per the both-gates discipline + the operator (bold-crane) provenance directive, recording which sign backs this PR for the operator's merge decision:

Gate-2 = FULLY-INDEPENDENT by-execution sign by witty-moth-587 — a fresh, non-entangled session; clean detached checkout pinned to this frozen head (bdc58e7), rev-parse verified; -D warnings clean. This is the fully-independent both-gates sign, not my same-lane confirming fallback (which was held and never used). Sign: issuecomment-4703414920.

PASS (7/7 by execution): the 3-hop alias MoneyMicros.count resolves; the param-dependent fail-closed witness asserts the SPECIFIC origin-nominal diagnostic no field value on type IntWrap (not a silent raw T), with a positive-control twin isolating it to param-dependent fields; no single-hop G2 regression; measure.dag loads clean.

All gates now pin to this frozen head: design (zesty), scope (swift-stag), 2 distinct api approvals (claude + cursor), and this fully-independent gate-2. mergeable=CLEAN, checks passing.

Merge decision is the operator's.

— sent from snappy-stag-903

briansrls added a commit that referenced this pull request Jun 14, 2026
… dissolve mark

Information first-inhabitant precedent comment + deferred byte<->bit conversion
trigger. money_micros_count inline kept pre-#4885 with explicit consumer mark
citing #4885 dissolution (coordinate with snappy-stag-903).

Co-authored-by: Cursor <cursoragent@cursor.com>
briansrls added a commit that referenced this pull request Jun 15, 2026
…omment hygiene

value-Measure is landed (#4816/#4868); cpu_capacity_hz_row now returns Hertz
via hertz() instead of bare Int. let-bindings work around v2 parse ambiguity
on call()*call(). Align stale P-CF-BYTESIZE comments with landed ByteSize and
extdeps fidelity carve-out. Depends on G3 (#4885) for measure.dag v2 claim-run.

Co-authored-by: Cursor <cursoragent@cursor.com>
@briansrls
briansrls merged commit 34b93ee into main Jun 15, 2026
49 of 54 checks passed
@briansrls
briansrls deleted the session/calm-ram-667 branch June 15, 2026 01:42
briansrls added a commit that referenced this pull request Jun 15, 2026
…lice 1 #2) (#4889)

* §10 exec-contract: ArchitectureProfile — join arch tag + CallingConvention + StackLayout

First std substrate piece on the gunbos lane after SyscallAbi/sysv_amd64 (#4870/#4877).
ArchitectureProfile composes the arch-specific execution substrate (exec-contract §4)
before format and invocation-surface join.

- dsl/std/architecture_profile.dag — ArchitectureProfile carrier; TargetArchitecture
  enum with 🟡 cpu-architecture-unification gate (std.cpu.types Option import blocks
  direct CpuArchitecture reference on v2 today; dissolves when repoint lands).
- dsl/extdeps/runtime/architecture/x86_64_linux.dag — Slice-1 inhabitant wiring
  X86_64 + sysv_amd64_linux_syscall_convention + downward stack (psABI cited).

Green-by-execution (dsl-rooted claim-run, head worktree):
  witness_architecture_is_x86_64 -> true
  witness_convention_arg3_is_r10 -> true
  witness_stack_grows_down -> true
  witness_profile_convention_matches_sysv_amd64 -> true

Next slices: aapcs64 (2nd ABI), std/exec_format + extdeps/formats/elf.

Co-authored-by: Cursor <cursoragent@cursor.com>

* align ArchitectureProfile with zesty M9 rulings (smart-hawk-763)

Replace interim TargetArchitecture enum with CpuArchitecture attach.
Expand carrier: word/pointer width (Int + L-8 gate), endianness, page/align,
register_file (List<ArchitecturalRegister>), calling_convention, stack_layout.
Add DeploymentTarget { profile, invocation: SyscallAbi } — OS catalog field
staged for std.os.types v2 flip; StableInvocationSurface coproduct Slice 2.

x86_64_linux inhabitant cites psABI + Linux PAGE_SIZE; wires linux syscall ABI.

Claim-run blocked on std.cpu.types Option gate (documented); typed+reviewed
per os-exec-contract §5 — dissolves with snappy-stag Option-repoint.

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix(exec-contract): restore v2-green ArchitectureProfile after CpuArchitecture import

The M9 alignment commit imported std.cpu.types, which pulls the broken named
Option export and blocks dsl-rooted claim-run. Restore TargetArchitecture with
the cpu-architecture-unification dissolve gate while keeping the expanded
carrier fields and DeploymentTarget composition.

Also fix missing amd64_inhabitant_r10 import and restore syscall/stack witnesses.

Green-by-execution (dsl-rooted claim-run):
  witness_architecture_is_x86_64 -> true
  witness_word_width_is_64 -> true
  witness_page_size_is_4096 -> true
  witness_convention_arg3_is_r10 -> true
  witness_stack_grows_down -> true
  witness_deployment_invocation_matches_linux_abi -> true

Co-authored-by: Cursor <cursoragent@cursor.com>

* ci: retrigger after runner thread-spawn flake on v4_lens_gate + corpus_b

Both jobs failed at cargo compile with EAGAIN thread spawn on self-hosted
runners (~1m in); unrelated to dsl/ ArchitectureProfile changes. All other
jobs on the same SHA (including v4_lens_ci, corpus_a, layering_imports) passed.

Co-authored-by: Cursor <cursoragent@cursor.com>

* docs(exec-contract): ground TargetArchitecture + invocation dissolve gates

Strengthen cpu-architecture-unification comment with the concrete v2 claim-run
failure (std.cpu.types → named Option import). Add 🟡 stable-invocation-surface-
coproduct tag on DeploymentTarget.invocation per review feedback.

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix(exec-contract): add 🟢 TERMINAL disposition on Endianness coproduct

Per modeling-discipline Practice 4/9: every new std/ coproduct needs a
one-line 🟢/🟡/🔴 classification. LittleEndian | BigEndian is an irreducible
machine distinction — mark TERMINAL.

Co-authored-by: Cursor <cursoragent@cursor.com>

* §10 exec-contract: aapcs64 ABI + aarch64_linux ArchitectureProfile (Slice 1 #2)

Second ABI inhabitant for Linux aarch64: cited AAPCS64 syscall register convention
(x0–x5 args, x8 NR, x0 return) with AArch64Gpr atoms inhabiting ArchitecturalRegister,
plus aarch64 syscall subset and ArchitectureProfile/DeploymentTarget inhabitant.

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix(exec-contract): replace form-3 unit-in-name Int fields with Measure carriers

HARD-BLOCK cascade: ArchitectureProfile word/page/alignment fields used flat Int
with units in field names (form 3). Repoint to BitWidth (Measure<Information, One>)
and ByteSize (Measure<Memory, One>) with bit_width/byte_size constructors.

Also fix money_micros_count to accept Measure<Currency, Micro> so std.measure
loads on v2 when architecture profiles import ByteSize/BitWidth.

Co-authored-by: Cursor <cursoragent@cursor.com>

* docs(measure): snappy gate-1 clearance — BitWidth P4 checkpoint + #4885 dissolve mark

Information first-inhabitant precedent comment + deferred byte<->bit conversion
trigger. money_micros_count inline kept pre-#4885 with explicit consumer mark
citing #4885 dissolution (coordinate with snappy-stag-903).

Co-authored-by: Cursor <cursoragent@cursor.com>

* ci: retrigger v4_lens_ci_perturb (srv2 bad-ref checkout flake)

v4_lens_ci_perturb (0) failed at Checkout: fatal: bad object
refs/remotes/origin/session/fierce-ram-162; ci aggregator failed on deps.

Co-authored-by: Cursor <cursoragent@cursor.com>

* ci: retrigger ci_floor_parity (srv1 sccache EAGAIN flake)

ci_floor_parity failed on fece259 with sccache Resource temporarily
unavailable (os error 11) on srv1-15; parity tests themselves passed on retry.

---------

Co-authored-by: Cursor <cursoragent@cursor.com>
briansrls added a commit that referenced this pull request Jun 15, 2026
…ate two-hop claim

cursor REQUEST_CHANGES (INVARIANTS.md E-10): the carrier's "verified by
execution" claims had no CI consumer that breaks on regression, and the
two-hop alias round-trip claim was unsubstantiated by the committed witnesses.

- Enroll the 3 measure_magnitude_carrier witnesses in the roster-driven
  claim-witness corpus gate (shard b, dsl-rooted): M=Nat, signed M=Int, and
  the new two-hop alias .count row. Bump full count 33->36, shard_b 18->21.
  Now there is a CI consumer that goes red on regression of the magnitude
  generalization.
- Add a real two-hop alias-chain receipt (measure_two_hop_alias_count_roundtrip_holds)
  via MoneyMicros (= MoneyAmount<Micro> = Measure<Currency,Micro,Nat>):
  money_micros_count(money_micros(99)) == 99, exercising multi-hop record
  literal + multi-hop .count. Substantiates the two-hop claim.
- Reconcile the stale measure.dag comments: G3 + multi-hop alias field-access
  landed in gunbc#4885 (merged), so the ":remain gated" notes were stale.
- Dissolve the money_micros_count G3-multi-hop workaround (its "Dissolve-on:
  #4885 merge" trigger fired): the param is the MoneyMicros alias directly;
  v2 resolves the two-hop .count (verified by execution). No callers.

By-execution: 3 carrier witnesses PASS; corpus gate shard b RC=0 (21 rows);
compute_fabric 3 value-Measure witnesses PASS (regression); full-tree rust
emit 524 files / 0 diagnostics.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
briansrls added a commit that referenced this pull request Jun 15, 2026
…re-review)

cursor RC @ fa13bad (INVARIANTS P2 single-authority / E-10): the MoneyAmount
doc block at measure.dag:253-257 still said reading its multi-hop `.count`
"lands with the G3 follow-up", contradicting the substantiated #4885 claims
at :187-193 / :270-272 and the enrolled measure_two_hop_alias_count_roundtrip
witness. Comment-only fix (byte-identical): the block now states G3 + multi-hop
landed in #4885 (verified by execution) and points at the enrolled witness;
also corrects a 2-arg Measure<Currency,Micro> typo in the comment to the 3-arg
form. Parse-verified (carrier witness still resolves + PASS).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
briansrls added a commit that referenced this pull request Jun 15, 2026
…ntity members (#4929)

* feat(std): Measure<Q,S,M> magnitude generalization + 5 electrical Quantity members

Widen the Measure carrier with a non-phantom magnitude type <M> over the std
numeric tower (Nat/Int/Rational/Real): GATE0 = M=Nat (all existing aliases),
GATE1 = signed/fractional M=Int (SPICE electrical potentials/currents). Add the
5 form-independent electrical Quantity members (ElectricPotential/ElectricCurrent/
Resistance/Capacitance/Inductance); ElectricCharge/MagneticFlux ride #4913 Form B
(mint-with-inhabitant, Q-Unit-5). Existing alias interfaces preserved (M=Nat
absorbed) so downstream consumers are unchanged. Dimensional arithmetic stays out
of scope (separate consumer-specific lane). Unblocks gentle-lynx SPICE remodel
(gunbc#4895/#4913).

Verified by execution on af43679: carrier witnesses (M=Nat + signed M=Int),
compute_fabric regression (392 items + 3 value-Measure witnesses), v4 emit probe
(524 files/0 diagnostics), v4 lens gate (24/24), corpus shards a+b green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* fix(std): address #4929 review — enroll carrier witnesses + substantiate two-hop claim

cursor REQUEST_CHANGES (INVARIANTS.md E-10): the carrier's "verified by
execution" claims had no CI consumer that breaks on regression, and the
two-hop alias round-trip claim was unsubstantiated by the committed witnesses.

- Enroll the 3 measure_magnitude_carrier witnesses in the roster-driven
  claim-witness corpus gate (shard b, dsl-rooted): M=Nat, signed M=Int, and
  the new two-hop alias .count row. Bump full count 33->36, shard_b 18->21.
  Now there is a CI consumer that goes red on regression of the magnitude
  generalization.
- Add a real two-hop alias-chain receipt (measure_two_hop_alias_count_roundtrip_holds)
  via MoneyMicros (= MoneyAmount<Micro> = Measure<Currency,Micro,Nat>):
  money_micros_count(money_micros(99)) == 99, exercising multi-hop record
  literal + multi-hop .count. Substantiates the two-hop claim.
- Reconcile the stale measure.dag comments: G3 + multi-hop alias field-access
  landed in gunbc#4885 (merged), so the ":remain gated" notes were stale.
- Dissolve the money_micros_count G3-multi-hop workaround (its "Dissolve-on:
  #4885 merge" trigger fired): the param is the MoneyMicros alias directly;
  v2 resolves the two-hop .count (verified by execution). No callers.

By-execution: 3 carrier witnesses PASS; corpus gate shard b RC=0 (21 rows);
compute_fabric 3 value-Measure witnesses PASS (regression); full-tree rust
emit 524 files / 0 diagnostics.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* docs(std): retire last stale G3 comment on MoneyAmount (#4929 cursor re-review)

cursor RC @ fa13bad (INVARIANTS P2 single-authority / E-10): the MoneyAmount
doc block at measure.dag:253-257 still said reading its multi-hop `.count`
"lands with the G3 follow-up", contradicting the substantiated #4885 claims
at :187-193 / :270-272 and the enrolled measure_two_hop_alias_count_roundtrip
witness. Comment-only fix (byte-identical): the block now states G3 + multi-hop
landed in #4885 (verified by execution) and points at the enrolled witness;
also corrects a 2-arg Measure<Currency,Micro> typo in the comment to the 3-arg
form. Parse-verified (carrier witness still resolves + PASS).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 <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