ID |
|
|---|---|
Status |
Spec |
Bucket |
structural |
Priority |
3 |
Theme |
structural-refactor |
Created |
2026-05-21 |
Updated |
2026-05-25 |
Dimensional model pivot: slots over cross-product permits
R222 is the umbrella for the rewrite’s dimensional pivot. Three sealed hierarchies pack multi-dimensional information onto single permit sets ; input-side classification (GraphitronType.InputType + TableInputType), field-side classification (QueryField / MutationField / ChildField with 46 cross-product permits), and classification-failure encoding (UnclassifiedType / UnclassifiedField riding as permits alongside legitimate carriers). The same disease in three organs. R222 absorbs R164 (field-model three-dimension pivot) and R226 (type-level classification failure pivot) and unifies them as one architectural shift, landing the target architecture stage-by-stage through independent spin-out slices.
Direction, not contract
The model sketched in this umbrella is the target direction ; where the rewrite is heading. Specific slot names, carrier shapes, the boundary between walker carriers and dimensional slots, and the vocabulary itself are expected to shift as implementation slices land and surface new understanding. Each spin-out slice gets its own spec item where the specifics for that scope get pinned; the slice is free to redraw the diagram so long as it doesn’t break the load-bearing claim (cross-product permits dissolve into dimensional slots; producers read graphql-java primitives directly; validity rides on the wrapper). What’s stable is the shape: slots on a single unified field type, one per consumer concern, populated by thin layers over the SDL substrate. Read the sketches below as illustrative of that shape, not as a frozen contract.
What is
Three cross-product encodings, three sets of permit-identity-driven discriminations.
Input-side classification. GraphitronType.InputType permits four backing-class variants (JavaRecordInputType, PojoInputType, JooqRecordInputType, JooqTableRecordInputType); GraphitronType.TableInputType is a separate sibling root for table-bound inputs. Nine consumer sites discriminate by permit identity: GraphitronSchemaValidator, MutationInputResolver, EnumMappingResolver, CatalogBuilder (four sites), FieldBuilder, TypeBuilder. TypeBuilder.findReturnTablesForInput already proves "table-bound" is a property of the consumer, not the input ; derived by O(N) back-scan over schema fields. R215’s lift admitted InputField.UnboundField into TableInputType.inputFields(), collapsing the eager-classification axis ahead of this pivot.
Field-side classification. 45 permits across QueryField (10), MutationField (8), ChildField (27). TypeFetcherGenerator dispatches per-leaf with one arm per permit. A mixin-interface overlay (BatchKeyField, SqlGeneratingField, MethodBackedField, LookupField, TableTargetField) carries cross-cutting traits. Each permit name packs several decisions: where source comes from (root, parent-keyed, list-parent), what the fetcher does (no I/O, @service invocation, generated jOOQ), the field’s output shape (single, list, connection), the jOOQ contribution (none, inlined column, own SELECT, UNION ALL, DML), modifiers (lookup mapping, error channel, splitQuery). RecordLookupTableField collapses four of these onto one identifier; QueryServiceRecordField collapses three. The cross product is the permit set; adding a value to any axis multiplies the permits below it.
Classification-failure encoding. GraphitronType.UnclassifiedType and GraphitronField.UnclassifiedField ride as permits alongside legitimate types and fields, carrying typed Rejection payloads. GraphitronSchemaValidator.validateUnclassifiedType / validateUnclassifiedField translate-then-project ; the validator does a half-job (walk Unclassified carriers; project payloads to ValidationError) on top of its real job (cross-type invariants).
GraphitronField’s sealed parent (`permits OutputField, InputField, GraphitronField.UnclassifiedField) carries the input/output split, and OutputField carries a further sub-seal (permits RootField, ChildField). All three rationales ; the cross-product permits, the input/output sibling split, the failure encoding ; dissolve in this umbrella.
What’s to be: dimensional slots, walker-driven, failure at the wrapper
Three changes, all instances of the same principle.
Surface axes as dimensional slots, not as permits. Each consumer concern lives in its own slot on the field; the field permits flatten to one record per emit-relevant identity (or stay sub-sealed only where authoring scope justifies it). Consumers read the slot they care about. Impossible combinations are excluded at production time, not by permit cross-product. Adding a new axis is additive: new slot, new family, new production logic.
Producers read graphql-java primitives directly. The walker abstraction (Walker<S, C>) ; a pure function over an SDL substrate S returning a sealed WalkerResult<C> ; is one implementation shape; slices may pick another. The load-bearing claim is that producers are thin layers over GraphQLFieldDefinition, GraphQLArgument, GraphQLInputObjectField ; no graphitron-internal substrate model intermediating between the SDL and the carrier. The unit-testability claim (parse a fragment, run the producer, assert on the sealed result) falls out of this shape; the test of correctness is the slot’s shape, not the producer’s.
Validity rides on the wrapper, not the carrier. Every classification step returns a sealed Ok(carrier, diagnostics) | Err(errors, diagnostics). Carriers have only "happy" arms ; valid or the explicit absent arm (No<Family>). Structural failure surfaces through Err; the orchestrator collects errors across the whole pass and blocks downstream generation. Classification runs to completion regardless; the LSP consumes the partial classification output independent of whether generation ran.
Destination sketch
GraphitronField becomes a single field namespace (the renamed OutputField after the input/output split dissolves and UnclassifiedField retires). Each carrier slot lives on the narrowest existing interface that names its property, not as a universal accessor on GraphitronField. The walker is universal across that interface’s implementers; slot presence is interface-gated, and consumers reading the slot through the interface always get a populated value. R238 (the foundation slice) pins this for the service MethodCall family: ServiceMethodCall (sealed Static / Instance) lands on a fresh ServiceField interface that sits sibling to MethodBackedField, not as a sub-interface of it. The earlier umbrella draft anticipated one unified MethodCall carrier on MethodBackedField, with per-directive markers as pure sub-interfaces; R238 surfaced that the call shapes across @service / @condition / @tableMethod / @externalField differ enough (ctor vs static, multiple-DSLContext rules, return-type relationships) that one unified carrier would carry a kitchen-sink of optional fields. Per-directive sibling interfaces let each slice ship a tight carrier scoped to its call shape; MethodBackedField retires only once every per-directive sibling has landed.
Subsequent slices for Pagination, Ordering, PredicateCarrier, ValidationShape, InsertRows, UpdateRows follow the same pattern: find (or introduce) the narrow interface that names the property, put the slot there, and add a marker sub-interface if a consumer subset needs polymorphic dispatch. Interface names land per slice:
| Carrier | Slot home | Status |
|---|---|---|
|
|
R238 (Spec) |
|
per-directive siblings |
future slices; collectively retire |
|
TBD (narrow interface or existing marker) |
future slice |
|
TBD |
future slice |
|
TBD |
future slice |
|
TBD |
future slice |
|
TBD |
future slice (R122 partner) |
|
TBD |
future slice |
A two-layer composition still holds where dimensional slots add real composition over multiple walker carriers: a QueryBuilder for an UPDATE field composes predicate() (WHERE), updateRows() (SET), and the field’s return-type table; a DataFetcherBuilder.Service composes serviceMethodCall() and a class accessor; a ValidationBuilder composes validation(). Where a consumer’s needs are simpler than full composition, a shared emitter parameterised on the carrier itself is the lighter tool: R238 introduces ServiceMethodCallEmitter(ServiceMethodCall) → List<CodeBlock>. Not every carrier needs a dimensional slot; choose per consumer’s need. Consumers attach at whichever layer (carrier, shared emitter, or dimensional slot) matches their concern; the layers compose without re-walking SDL behind them.
Within each sub-seal, R164’s permit consolidation collapses the cross-product permits to one record per emit-relevant identity. RootField as the intermediate between OutputField and QueryField / MutationField retires alongside the parent collapse.
The unified diagnostic surface
Diagnostic is an LSP-aligned sealed family ; severity (Error / Warning / Information / Hint, mirroring LSP DiagnosticSeverity), code (stable string id), source ("graphitron"), message, tags (Unnecessary, Deprecated), relatedInformation. Arms keep type-safe pattern matching on the producer side; the LSP wire-format adapter reads the LSP fields and projects mechanically. AuthorError (the existing Rejection.AuthorError sealed family) carries on WalkerResult.Err.errors; the wire-format adapter projects each leaf to severity=Error LSP Diagnostic records with a code derived per leaf type.
ValidationReport carries errors: List<ValidationError> and warnings: List<BuildWarning> today; the foundation slice adds a walkerDiagnostics: List<Diagnostic> slot alongside them, and once every producer migrates the three slots collapse into one diagnostic stream. From the foundation slice forward, walker output reaches the editor through the same channel today’s validator output does ; Workspace.setBuildOutput(BuildArtifacts, ValidationReport) is the seam, the rest of the wire (recalculateListener → Diagnostics.compute → LanguageClient.publishDiagnostics) is already live, and Diagnostics.validatorDiagnostics gains an arm projecting the walker Diagnostic family.
Single field namespace, no failure permit
After the pivot, GraphitronField’s sealed parent and the `UnclassifiedField permit are both gone:
-
InputField(and its sub-permits) retires as input-side carriers move to slots on the unified field. -
UnclassifiedFieldretires as classification failure moves toWalkerResult.Err. -
OutputFieldandRootFieldretire as redundant intermediate sub-seals ; there is no "Input" half left to contrast with, and theRootFieldbetweenOutputFieldandQueryField/MutationFieldcarries nothing distinctive. -
The surviving permits live directly under
GraphitronField:QueryField,MutationField,ChildField.
Architectural principle this codifies
The rewrite-internal disease is encoding multiple independent axes through one permit set; the cure is dimensional slots populated by independent producers, each producer a thin layer over graphql-java primitives, validity riding on the wrapper.
-
Cross-product encodings hide axes. Per-axis encodings surface them. Adding an axis becomes adding a slot; adding a value to an axis becomes adding an arm to that slot’s sealed family. No multiplication. Impossible combinations are excluded at production time, not by permit cross-product.
-
The walker abstraction is one implementation shape, not the load-bearing claim. The load-bearing claim is that producers read graphql-java primitives directly and return typed sealed results. Slices may share an abstraction or roll their own; the test of correctness is the slot shape, not the producer shape.
-
Absence encoding follows the slot’s home. When a slot is field-universal (lives on
GraphitronFieldor a sub-seal), absence is encoded by aNo<Family>arm: the producer runs unconditionally, the carrier has a no-signal arm, consumers pattern-match exhaustively. When a slot is directive-gated and lives on a narrow interface (R238’s pattern:ServiceMethodCallonServiceField, sibling ofMethodBackedField), absence is encoded by interface non-membership: the producer runs only for implementers, consumers reading the slot through the interface always get a populated value. Both forms make absence first-class; neither usesOptional. -
Validity lives at the wrapper, not inside the carrier. Encoding failure inside the carrier family would force every downstream consumer to either filter or handle the failure arm. Encoding it at the wrapper plus a classification/generation phase split lets downstream consumers assume
Ok-only inputs while classification runs to completion for the LSP’s benefit. -
LSP-aligned diagnostics from day one. Every diagnostic carries the LSP-shape fields (severity, code, message, tags, relatedInformation) so the wire-format adapter is a mechanical projection rather than a translation layer. R226’s reframing of validator output as walker diagnostics, and R222’s walker output, share one wire format.
-
Each axis is independently testable. A producer is a pure function: SDL fragment in, sealed result out. Tests don’t need a graphitron classification context.
Stages
Each stage is a work-stream; spin-out roadmap items file as slices get picked up. The order below names dependency edges, not the schedule. Stages 1–4 are mostly parallelizable across slices; Stages 5–7 are sync points.
Stage 1 — Foundation slice
One vertical slice, end-to-end: one slot on a carrier-bearing interface, one producer that fills it, one consumer migration, one LSP wire arm. The slot’s home (existing narrow interface vs. introduced one), the producer’s implementation shape (sealed Walker<S, C> vs another), and the first consumer to migrate are the foundation slice’s call. The point is that the slot pattern lands once, in tree, demonstrating the pivot’s structural shift end-to-end. The foundation slice fixes the wire-format conventions (source attribution, code namespace, per-walker AuthorError sub-seal) and the slot-home convention (narrow interface, not universal parent; interface-gated absence rather than No<Family> when directive-gated) that subsequent slices inherit. R238 ships the foundation slice as ServiceMethodCall on a new ServiceField sibling of MethodBackedField.
Stage 2 — Walker carrier slots
Each remaining walker-output carrier ships as an independent slice: its sealed family or record, its slot on a carrier-bearing interface (existing or introduced), the consumer migrations that read it, the Diagnostic arms it surfaces. Whether the carrier needs a No<Family> arm depends on the slot’s home: field-universal slots do, directive-gated slots on narrow interfaces don’t. Candidates (minus whichever Stage 1 ships): ValidationShape, Pagination, Ordering, PredicateCarrier, MethodCall, InsertRows, UpdateRows. Slices are parallelizable; no ordering between them.
Stage 3 — Field dimensional slots
R164’s content. DataFetcherBuilder, QueryBuilder, ValidationBuilder dimensional slots land per sub-seal, composing walker carriers and reflection-driven information into emit-ready form. Each dimension’s sealed family lands once; the sub-seal’s cross-product permits flatten under it. Each dimension is a spin-out slice; runs in parallel with Stage 2 once the foundation lands.
Stage 4 — Failure at the wrapper everywhere
R226’s content. UnclassifiedType and UnclassifiedField retire. GraphitronSchemaValidator.validateUnclassifiedType / validateUnclassifiedField retire. Type-level classification (GraphitronSchemaBuilder’s type-classification step) lifts into `WalkerResult<C>. The validator’s surface narrows to cross-type invariant checks. ValidationReport’s `errors / warnings slots collapse into the unified Diagnostic stream.
Stage 5 — Legacy permit deletion
Sync point on Stages 2 + 3 (every consumer reads via slots; every cross-product permit’s dimensional slots have ingressed). Retirements:
-
GraphitronType.InputType4-arm permit +TableInputTypesibling root -
ArgumentRef.InputTypeArg.TableInputArg,PlainInputArg -
InputFieldsealed family (ColumnField,ColumnReferenceField,CompositeColumnField,CompositeColumnReferenceField,NestingField,UnboundField) -
HasInputRecordShapecapability marker -
RootFieldintermediate sub-seal betweenOutputFieldandQueryField/MutationField -
Cross-product field permits per R164’s consolidation (
RecordLookupTableField,QueryServiceRecordField, etc.) -
TypeBuilder.findReturnTablesForInputback-scan
Stage 6 — Namespace collapse
Sync point on Stages 4 + 5. After Stages 4 + 5, GraphitronField’s `permits OutputField, InputField, UnclassifiedField reduces to permits OutputField. Delete the sealed parent; rename OutputField → GraphitronField. Re-flatten field signatures across consumers; the RootField intermediate retires here if not already.
Stage 7 — Directive narrowing
@table, @record(class:), @value drop from INPUT_OBJECT scope; the SDL directive declarations narrow; fixture sweep. Closes R97. Lands anywhere after Stage 5.
What this absorbs
| Item | Absorption mode |
|---|---|
R164 (field-model three-dimension pivot) |
Stage 3 + Stage 5 (permit consolidation). File discarded |
R226 (classification dimensional pivot: diagnostics off the model) |
Stage 4 + unified |
R171 (sealed |
Dissolves; no per-input model record survives |
R97 (deprecate |
Stage 7 directive narrowing closes the item. |
R213 (rejections at consumer field) |
Walker-time |
R209 (FieldRegistry classify-input trace) |
Typed |
R221 (validator walks |
Dissolves; per-permit dispatch retires |
R144 (lookup-key / set-field partition stored on |
Two reversals. (1) Partition lives in |
R215 (column-binding at classification, not usage) |
Subsumed; column binding happens inside each SQL-emitting producer at its leaf-resolution step. R215’s |
R98 (multi-source input validation) |
Dissolves structurally. Per-output-field validation makes "different consumers, different POJOs" the default behaviour, not a structural extension |
Adjacent but not absorbed:
-
R220 / R193 (
ServiceCatalogpredicate consolidation, sealedUnresolvedParam): same disease in a different file. R222 primes the pattern; those items apply it on the consumer-side surface independently. -
R122 (compound-entity-mutations): contract partner. R122 owns `InsertRowsWalker’s tree shape and FK threading; R222 names the slot the producer fills.
-
R200 / R195 (honor
@field(name:)inInputBeanResolver): naming binding between SDL fields and Java members, orthogonal to the pivot. -
R166 Phase 1 (reachability slot): orthogonal; producers run on reachable fields anyway.
Dependencies and sequencing
-
Stage 1 enables Stages 2–4. Slices within those stages are parallel; no inter-slice ordering.
-
Stage 5 syncs on Stage 2 + the parts of Stage 3 whose dimensional slots compose Stage 2 carriers.
-
Stage 6 syncs on Stages 4 + 5.
-
Stage 7 lands anywhere after Stage 5.
-
R215 (Done): unbound-field admit set generalises to producers' "unresolved" arms; no further build-order concern.
-
R94 (shipped): the existing
HasInputRecordShapemarker +InputRecordShaperecord become theValidationShapecarrier; the slice that ships this is per-output-field POJO emit inInputRecordGenerator. R94’s per-input POJO becomes per-(output-field × input-type-typed-arg) POJO; R98’s multi-source case becomes default behaviour.
Vocabulary
The names below are the working vocabulary for the umbrella; slices may rename, narrow, or restructure as their implementation details surface. Treat this as anchor terminology, not a frozen API.
-
Walker<S, C>; a pure function over an SDL substrateSreturningWalkerResult<C>. One implementation shape for producers; slices may pick another. Substrate-parametric for forward-compat with type-level producers. -
WalkerResult<C>; sealedOk<C>(C carrier, List<Diagnostic> diagnostics)/Err<C>(List<AuthorError> errors, List<Diagnostic> diagnostics).Okrejects Error-severity diagnostics by compact-ctor;Err.errorsis non-empty by compact-ctor invariant. Classification runs to completion regardless of how manyErr`s; downstream generation is blocked when any `Erris present. -
Carriers:
ValidationShape,Pagination,Ordering,PredicateCarrier, theMethodCallfamily (per-directive:ServiceMethodCall,ConditionCall,TableMethodCall,ExternalFieldCall),InsertRows,UpdateRows. Each a sealed family or record carrying the reduced output.No<Family>arms apply when the slot is field-universal; directive-gated slots on narrow interfaces (R238’s pattern) skip theNo<>arm and use interface non-membership instead. NoInvalidarm in either case; structural failure rides onWalkerResult.Err. -
Dimensional slots ;
DataFetcherBuilder,QueryBuilder,ValidationBuilder. Compose walker carriers + reflection-driven information into emit-ready form. -
No<Family>: the domain arm naming "the substrate carries no actionable signal for this family." Producer ran, no error, nothing to encode. Applies when the slot is field-universal; directive-gated slots on narrow interfaces use interface non-membership instead. Concrete shapes vary per family (NoPredicates,NoValidationShape, …); framing is uniform across the cases that need it. -
PredicateCarrier’s two valid arms; `Condition for SQL-emitting read fields,LookupRowsfor mutation fields. The producer’s bailout-restart pattern handles role discovery: sentinel directives (@lookupKeyon a read field,@multirowson a mutation field) trigger an arm flip. Consumers pattern-match the arm at use time. -
MethodCallfamily: per-directive records carrying(target, methodName, bindings, returnShape). R238 pins the first instance ;ServiceMethodCall(sealedStatic/Instance) ; and lands its bindings asMappingEntryarms (FromArg,FromContext,FromDsl) plus a recursiveValueShapefamily for input-object bindings. Subsequent slices addConditionCall,TableMethodCall,ExternalFieldCallwith their own binding shapes. The earlier umbrella draft named one unifiedMethodCallwithParamBindingarms covering every directive (FromEnvArg,FromContextKey,FromDslContext,FromBatchKeys,FromSourceRow); R238 split it per-directive because call shapes differ enough that one unified carrier would carry many always-absent slots per callsite. -
Shared emitter: a static utility parameterised on a carrier-bearing interface that produces emit-ready code fragments (var-decls, expression blocks). R238 introduces
ServiceMethodCallEmitter(ServiceMethodCall) → List<CodeBlock>. Lighter than a dimensional slot when the consumer’s only need is the carrier’s emission, not multi-carrier composition. Slices choose between shared emitter and dimensional slot per consumer’s need. -
Per-directive sibling interface: a sibling of
MethodBackedFieldcarrying one directive’s call slot. R238 introducesServiceField(carryingServiceMethodCall) as the first instance. The earlier umbrella draft framed this as a pure marker sub-interface ofMethodBackedField; the sibling shape lets each slice ship narrow without forcing the other sixMethodBackedFieldimplementers to grow no-op slot accessors.MethodBackedFieldretires once every per-directive sibling has landed. -
BackingClass; three-arm sealed family (Pojo,JavaRecord,JooqTableRecord); attaches per binding kind where method-call semantics need it. -
Diagnostic; LSP-aligned sealed family. Carries non-error events on bothOkandErr.BuildWarningmigration retires; the channel is one unified stream at the LSP boundary. -
AuthorError; the existingRejection.AuthorErrorsealed family. The wire-format adapter projects each leaf to severity=Error LSPDiagnosticwith a code derived per leaf type (e.g.AuthorError.UnknownName→"graphitron.unknown-name"). -
@table/@record(class:)on input types ; drop entirely. Table-binding collapses to the consumer’s@tablereturn at production time.@record(class:)’s deserialization-target function collapses to the user’s declared service-method param type, read by reflection at the `MethodCallproducer’s site. -
@valueon input fields ; drops as redundant scaffolding. The WHERE-vs-SET partition derives from catalog PK membership inside the SQL-emitting producers.
Out of scope
-
The R164 sub-dimension internals (
DataFetcherBuildersource-cardinality + action + field-cardinality axes, etc.) ; Stage 3 spin-outs own those. -
The R122
InsertRowsWalkertree shape and FK threading ; R122 owns. -
ServiceCatalogpredicate consolidation (R220 / R193) ; adjacent disease in a different file. -
argMappinggrouping syntax (R97 Phase 1) ; adjacent. -
Reachability pruning across all type kinds (R166 Phase 1) ; orthogonal.
-
Producer-side unification of method invocation paths (uniform reflection-mapping rules across
@service/@externalField/@tableMethod/@condition) ; separate work that R164’sDataFetcherBuilderdimension may absorb piecewise; not load-bearing on the umbrella.
Previous design attempts
Rejected alternatives from earlier R222 drafts, recorded so reviewers don’t re-derive the dead ends.
-
Horizontal Phase 1 (all carriers, all slots, all
Diagnosticarms up front). Earlier draft committed seven carrier families, seven slot getters, fourDiagnosticarms in Phase 1 with onlyValidationShapepopulated. Rejected: vocabulary that doesn’t ride a live producer is a contract committed before any consumer pulls on it. Vertical slices each ship their own vocabulary. -
R164 + R226 as separate items. Treated as adjacent contract partners. Rejected because the work overlaps structurally: R164’s dimensional slots compose R222’s walker carriers; R226’s
Unclassified*retirement uses the sameWalkerResult.Errwrapper R222 produces. Unifying as one umbrella with parallel spin-outs replaces the coordination tax with explicit stage dependencies. -
Recursive
InputUsagecarrier scoped to SQL emission. First pivot folded WHERE construction, DML row-shaping, lookup-key identification, method-param binding, pagination, and ordering into one classified output that consumers re-discriminated by role. Rejected as wrong granularity. -
Invalid<Family>arms inside the carrier families. Encoded structural failure inside two of the seven families. Rejected because no downstream generator inspects anInvalidarm ; generation is blocked before any generator runs in either failure mode, so the asymmetry encoded an invariant the wrapper + phase split already enforce. -
Input/InputFieldDeclper-input wrapper records. Pass-through wrappers overGraphQLInputObjectType/GraphQLInputObjectField. Rejected because per-input identity has no carrier-independent state worth a record, and graphql-java already provides every accessor. -
SchemaCoordinate(String)identity wrapper. Stringly-typed wrapper conflating"FilmInput"and"FilmInput.title". Rejected because plainStringcovers the type-name case and graphql-java’sFieldCoordinatescovers the type+field case. -
Optional carrier slots on
OutputField(Optional<Pagination>, …). Presence-vs-absence at the storage layer. Rejected for field-universal slots because theNo<Family>arm makes absence a first-class domain state consumers pattern-match exhaustively; Optional re-introduces a present/missing flag the sealed family already encodes. For directive-gated slots, R238 surfaced a third option: interface-gated presence (slot lives on a narrow interface, absent for non-implementers). Optional remains rejected in both cases. -
ValidationShapeas a per-input carrier (Map<String, ValidationShape>on the classification artifact). Two-substrate variant: per-output-field carriers onOutputField, per-input carriers in a name-keyed map. Rejected because validation fires at the resolver method-arg boundary, which is the output field’s seat; the "global common shape across consumers" framing tried to reuse a per-type POJO, but the consumer is the unit of validation. -
MethodArgumentsas the carrier name. Earlier vocabulary draft. Replaced byMethodCallto align with the rewrite’s existingCallParam/CallSiteExtraction/MethodRef.Callnaming family.