ID |
|
|---|---|
Status |
Backlog |
Bucket |
architecture |
Priority |
6 |
Theme |
diagnostics |
Widen string-carrier intermediates onto Rejection (R58 follow-up)
R58 lifted the direct candidate-hint producers onto typed
Rejection.AuthorError.UnknownName
factories. Five intermediate carriers still flatten the typed shape into prose before it reaches
a Rejection consumer, blocking five candidate-hint producers from reaching the typed surface
their factories (unknownForeignKey, unknownTypeName, unknownEnumConstant,
unknownNodeIdKeyColumn, unknownColumn) already exist for. R58 Phase D shipped the factories;
this plan adds the carrier widenings so the typed values reach consumers.
R58 is Done (see the changelog), so its factories are settled surface; the original reversion
hedge is struck. Anchor note (2026-07-13): the carrier audit below is re-verified as still
accurate at the symbol level, but BuildContext.java has roughly tripled since filing, so the
bare line numbers in items 1-5 have drifted; re-derive them from the named symbols at pickup.
Current definition anchors: record ParsedPath(..., String errorMessage, ...) at
BuildContext.java:1037 with the sibling record ChainSegment(..., String errorMessage) at
:1403 (a sixth string carrier of the same shape, added after filing; fold it into Phase A),
record Unresolved(..., String reason) at InputFieldResolution.java:20,
record UnboundArg(...) at ArgumentRef.java:218, and
record Mismatch(Rejection rejection) at EnumMappingResolver.java:76.
Carrier audit
-
BuildContext.ParsedPath.errorMessage: String; populated byparsePathElement’s `List<String> errorsaccumulator (BuildContext.java:388); surfaces at everyparsePath().hasError()consumer inFieldBuilder,BuildContext,NodeIdLeafResolver,TypeBuilder. Widening to a singlerejection: Rejectionunlocks the producer atBuildContext.java:583-584(FK SQL name miss →unknownForeignKey). -
InputFieldResolution.Unresolved.reason: String; built byBuildContext.classifyInputFieldand consumed byBuildContext(typename inference, nested field aggregation) andInputFieldResolver. Widening torejection: RejectionunlocksBuildContext.java:875-878(typename in@nodeId→unknownTypeName) andBuildContext.java:1015-1016(column-in-path-leg →unknownColumn). -
ArgumentRef.ScalarArg.UnboundArg.reason: String; single producer atFieldBuilder.java:849-852(column on filter table). Called out as out of scope in R58 Phase D. Widening torejection: Rejectionunlocks theunknownColumnmigration. -
EnumMappingResolver.EnumValidation.Mismatch(Rejection rejection); the carrier already holds aRejection, but the producer atEnumMappingResolver.java:159-162joins per-constant misses into a single prose blob viaString.join("; ", mismatches)and wraps inRejection.structural(...). Surfacing each miss asunknownEnumConstantwith its own candidate set requires widening the component toList<Rejection> rejections. The "GraphQL type is not an enum" branch at:145-146is single-rejection; under the widened shape it becomesList.of(Rejection.structural(...)). -
TypeBuilderaggregations ; same multi-miss pattern as (4):-
TypeBuilder.java:397-409(keyColumnErrors: List<String>) joins to oneRejection.structural(...). Widen the accumulator toList<Rejection>and migrate per miss tounknownNodeIdKeyColumn. -
TypeBuilder.java:686-708(failures: List<InputFieldResolution.Unresolved>) joinsUnresolved.reasonstrings. After Phase A2 eachUnresolvedcarries aRejection; this site fans those out per miss ontounknownColumn.
-
Phases
-
Phase A ; single-Rejection carrier widenings (items 1-3). Independent; each independently shippable.
-
A1 (item 1): widen
ParsedPath.errorMessage: String→rejection: Rejection. UpdateparsePathElement’s accumulator from `List<String>toList<Rejection>and join into a singleRejectionatparsePathexit (one rejection per path-parse failure preserves the current per-call-site error semantic). Migrate the FK-name producer at:583-584ontounknownForeignKey. Other producers inparsePathElementconstructRejection.structural(reason)to preserve current prose at the byte boundary. -
A2 (item 2): widen
InputFieldResolution.Unresolved.reason: String→rejection: Rejection. Migrate:875-878ontounknownTypeNameand:1015-1016ontounknownColumn. OtherUnresolvedconstruction sites wrap inRejection.structural(reason). Required before B2’s second site (TypeBuilder.java:686-708) soUnresolvedalready carries the typed shape its consumer can fan out. Co-design note (2026-07-15): two other Backlog items grow this sameUnresolvedrecord and its fold path, and neither cross-referenced this one before now. R209 (field-registry-typed-rejection-trace) fork (a) is verbatim this A2 change (widenUnresolvedto carry aRejection); if A2 ships first, R209 collapses to a consumer update. R213 (input-field-rejection-attribution) adds aSourceLocationto the same record and reshapesfoldRejectionsto carryList<LocatedRejection>. A located typed rejection is the natural union of A2 and R213, so whichever lands first should carry the combined(SourceLocation, Rejection)shape rather than force the second to revert it. Sequence the three knowingly. -
A3 (item 3): widen
ArgumentRef.ScalarArg.UnboundArg.reason: String→rejection: Rejection. Migrate:849-852ontounknownColumn. -
Phase B ; multi-miss carrier widenings (items 4-5). Decided shape:
List<Rejection>at the aggregating carrier; consumer fans out to NUnclassifiedField/UnclassifiedType(and therefore NValidationError`s).Rationale: keeps R58’s single-rejection-per-Unclassified*` /ValidationErrorinvariant intact; each typed miss surfaces as its own validator entry, which is more useful for IDE/LSP consumers than a joined prose blob. Considered alternatives: aRejection.Multi(List<Rejection>)seal arm (rejected: complicates the seal and forces everyRejectionconsumer to handle nesting); wideningValidationErroritself (rejected: R58 Phase I just locked the single-rejection shape). -
B1 (item 4): widen
EnumValidation.Mismatch(Rejection)→Mismatch(List<Rejection>). Producer at:159-162emits oneunknownEnumConstantper missing constant. Branch at:145-146becomes a singleton list. Consumer inFieldBuilder(column-bound enum filter classification) fans out to oneUnclassifiedFieldper typed rejection. -
B2 (item 5): same widening on
TypeBuilder.keyColumnErrorsand thefailures-list rendering at:686-708. Producers emit oneUnclassifiedTypeper typed miss instead of one type-levelRejection.structural(joined). Author-side UX shift: a multi-miss@nodetype now produces N validator entries (one per unresolved key column) instead of one entry with semicolon-joined prose. Documented in the changelog as an intentional trade for typed candidate hints.
Tests
-
Pipeline (extending
R58TypedRejectionPipelineTest): -
One case per Phase A unlock point (
unknownForeignKeyvia path-parse,unknownTypeNamevia@nodeId,unknownColumnvia path leg,unknownColumnvia filter-table column). -
Phase B fan-out: a 3-miss enum mapping produces 3
ValidationError`s, each carrying `Rejection.AuthorError.UnknownNamewithattemptKind = ENUM_CONSTANTand its own candidate list. -
Phase B fan-out: a 2-miss
@node(keyColumns:)produces 2ValidationError`s, each carrying `unknownNodeIdKeyColumn. -
Model (extending
RejectionRenderingTest):EnumValidation.Mismatch.message()joins the list via;so the byte-stable log surface validator consumers depend on is preserved when a caller chooses to render the multi-miss carrier as a single line (e.g. log emission).
Out of scope
-
Threading nested rejection chains as a typed
Rejection.NestedRejectarm. Same deferral as R58 ; not on any in-flight consumer’s path. -
LSP fix-its consuming the typed
UnknownName.candidateslists this plan unlocks. R18 wires the consumer; this plan supplies the typed shape. -
Lifting
ArgumentRef.UnclassifiedArg.reasonontoRejection. Separate axis (argument classification, not field classification); same shape-pattern but different producer set. -
BuildWarning.message. Single producer site; premature. -
Renaming
RejectionKind. Same deferral as R58.
Fact-base note (2026-08-06)
Split the phases by survivorship before spending effort: several carriers here (Unresolved, UnboundArg) sit on the surface the strangler frame retires, where the typed payload becomes columns on a violation row, so widening them is work against a dissolving surface. Widen only the carriers whose consumers survive the migration.
Context and the whole-board picture: roadmap/audits/2026-08-06-fact-base-impact-sweep.md.