Expand description
Project-wide orchestration: discovery → parse → group → resolve, shared
between bynk-emit’s run_checks (both Mode::Build and Mode::Analyse)
and this crate’s own crate::analysis::analyse_project.
P4.1 (#1115), second scope finding on the tracking issue: this pipeline —
phase_discovery through assemble_unit_info, plus the per-unit symbol
composition (compose_unit_symbols/merge_consumed_exports/
collect_unit_methods) — used to live only in bynk-emit/src/project.rs,
inline in run_checks. A literal no-indirection bynk-check-side analysis
entry point needs the identical sequence, so rather than write a second,
independently-maintained copy (the mistake this whole design track’s
extract, don't duplicate principle exists to prevent — see
lower_field_default_wire, build_capability_op_info for the same move
made earlier in this track), it moved here. bynk-emit’s run_checks
becomes a caller of these functions instead of owning the logic, the same
way P4.0 turned project.rs into a caller of bynk-project.
What stayed in bynk-emit (not shared, because only the Mode::Build path
needs it, or because it’s genuinely emission-shaped): the Mode::Build
bail gate and everything from emission onward (EmitUnitCtx, emit_unit,
collect_history_target_agents). The whole-project messages/locale-
ambiguity/event-subscription checks (P5.0/P5.1), the function-type-
boundary check (P5.2, phase_function_type_boundaries), and
schema-registry reconciliation/platform-lock enforcement (P5.3,
crate::schema_registry::reconcile/phase_platform_lock) have since
moved here too — the P5.2 move closed phase_group’s optional
boundary-check hook, which used to be the only way run_checks and the
new entry point could reach it without duplicating the diagnostic-ordering
logic (see analysis.rs for the residual-gap accounting that remains).
Structs§
- Adapter
Binding - v0.17: a resolved adapter binding — the user-authored
.binding.tsmodule that supplies an adapter’s external provider symbols. Copied verbatim into the output beside the adapter’s emitted interface module so thattscchecks theimplementscontract and compose can import the symbols. - Error
Sink - Collection-point error sink (ADR 0052). Helpers keep their plain
&mut Vec<CompileError>signatures; call sites attribute viaextend_forwith the file in scope at that point. - Unit
Info - v0.29.4: the per-unit facets that the producer phases build as nine parallel
HashMap<String, _>s, all keyed on unit name. Assembling one record per unit makes the “all these maps share one keyset” invariant structural: a single lookup yields every facet as a field, so the per-column.unwrap()s on the shared keyset disappear. Fields are total —exports/aliases/flatteneddefault to an empty map for a unit with no entry, reproducing the old.unwrap_or(empty)read semantics without the dance.
Enums§
- Build
Target - The build target. Determines how cross-context calls and per-context modules are emitted (v0.8). Bundle mode is the default — all contexts emit into one TypeScript bundle and cross-context calls are direct function invocations. Workers mode produces per-context Cloudflare Worker bundles that communicate via Service Bindings.
Constants§
- FIRSTPARTY_
ID_ BASE - FIRSTPARTY_
ID_ BLOCK - A memoized parse of one first-party synthetic source, keyed by the
call-site’s own
cachestatic — each ofphase_parse’s 7 injection sites below passes a distinct one. Finding #55/#65: the source text is a fixedinclude_str!constant, so its parse is a pure function of that constant and only needs computing once per process, not once per compile/analyse round. The gating below (consumes_bynk,uses_map, etc.) is unaffected — it still runs fresh for every project from that project’s own parseduses/consumes; only the parse result being gated is cached. T3.4 (R2.4): each first-party synthetic unit reserves its own 1M-wideExprIdblock, spaced far above anything a real project’s own file count could ever reach — seefirstparty_parsed’s doc comment for why a fixed reservation, not a threaded counter, is the right shape here.
Functions§
- assemble_
unit_ info - v0.29.4: fold the nine parallel per-unit maps into one
HashMap<String, UnitInfo>. Assembly is driven by thegroupskeyset (the authority), so every group yields exactly one record. Facets that are genuinely optional in the producer maps (exports/aliases/flattened, andfile_indexfor a unit with no declarations) default to empty — reproducing the old.unwrap_or(empty)read semantics as a total field. - check_
discovered_ files - The checks every tree’s file list must pass regardless of where it came
from — a real disk walk (
phase_discovery) or a caller-supplieddiscovered/CompileOptions.sourceslist (#1077/#1081 review). An empty project (bynk.project.no_sources) signals a bail viaErr(()); a file/directory name conflict is a non-fatal diagnostic. - check_
function_ type_ boundary_ items - Item-level body of the boundary confinement, shared with the single-file
(legacy) compile path in
bynk-emit’slib.rs. Relocated alongsidephase_function_type_boundaries(P5.2). - collect_
type_ decls - v0.174 (#592): a
name -> TypeDecltable over a set of items, for the recursive-generic boundary walk. Relocated alongsidephase_function_type_boundaries(P5.2) — public sincebynk-emit’s single-file compile path (lib.rs) also needs it, across the crate boundary this relocation now draws. - collect_
unit_ methods - Phase 8c: collect every method authored anywhere in one unit, keyed by its
attached type’s name — so a type’s methods surface in the file that declares
the type even when the method lives in a sibling file. The collection loop
has no
continues, so it lifts out whole. - compose_
unit_ symbols - Phase 8a: compose one unit’s symbol space — its local table plus a
one-level
usesmixin (commons identity preserved). Returns the combined type/fn/method tables and theimported_fromprovenance maps; the mixin loop has nocontinues, so it lifts out whole. - firstparty_
parsed - handler_
cross_ caps - v0.15: the cross-context capabilities a context’s handlers reference,
as
deps_key → consumed_context. Shared withbynk-emit, not duplicated — seeresolve_consume_prefix’s doc. - inject_
service_ defaults - Inject a single service’s
by/givendefaults into its handlers. A handler that names its ownby(orgiven) overrides the default outright — the default fills only an absent clause, never merges. A service with no default is left untouched (byte-for-byte the pre-v0.155 behaviour). - merge_
consumed_ exports - Phase 8b: merge one context’s
consumesexports into the composed symbol space, recording visibility metadata in the returnedconsumed_types. The per-exportcontinues (missing decl, name conflict) stay internal to the loop, which lifts out whole; name conflicts are pushed intoerrorsand the caller’sgroup_error_baselineguard reacts to them after this returns. - normalize_
service_ defaults - phase_
consumes_ aliases - Phases 5b’/5b’‘: collect each context’s
consumesaliases (alias → consumed-context name), reporting alias-vs-alias conflicts (5b’), then report any alias that clashes with a locally-declared type/fn/capability/service/agent (5b’’). Returns the per-context alias maps; diagnostics go intoerrors. - phase_
detect_ consumes_ cycles - Phase 5c: detect
consumescycles. #696: record eachconsumes-clause site (file + span) keyed by(consumer, target)so a detected cycle anchors on the exact clause that forms the closing edge — a real span in a real file — and renders with source context. Synthetic units are left out so their (snapshot-less) files never claim a diagnostic. - phase_
discovery - Phase 1: discover the
.bynkfiles under the source (and, in split mode, the tests) root by walking the filesystem. Pushes any discovery error intoerrorsand signals a pipeline bail viaErr(())(the caller terminates withfinish); otherwise returns the discovered(src_files, tests_files). - phase_
event_ subscriptions - Events track, slice 0 (spine #936): a
from Events(E)subscription must name a real, declared event — owned either by this context or by a context itconsumes(mirroringdiscover_event_subscribers’s own ownership resolution,project.rs, which silently drops an unresolvable subscription rather than diagnosing it). Runs at the project-wide phase (needsunit_tables+unit_consumestogether, unlike the local, per- contextcheck_service_protocols), alongside the other cross-unit checks that need the same two maps. - phase_
file_ index - Phase 7: build each production unit’s file-declaration index (which file in the unit declares which name), for cross-file lookups in the back half.
- phase_
function_ type_ boundaries - v0.20a: apply the function-type boundary confinement to every serialisable
or boundary-crossing position in a file’s items: record fields and sum
payloads (types can cross contexts and persist), service/agent handler
signatures (the Workers wire), capability operation signatures (kept out
in v0.20a — see ADR 0030), agent state fields, and agent keys. Free
fnsignatures are deliberately NOT walked — they are the non-boundary home of function types. - phase_
group - Phase 3: group the parsed units by qualified name (production units, unit
tests, and integration suites tracked separately), run the per-directory
and path/name consistency checks, enforce the reserved
bynknamespace and the adapterbindingrules, resolve each adapter’s binding module, and fold the adapters’ pinned npm dependencies. Pushes diagnostics intoerrorsand returns the productiongroups/kinds, thetest/integrationgroups, the resolvedadapter_bindings, and the collectednpm_deps. - phase_
locale_ bundle_ ambiguity - Locale capability track, slice 2 (#882): a context whose direct
usesreaches two or more message-bundle commons has no principled single answer for whatLocale.current()should negotiate against — but this is only worth diagnosing when the context actuallyconsumes bynk { Locale }at all; a context with 2+ bundles that never touchesLocalehas nothing ambiguous to resolve. - phase_
messages_ bundles - message-bundles slice 1 (#859): messages-block legality,
@referencecardinality, within-block duplicate codes, and theuses bynk.localedependency. Runs here (not inphase_group) because it needsunit_uses, resolved just above. - phase_
parse - Phase 2: parse every discovered file into a
ParsedFile, recording each file’s source text intosnapshotsand any parse errors intoerrors. Then inject the first-party synthetic units (thebynk/bynk.cloudflareadapters and thebynk.{list,map,string}commons) that the project consumes/uses. Returns the parsed units plus whether thebynkandbynk.cloudflareadapters were injected; signals a pipeline bail viaErr(())when parsing produced errors and yielded no units at all. - phase_
platform_ lock - v0.19 (decisions 0017/0024): enforce the platform lock per deployment
unit — each context under
--target workers, the whole program underbundle(co-location shares the lock). - phase_
resolve_ consumes - Phase 5b: resolve each unit’s
consumesclauses (target exists, is a context or adapter, not self-referential, obeys the adapter selection rules), and for the bracedconsumes U { Cap, … }form validate and record the flattened capabilities. Returns unit → consumed targets and unit → flattened-cap → owning unit; diagnostics go intoerrorsand clause-position references intorefs. - phase_
resolve_ uses - Phase 5: resolve each unit’s
usesclauses, checking the target exists, is a commons, and is not self-referential. Returns unit → deduplicated list of used commons; diagnostics go intoerrors. - phase_
secrets_ computed_ name - v0.173 (ADR 0196 D1), P5.5 (
design/tracks/semantics-in-the-checker.md§6, §9): warn where abynk.Secretsread names its secret with a computed expression. Non-failing — the program is correct,bynk deploysimply cannot see the name — walked per file rather than per unit, since a mergedUnitTablehas thrown away which file a call site lives in andErrorSink::extend_forattributes a diagnostic to a path. - phase_
symbol_ tables - Phase 4: build each production unit’s combined symbol table from its files,
pushing any table-construction errors into
errors. - phase_
uses_ name_ conflicts - Phase 6: for each unit, detect when two
uses-imported commons declare the same (non-shadowed) type or function name — an unrenamable conflict at the use site. Diagnostics go intoerrors. - phase_
validate_ capability_ exports - Phase 6b’: validate each context/adapter’s
exports capability { … }clauses (v0.15 §4.1) — every name must be a capability the unit declares and provides, with no duplicate exports. Diagnostics go intoerrorsand export references intorefs. - phase_
validate_ providers - Phase 6c: validate that every (non-external) provider matches its capability
exactly — each capability op has a provider op, and every provider op has a
matching capability op with the same parameter and return types. Diagnostics
go into
errors. - phase_
validate_ type_ exports - Phase 6b: validate each context/adapter’s
exports opaque/transparent { … }clauses — every name must be a locally-declared type, with no duplicates within a clause or conflicting visibilities across clauses. Returns unit → (type → visibility); diagnostics go intoerrorsand export references intorefs. - resolve_
consume_ prefix - v0.15’s cross-context capability resolution, relocated alongside
phase_platform_lock(P5.3): resolve agiven/handler capability prefix (ctx.Cap) against a context’s ownconsumes/alias tables. Pure — no codegen, nobynk-emitdependency of its own — so unlikecollect_given_closurethis one is shared rather than duplicated:bynk-emit/src/project.rs’s own copy of this function (and ofhandler_cross_caps) was deleted in review (#1133) and every one of its call sites repointed here —bynk-emitalready depends onbynk-check, so there was no dependency direction to route around, and keeping two copies only bought two things that could drift out of sync for no reason. - tree_
root_ for - The
includetree that discoveredpf, found by matching its absolute path against each tree’s root — nottrees[0]unconditionally, since R3.9 (#1113) lets a file live under anyincludetree, not just the first. Falls back totrees[0]for apfwith noabs_path(unreachable for a real adapter: only synthetic units, which never declarebinding, go without one) or if it somehow matches none. The longest matching root wins, in case oneincludetree is nested inside another.