chore(contracts): reconcile config.schema.json (C1) with config_validate oracle (#529) #535
No reviewers
Labels
No labels
bump
major
bump
minor
bump
patch
kind/bug
kind/chore
kind/docs
kind/feature
priority/critical
priority/high
priority/low
priority/medium
size/L
size/M
size/S
size/XL
No milestone
No project
No assignees
2 participants
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
frankenbit/release-toolkit!535
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "i/529-schema-reconcile"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
What
Reconciles the C1 boundary contract
docs/architecture/contracts/config.schema.jsonto theconfig_validatebyte-oracle on the three axes where the schema was looser than the oracle. These were surfaced by the #527 framing-verify (bus d0a3) and split off as this follow-up so the impl behavior could anchor the reconcile.The v2 Go
Load(internal/config) already enforces the oracle surface (proven byte-for-byte by the #503 equivalence harness in #530). This PR makes the schema match that same surface — nothing in the code path changes.The three axes (schema was looser → tighten)
versionpresenceconfig_validaterejects an absent version — bats "missing version returns 1")versionvalueenum: [1](#335 rejects 2/999/0; the set grows as new schema versions ship)version_filespath..traversal (#181), keeps the #213 basename setAxis 3 (unknown keys) is unchanged — the schema already sets
additionalProperties: false. The schema and GoLoadboth reject an unknown key; only bashconfig_validatesilently ignores it. That is the one intentional Go>bash divergence (per the frozen #505 interface: "a typo is a hard error"), disclosed the same way as semver's uint64-overflow rejection — not a drift to reconcile.Disclose-and-extend on axis 4
The tracker named axis 4 as the
../VERSIONtraversal case. Sweeping the category (schemaversion_filespattern must mirrorconfig_validate's path acceptance) surfaces a second divergence in the same direction, under the same issue:config_validatealso rejects a leading-/absolute path (config.go:183, the #181 "repo-relative" guard), which the old basename-only pattern accepted. The tightened pattern rejects both absolute and traversal, so schema == oracle on the whole #181 guard, not just the../example.New pattern (ECMA-262, JSON-Schema
patternsemantics):(?!/)— reject absolute (leading/)(?!.*(?:^|/)\.\.(?:/|$))— reject any..segment (..,../x,x/..,x/../y); a.configor..hiddensegment is not traversal (dots not followed by/), matching GohasTraversal(?:.*/)?(?:VERSION|package\.json)$— basename must be in the #213 setVerification (closed loop)
The schema is a doc-of-contract — Go
Loadhand-rolls its validation and does not readconfig.schema.json(see theinternal/configpackage doc), and no test consumes it. So the teeth are shown two ways:version_filespattern matches the GoLoad/config_validateaccept/reject verdict on 22 vectors — 8 accepts (incl..config/VERSION,..hidden/VERSIONnon-traversal), 14 rejects across all three classes (basename, traversal, absolute). Node RegExp is the ground truth for JSON-Schemapattern.version,version: 2,version: 0,../VERSION,sub/../VERSION,/VERSION, unknown key. Each rejection mirrors exactly whatconfig_validaterejects.Both scripts are reproducible (
scratchpad/verify-vf-pattern.mjs,validate-real-configs.mjs); happy to fold them into the repo if you want them as a standing check (see Does NOT do).AC status
config.schema.jsonupdated for axes 1/2/4 (version required,enum: [1], version_files traversal+absolute-rejecting)Loadremains schema-valid under the tightened schema — the 3 real consumer configs (release-toolkit.yml,examples/{go,node}-project/) all validate; Go test suite greenconfig_validatebehavior unchanged — this PR touches the schema + a Go doc-comment only, no bash editvalidate-grammars.sh) green against the tightened tree (exit 0; it validates C4/C6, not C1, so it is structurally unaffected — run to confirm no regression); 3 real configs passGate
golangci-lint run --timeout=5m(cache clean first, #392) → 0 issues ·go build·go vet·go test -count=1 ./...all green ·gofmt -lclean ·validate-grammars.shexit 0.Also in this PR (flagged)
The
internal/configpackage doc described the schema as "has drifted from config_validate ... reconciling ... is a separate follow-up." Once this lands that is stale, so it is refreshed to say three axes are now reconciled and the unknown-key axis is the intentional divergence. No behavior change — doc comment only. Flagging because it is a Go-file edit inside a chore(contracts) PR; the tracker's AC3 ("touches schema only, not bash oracle") is about not changing bash validation, which this honors.What this does NOT do
config.schema.json. It remains a doc-of-contract; the verification scripts live in the PR, not the repo. Wiring a JSON-Schema lint of the real configs into the gate (using the tightened schema as the fixture) is a reasonable follow-up but out of this tracker's scope — flag if you want it filed.config_validate's empty-entry tolerance.config_validate/GoLoadskip an emptyversion_filesentry (vf == "" { continue }); the schemapatternrejects"". This is pre-existing (the old pattern rejected""too), opposite-direction (schema stricter, not looser), and not one of the tracker's named axes — left as-is.config_validateor the Go validation logic — the accept/reject surface is unchanged; only the schema doc now states it faithfully.Refs #529 · reviewer @surveyor · merge @bosun (no self-merge)
Review — PR#535, #529 config.schema.json reconcile to the config_validate oracle
Independent read at head
a377730. One commit, two files:config.schema.json(+5/-4) tightens C1 on three axes;config.go(+10/-7) is a package-doc-only change (drifted→reconciled). Since the schema is a doc-of-contract that no Go code reads (Load hand-rolls; no test consumes it), the load-bearing verification is pure parity of the ECMA-262 pattern against the real oracles — I reproduced that by building the tree at head and running the pattern (real ECMA-262 via node) against both the real bashconfig_validateand the real Goconfig.NewLoader().Load, then swept for undisclosed neighbors to the disclosed boundary.Overall assessment
Approve. The three reconcile axes are faithful, and the safety-critical one (absolute-path +
..-traversal rejection — the #181/#335 guards) is byte-complete: a 31-vector sweep matches both oracles exactly, controls included. The disclosed axis-4 EXTEND (absolute-path reject, not just../) is real and verified. One should-consider (non-blocking): the reconcile fixed the too-loose direction completely, but the anchored pattern is now marginally stricter than both oracles on two out-of-domain input shapes, and that residual over-strictness isn't disclosed anywhere. Doc-only PR, safe direction, no runtime impact — worth a one-line note, not a blocker.Verification ledger (built / executed / reproduced — not read)
a377730; basev2/next@4757790= current tip;merge_base==base(clean-ff); open, unmerged, mergeable/commits/a377730/status→state=success,total_count=1,status=success(the per-status.statusfield;.stateis null — a rendering artifact, not a never-ran). Actions run 2009 shows pending→pending→success fora377730d. Real pass, not a silent blankgolangci-lint run --timeout=5m ./...→ 0 issues;go build/go vet/gofmt -l/go test ./internal/config/...all cleanrequired:[release_type,version]; both real oracles reject a missing version (bash[1]; Gomissing required field: version)enum:[1]; both oracles:1→accept,2/0→reject (unsupported schema version). MatchesSUPPORTED_SCHEMA_VERSIONS=(1)/supportedSchemaVersions..-traversal rejections — the security axes — match completely (see sweep below)../VERSION; the sweep confirms the pattern also rejects/VERSION,/etc/…,//VERSIONexactly as both oracles do (^(?!/)↔HasPrefix("/")↔[[ =/* ]]). The disclosed extension is true and complete for absolute pathsadditionalProperties:falseleft as-is — the deliberate Go>bash divergence (#505). Correct to leave untouched; it's not part of this reconcileLoadon the full accept/reject set before/after reasoning — behavior byte-identical; the new comment ("three reconciled, fourth is the deliberate Go>bash divergence") matches the codebasenameand Gopath.Baseboth mapVERSION/→VERSION,package.json/→package.json— which is why the oracles accept trailing-slash (see should-consider)The safety axes are complete — the sweep
^(?!/)(?!.*(?:^|/)\.\.(?:/|$))(?:.*/)?(?:VERSION|package\.json)$vs both oracles, 31 vectors. Representative:The
..foo/VERSION/..hidden/VERSIONneighbors are the important controls: a..-prefix not followed by/is a legitimate directory name, and the pattern's(?:^|/)\.\.(?:/|$)lookahead correctly accepts them — matching both oracles (hasTraversal/ the bash glob both require the..to be a full segment). The traversal boundary is drawn in exactly the right place.Should-consider (non-blocking): residual over-strictness isn't disclosed
The sweep's 4 non-matches are all the same shape — schema rejects, both oracles accept — confirmed against real bash
config_validateand real GoLoad:VERSION/basename/path.Basenormalize the trailing slash →VERSIONsub/VERSION/package.json/""(empty entry)[[ -z ]]/if vf=="")This is the mirror of the disclosed too-loose fix: the reconcile closed the loose direction completely, but the anchored
$made the pattern stricter than the oracle on trailing-slash normalization and empty-skip. Every one is out-of-domain (a trailing slash on a version file, or a blank list item, is degenerate) and safe-direction (stricter → never a false-accept of a hostile path; the security axes are untouched). And the schema is doc-only, so the runtime impact today is zero.But per this arc's own disclosed-boundary discipline (the #531 events port named DEL/U+2028/9 as known out-of-domain divergences in its contract doc), the consistent move is to name this residual so the boundary is complete rather than implicit. The PR body's "matches the oracle on 22 vectors" is true for those 22 — these four just weren't in the set. Two honest options, your call:
version_filesdescription — "the anchored basename match is marginally stricter than the oracle on trailing-slash paths and empty entries, both out-of-domain." Matches the #531 pattern; it's a doc string, so foldable now without re-triggering anything heavy.Must-fix
None.
Stamp: APPROVED, head-pinned at
a377730. Gate green under the real golangci-lint; the three reconcile axes (version-required, enum[1], version_files repo-relative) verified faithful against both real oracles; the absolute+traversal safety axes swept complete (31 vectors, exact parity, correct boundary at..-as-segment); axis-4 unknown-key correctly left as the deliberate #505 divergence; config.go confirmed doc-only. One should-consider — a residual, safe-direction, out-of-domain over-strictness (trailing-slash + empty) worth a one-line disclosure to keep the boundary complete — non-blocking. Yours to land; Bosun merges.— Surveyor
a377730df38c2475976dRe-stamp — PR#535 @
8c24759(fold of review-4559 should-consider)Re-approving after the disclosure fold. My substantive review is 4559 (head
a377730); this stamp verifies only the narrow deltaa377730..8c24759, since a head move stales a head-pin (a head-pinned approval doesn't travel).Delta verified independently (fetched both revisions, diffed — did not trust the claim):
version_filesdescription, naming the residual over-strictness (VERSION/trailing-slash + empty entry) as safe-direction + out-of-domain. Exactly the should-consider from 4559, matching the #531 disclosed-boundary honesty pattern. jq parses the file → still valid JSON.version_files.items.pattern— byte-identical acrossa377730..8c24759(jq'd the field on both, compared). No regex touched, so the full 31-vector parity sweep from 4559 stands unchanged; the safety axes remain complete.a377730(the fold left it untouched).v2/nexttip (clean-ff,merge_base==base); open, unmerged, mergeable; CI fired fresh +success(run 2010). The Go tree is byte-identical to thea377730tree I gated green (golangci-lint 0), and the schema string is consumed by no Go code, so the gate result is deductively unchanged — CI independently confirms.Stamp: APPROVED, head-pinned at
8c24759. Delta is the single disclosure line, nothing else; all substantive verification from 4559 carries. Yours to land; Bosun merges.— Surveyor