| id | 238 | |
|---|---|---|
| title | cuelite phase 2 — surfaces A + B (schema, query) | |
| status | ✅ | |
| model | opus | |
| summary | Move internal/schema, requiredstructure, and internal/query onto cue/cuelite's compile/unify/validate façade (green), then flip to the in-house value model and validator — validating front matter directly without the JSON round-trip, preserving per-field MDS020 diagnostics, checked against the CUE oracle across the whole corpus. | |
| depends-on |
|
Move schema validation (MDS020) and query/where: onto
cue/cuelite, then flip them to the in-house value model and
validator.
Phase 2 of plan 218, the largest migration. Surface A is internal/schema and requiredstructure; surface B is internal/query, a strict subset of A. See plan 218 for the value model and the unification rules.
- Adopt the compile/unify/validate façade already shipped by
phase 0 (plan 236):
Compile,CompileJSON,Value.Unify,Value.Validate, and theErrorsaccessor. Extend it with the per-surface methods these call sites need (LookupPath,Decode,Fields, …), still delegating to CUE. The phase-0 interim has two costs to retire here. First, eachCompile/CompileJSONowns a fresh*cue.Context, and a cross-contextUnifyrebuilds whichever side retains source into the other side's context — one rebuild per suchUnify, leaving the result in (and mutating) the non-rebuilt side's context. Second, the schema path still marshals front matter to JSON andCompileJSONparses it back, so validating one file pays three JSON traversals — the marshal,CompileJSON's duplicate-key scan, andcuejson.Extract's own parse. The duplicate scan is interim-only: the post-flip hot path validates themap[string]anydirectly and bypassesCompileJSONentirely, so the scan disappears with the round trip. All three blow the ≤ 10 allocs/op budget on the hot path, so the budget is met only by the flip in task 3 — the in-house engine validates amap[string]anydirectly (plan 218), with no JSON round-trip and no per-Valuecontext — not by the façade adoption in this task. - Move internal/schema, requiredstructure, and internal/query onto the façade. The suite stays green.
- Flip to the in-house value model,
Unify, andValidate— red/green per rule and per ⊥/error path. Validate front-matter maps directly, with no JSON marshal. - Hold the differential harness green across every
frontmatter:constraint, the file-kinds conflict table, and the query/where:examples.
-
internal/schema,requiredstructure, andinternal/queryimportcue/cuelite, notcuelang.org/go(non-test files; task 2). - Front-matter validation skips the JSON round-trip
(
CompileMap/LiftMap). The per-rule alloc-budget test passes; the hot-path benchmark drops to 85 allocs/op (from 356) and 8.0 µs/op (from 89 µs), 0.25× CUE. - MDS020 diagnostics stay actionable and navigable: the schema suite (plan 147 / plan 230 tests) passes unchanged.
- The harness shows in-house and CUE agree on the full
corpus, the real-repo-schema sweep, and a 300 s
schema×data fuzzer.
cue/cueliteandinternal/cuelitetestreach 100 % statement coverage after the task-4 dedup; gobco branch coverage is 1276/1278 (two structural arms). - All tests pass:
go test ./... -
go tool golangci-lint runreports no issues.
- Build paths from data keys via
MakePath, neverParsePath.query.collectPathsand any other consumer that derives a path from amap[string]anykey (e.g. iteratingValue.Fields()) must usecuelite.MakePath(segs...), which stores raw segments — including empty, dotted,true-headed, or quote-needing keys thatParsePath(the narrower string-label EXPRESSION grammar) cannot parse back. ReserveParsePathfor user-typed path expressions. There is deliberately noPathrender today (PathError.Error()'s dot-join is lossy and display-only); a futurePath.String()must quote any segment that is not a bare identifier. - LookupPath provenance. A derived
Value(aUnifyresult) is context-pinned and retains no source, so it cannot be rebuilt into another context.LookupPathon such a value inherits that limit: a section-level lookup against a cached schema must keep rebuildable provenance — for example the root source plus the looked-up path — or the cached schema either races (each lookup mutates its shared context) or forfeits caching (every lookup recompiles). Choose the provenance representation when addingLookupPath, not after. - Call-site operand order. Today's adopters call
schemaVal.Unify(dataVal)in validate.go andm.schema.Unify(dataVal)in query.go — the SHARED schema is the receiver, so the cross-context rebuild puts the schema's context in the mutated (non-rebuilt) position. Phase 2 must pick the operand order and locking deliberately: a schema cached and reused across files or goroutines cannot sit in the mutated position without synchronization. - RunCache / CachedCompile shape (task 2). The cache's contract
— one schema compile per Run, shared across parallel workers via
CompiledCUE.Ctx— has NO façade equivalent, because cuelite hides the context. Neither operand order resolves it under the cache:schemaVal.Unify( dataVal)rebuilds the data into the shared schema's context and MUTATES it (a data race under-raceCI when workers share the cached schema), whiledataVal.Unify(schemaVal)rebuilds the schema per file and recompiles it each call (the cache becomes a no-op and the compile-once assertions in validate_runcache_test.go break). So task 2 must redesign the cache shape, not just adopt the façade — cache the SOURCE and compile per worker, or guard the sharedValuewith a lock, or defer caching to the in-house flip in task 3 (where a context-freeValueis shareable). Name the affected files when scheduling:compile_cache.goandvalidate_runcache_test.go.
Added the surface-A/B accessor methods to cue/cuelite, still
CUE-backed, each with a dedicated unit test and 100 % statement
coverage held:
Value.Exists,Value.LookupPath(Path) (Value, bool),Value.Fields() []Field(with theField{Selector, Value}shape),Value.String,Value.Decode— incue/cuelite/access.go.- LookupPath provenance decision (plan note resolved). A
LookupPath/Fields result keeps REBUILDABLE PROVENANCE — the root
source plus the path that reached it (
lookupRoot,lookupPath,hasLookuponValue) — instead of pinning the context-bound derivedcue.Value.Value.rebuildgained ahasLookupbranch that recompiles the root in the target context and re-applies the path, so a section-level lookup against a cached schema crosses contexts without mutating the shared value (the race the note rules out). Fields extends the prefix one selector per child so a nested field carries the full path. - Differential harness extended:
internal/cuelitetest/access.goadds anAccessCase/AccessOutcome/RunAccessarm comparing Exists/LookupPath/Fields/String against a direct-CUE oracle over a per-class corpus; green in CI as the flip scaffold.
All three packages import cue/cuelite now. No non-test file imports
cuelang.org/go. The grep over those trees is empty.
One cuelang import survives, in
internal/schema/shortcuts_test.go. It is a test. It cross-checks the
shortcut canonicals against CUE, like the harness oracle, so it stays.
Every existing test stayed green unchanged. The CompiledCUE-typed
assertions in compile_cache_test.go and validate_runcache_test.go
still pass. CompiledCUE is a schema-package type. Its internals
swapped to wrap a cuelite.Value. Its .Err() and identity surface
did not change.
Added Value.Err() to the façade. It reports compile/bottom status.
It skips the concreteness check Validate applies. CompiledCUE.Err()
and checkUnifiable need exactly that.
RunCache / CachedCompile shape decision. Cache the
source-retaining compiled value. Fix the Unify operand order: the
shared schema is the operand, the per-file data is the receiver. Every
call site reads dataVal.Unify(schemaVal) — validate.go,
requiredstructure, query.go.
Under cuelite's rebuild, the receiver's context is the one rebuilt
into. So the per-file data context absorbs a fresh recompile of the
schema's source. The shared cached Value is only read for its src.
It is never mutated.
This keeps the cache's compile-once contract. The
validate_runcache_test.go slot-populated assertion still holds: the
slot is keyed by CUE source and built once per Run. It is also
-race-clean when parallel workers share the cached schema.
CachedCompile_ConcurrentSingleBuild and the runcache compile-once
test both pass under -race.
The per-file schema recompile is the interim cost. Task 3's
context-free immutable Value erases it. The operand order then stops
mattering. compile_cache.go's CompiledCUE dropped its Ctx field,
because cuelite hides the context.
MDS020 PER-FIELD diagnostics stay byte-identical. The error walkers
consume []*cuelite.PathError now, which they get from
cuelite.Errors; the old code used []errors.Error. Both carry the
same .Path() route. So the per-field diagnostic, the dedup key, and
the anchor line do not change. The schema suite passes unchanged,
including the plan-147/230 diagnostic-shape tests.
The byte-identity claim is scoped to per-field diagnostics. The
front-matter-shape diagnostic is a synthetic fail-safe now. It fires
only on a value the lifter cannot represent. Its wording moved to the
LiftMap check.
The flip landed: an in-house value model, Unify as lattice meet,
direct map[string]any validation, and a schema/data fuzzer. The
differential harness (validate, access, path arms) is the oracle.
The interim per-Value-context behaviors broke from single-context CUE on
purpose. The flip restores single-context CUE as the truth. So four
pinned-test classes were rewritten, with sign-off:
- Cross-context bottom tests now assert the post-flip contract: a chained
unify of derived values SUCCEEDS and validates per single-context CUE.
TestValue_Unify_singleContextOraclerebuilds each chained composition by unifying the same source fragments inside ONEcue.Contextand asserts the oracle's accept/reject matches the in-house engine — the direct single- context check behind this claim (the two-input cuelitetest harness covers the schema×data shape; the multi-fragment chained compositions are pinned by this oracle test).errCrossContextand therebuildmachinery were deleted with their tests. Errors() → []*PathError(+PathError.Unwrapto the engine's own cause). Theerrors.As-to-cuelang assertions were dropped; in-house sentinel unwrap tests (TestValidate_unwrapsBottom) kept.- Exact CUE message strings re-pinned on the in-house engine's stable
wording:
conflicting values <a> and <b>, path-tagged. - The CUE-only private-helper tests (
buildJSON/rebuild/scanDuplicateJSONKeys/jsonLevel/recordKey/cueErrorsOf) were deleted with their helpers. The strict-JSON duplicate-key CONTRACT ofCompileJSONis durable and preserved by the in-house JSON lifter; its behavior-level tests stay green.
The constraint parser reuses cuelang's syntax frontend (cue/parser →
cue/ast) in phase 2; the in-house compiler walks that AST into the
value model. The evaluator stays fully in-house (unify, validate,
concreteness) — that is the actual flip. The oracle keeps its own
direct-cuelang evaluator, so a shared parser does not collapse the two
arms. Phase 4 (plan 240) swaps the cuelang parser for a hand-rolled one
and drops cuelang.org/go; this interim is its removal target. The
blocker — four pinned CUE-behavior test classes — was resolved by the
authorization the "Test-contract flip" section records.
Dedup refactor. The duplicate builders in eval.go collapsed
into one scope-threaded set. compileExpr is the unscoped face of
evalExpr (a nil scope), and evalChild routes the field, element,
and branch positions through the compile-time deferral. An index or
relational over an unresolved sibling becomes a thunk. A bare
reference stays a hard "reference not found". Five compile-only
builders were deleted.
Coverage 100 %. cue/cuelite and internal/cuelitetest are both
at 100 % statements. Residual defaults were driven red/green on
constructed values or restructured away. The two ast.LabelName-error
branches were removed: the parser always yields an *ast.Ident
selector member, read via selectorName. The impossible list-tail
nil-fills were dropped under the "openTop ⟹ elem != nil" invariant.
gobco. go tool gobco -branch ./cue/cuelite reports 1276/1278
conditions. The two remaining are the structural gaps plan 237
records (path.go's sepBracket switch arm and multiline.go's
for i > 0 walk-back bound), neither in the flipped engine.
Alloc. The internal/integration per-rule gate passes. MDS020
allocates 0.0/op on the gate fixture (no front matter → early exit).
The schema-validate hot path it delegates to measures
BenchmarkValidate/cuelite 7.9 µs / 85 allocs (0.24×/0.40× CUE) and
BenchmarkCompileValidate 22.1 µs / 205 allocs (0.35×/0.53× CUE).
Factor gate tightened. HotFactorBudget and ColdFactorBudget
set to 1.0 at this flip, not deferred to plan 240. The interim
hot-looser-than-cold guard now asserts both ≤ 1.0×. The armed gate
passes with margin: hot 0.20–0.30×, cold 0.36–0.40×.
Round 2 replaced flatten-and-mark-pointers with CUE's per-disjunct
mode (engineValue.modes).
evalDisjunctionflattens by VALUE, so a collapsed sub-disjunction loses its default:(*0|0)|10rejects (the plan-239 carry;schemaHasNestedDuplicateDefaultdeleted).unifyDisjunctionprunes a NESTED-bottom branch and reconciles the meet default from operand defaults;concreteValueEqualcovers*[].forceThunkFixpoint,evalIdent, and anevalBinarythunk-&deferral complete P0.
Each claim was probed against v0.16.1. The 600 s fuzz found one divergence, fixed and seeded. Coverage 100 %.