Skip to content

Transport binding spine: one target-neutral semantic binding for all four transports, then Filesystem bindings + Rust renderer to restore the 03_ingest board - #8945

Closed
gunbai-bot[bot] wants to merge 2 commits into
mainfrom
session/witty-carp-775
Closed

gunbai-bot[bot] wants to merge 2 commits into
mainfrom
session/witty-carp-775

Conversation

@gunbai-bot

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

Copy link
Copy Markdown
Contributor

Auto-opened by session-dashboard for session witty-carp-775.
Pushing to session/witty-carp-775 advances this PR.

Worker attestation

Before flipping this PR to ready for review, confirm each item:

  • Title describes the change (not the session id or branch).
  • PR body summarises what and why (replace the TODO below).
  • Tests run: name the command (e.g. npm test, cargo test) and the result.
  • If this closes a work item, the body contains a Closes #N directive.
  • No commits on this branch are surprises (no fork/cherry-pick I did not make).
  • No secrets / credentials / large binaries staged.

Summary

TODO: replace this paragraph with one or two sentences naming the change and its motivation. Reviewers read this first.

Test plan

  • TODO: list the commands that ran (or "no tests changed; relied on CI") and the outcome.

gunbc-ci-auto-heal and others added 2 commits August 22, 2026 21:20
…s and a Rust renderer: the 03_ingest board emits again

#8858 deleted three per-target file renderers that each fabricated a read of
`base_path + "/" + op_name` for EVERY operation, and replaced them with a typed
refusal. It named its own next rung precisely: the class climbs when a real
consumer needs a file transport emitted, the repair is DESIGN section 3's -- bind
a realization handler -- and NOT a fourth per-target renderer.

The consumer arrived. `src/v2/compiler/03_ingest.dag`'s closure reaches
`extdeps.filesystem.filesystem_io`, so the whole Rust self-host board sat behind
five refusals.

MEASURED, both on this branch's own authority with mirrors regenerated from it
(never main's mirrors against this branch's .dag -- that hybrid is unmeasurable):

  BEFORE  abf7194   emit exit=1, files=0, file-transport refusals=5
  AFTER                 emit exit=0, files=176, file-transport refusals=0

THE DEFECT WAS SEMANTIC INFORMATION LOSS, ONE FRAME ABOVE THE BRANCH. Rest and
shell received the raw transport node and re-derived its meaning; local received
only a spelling and an indent depth; file refused. Three targets independently
deciding what an operation MEANS is the N x M trap -- the N x M defect is not
that each target renders differently, which is correct, but that each decides.

CUT A -- one target-neutral binding. `bind_operation_transport` classifies once,
in the frame that still holds the declaration, before it narrows to (op_name,
depth), and yields RestBound / ShellBound / FileBound / LocalBound /
BindingRefused{cause}. Both dispatchers -- `emit_unified_transport_dispatch`
(Go, Python) and `emit_transport_call` (Rust) -- now match on that binding and do
target selection ONLY. Direct target-side transport classification: zero.
`child_from_key` and `unwrap_single_field_product` move out of the Rust backend
so the declared output projection is read target-neutrally rather than forked.

It is a closed total function, not a handler roster: nothing declares an
independent handler, so an open registration surface would have no consumer.

THE LOCAL HOLE IS CLOSED POSITIVELY. `is_local_transport` was defined negatively
("not rest, not shell, not file"), so a malformed transport BECAME local and
emitted a direct call by omission -- which also made both dispatchers'
"unrecognized transport kind" arm unreachable, so the check that existed to catch
this could never fire. `LocalBound` now requires a canonically empty transport;
nonempty-and-unclaimed refuses. Verified `local_transport_node` IS canonically
empty, so every existing local operation is unaffected by construction.

CUT B -- Filesystem bound, Rust renders it. The five operations bind from the
declaration: verb, resolved path template, payload input, declared result
projection. `WriteOwnerOnly` is NOT an alias for `Write` -- create_new + mode
0600, and a non-Unix host REFUSES rather than degrading to a plain write. Go and
Python have no file renderer and refuse as the backend-capability gap they are,
never falling back to the deleted renderers.

KEYED ON THE DECLARATION, NOT ON THE FIVE OPERATION NAMES. Neither name nor
transport alone determines the operation -- `Write` and `Read` both carry a file
transport with no verb -- but the pair does: explicit `verb:` row, else the
content-param convention. That is exactly what the working interpreter
(`dispatch_file`) already does, so the rule now has ONE authority read by both
realizations instead of two agreeing by coincidence. Hardcoding
`extdeps.filesystem.filesystem_io`'s operation names into the compiler would put
one extdeps module's identity inside it -- the section 3 authority inversion.

THE WALL IS NOT RETIRED. `TransportEmissionNotModeled` still refuses a target
with no file renderer, and any operation the binding cannot bind -- now carrying
the EXACT cause rather than one generic sentence. Its permanent regression
control is retargeted rather than deleted (section 4b(4)): the well-formed
fixture flips from expecting 3 refusals to expecting 0, and four new unbindable
fixtures carry the discriminating RED.

The omitted-path row is the sharp one. The parser SUBSTITUTES an empty string
literal when `path:` is absent, so a binding that trusted what it was handed
would emit a write to "" and report it as a successful realization. The binding
therefore refuses a path template that is present but empty, not merely absent.

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

A file operation declaring exactly ONE output field refused as if it had declared
no channel at all, so `output { success: Bool from "write_success" }` -- a
perfectly ordinary declaration -- could not be emitted.

THE CAUSE. `bind_file_result_fields` called `unwrap_single_field_product` before
reading each child's `from` key. That helper collapses a one-field product to the
field's OWN type, and a scalar is not a product, so the child list went empty and
the binder reported FileTransportUndeclaredResultChannel.

WHY IT SURVIVED THE FIRST PASS. Every one of `extdeps.filesystem.filesystem_io`'s
five operations declares two or more output fields, so the real consumer that
motivated this work never exercised the single-field path. The fixture in the
regression control does, which is what caught it -- the discriminating RED
earning its enrolment on the first change after it was written.

WHY THE HELPER IS RIGHT WHERE IT IS AND WRONG HERE. `emit_shell_return` unwraps
deliberately, because it has a defensible default to fall back to: an unkeyed
shell output means stdout. A file operation has no such default -- that is the
entire reason this binder refuses instead of guessing -- so it must read the
declaration as authored. The binder now reads the resolved type directly and
derives `single_field` from the child count, which also makes the one-field case
project `Ok(expr)` rather than a one-tuple.

`unwrap_single_field_product` moves back to the Rust backend: the previous commit
relocated it to the shared layer for this binder, and with the binder no longer
calling it that move was churn with no consumer. `child_from_key` stays shared --
it is genuinely read target-neutrally.

Named products refuse rather than being tuple-projected: the emitted signature
would carry the type NAME, and a positional tuple would not match it.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@gunbai-bot

gunbai-bot Bot commented Aug 23, 2026

Copy link
Copy Markdown
Contributor Author

Superseded by #8957.

Same architectural change, regenerated onto #8954's closed transport roster. #8957 is the canonical head: it carries the stage0 mirrors regenerated for the cut, so the construction wall exists in the compiler that actually runs rather than only in the authored source. This branch predates that and is nonmergeable.

Closing rather than leaving two open PRs with the same title, so the wrong head cannot be reviewed or composed against.

— sent from witty-carp-775

@gunbai-bot gunbai-bot Bot closed this Aug 23, 2026
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.

0 participants