Skip to content

feat(grade): incomplete becomes the floor with a per-grade roadmap [skip Codex]#32

Merged
foadshafighi merged 7 commits into
mainfrom
release/v0.8.0
Jun 23, 2026
Merged

feat(grade): incomplete becomes the floor with a per-grade roadmap [skip Codex]#32
foadshafighi merged 7 commits into
mainfrom
release/v0.8.0

Conversation

@foadshafighi

Copy link
Copy Markdown
Contributor

Summary

Makes incomplete the true floor of the conformance model and decomposes the roadmap per grade all the way to full. The model is now three grades (core, standard, full) above an incomplete floor.

  • No failure path on data completeness: a record with identity but zero source documents is valid, grades incomplete, and validate / build-index / from-sheet all exit 0. The below-floor none state is removed.
  • The roadmap reports each grade as satisfied, an outstanding delta, or gated (its own requirements met but blocked by a lower grade, naming the grade to reach).
  • The cutsheet becomes a graded core requirement instead of a schema requirement.

Behavior change for consumers

Records that previously failed validate / build-index / from-sheet because they were anchorless or missing required index keys now succeed and grade incomplete with a roadmap. Any tooling that treated a nonzero exit as "unacceptable" must treat incomplete as a valid, expected, below-core state. Nonzero exits remain only for schema-invalidity, malformed input, source-file integrity failures, or stored-vs-recomputed drift.

Changes

  • schema/ulc.schema.json, schema/taxonomy.schema.json - loosen three required sites and source_files.minItems (1 to 0); reframe the descriptions. All additive: no existing record becomes invalid.
  • tools/validator/internal/grade/grade.go - remove LevelNone; floor AchievedLevel at incomplete; add the cutsheet core rule; emit the per-grade roadmap with satisfied / gap / gated states.
  • tools/validator/internal/findings/findings.go - add conformance/grade-satisfied and conformance/grade-gated info codes.
  • tools/validator/internal/index/builder.go - always stamp conformance_level; shrink RequiredKeys; bump BuilderVersion to 0.5.0.
  • tools/validator/cmd/ulc/main.go - incomplete is a success in all three subcommands.
  • Examples re-stamped to ulc_version 0.8.0 / builder_version 0.5.0; grades unchanged (four standard, Vode core).
  • Docs and CHANGELOG reframed to three grades above an incomplete floor.

Test plan

  • go test ./... and go test -race ./... pass
  • go vet ./... clean; go build ./... clean
  • ulc validate examples/* exits 0 for all five records
  • New coverage: floor (TestZeroDocumentRecordGradesIncomplete, TestCLIFloorExitsZero), gated cascade (TestGatedGradesReported, TestGatedNamesIntermediateBlocker), per-grade roadmap, cutsheet core rule, builder always-stamp

Callouts for reviewers

  1. grade.go Report per-grade loop: the three-state emission (satisfied / gap / gated) and the blocker naming.
  2. builder.go always-stamp plus the shrunk RequiredKeys (identity only).
  3. Schema loosenings are additive; confirm no existing record is invalidated.
  4. Example grades are intentionally unchanged; only the two version fields move.

- Make incomplete the conformance floor: a record with identity but no
  source documents is valid, grades incomplete, and validate, build-index,
  and from-sheet all exit 0. Remove the below-floor none state.
- Emit a per-grade roadmap to full. Each grade reports as satisfied, an
  outstanding delta, or gated (its own requirements met but blocked by a
  lower grade, naming the grade to reach). Add the grade-satisfied and
  grade-gated info findings.
- Make the cutsheet a graded core requirement instead of a schema
  requirement, so an unattached cutsheet grades incomplete with a roadmap
  entry rather than failing schema validation.
- Loosen the schema additively: shrink the index and product_family
  required sets and allow an empty source_files array.
- Always stamp conformance_level; the index may be sparse for an incomplete
  record; bump the builder version to 0.5.0.
- Re-stamp the example records to 0.8.0; grades are unchanged.
- Reframe the docs and schema descriptions to three grades above an
  incomplete floor.
@foadshafighi foadshafighi requested a review from a team as a code owner June 23, 2026 13:10
@github-actions

Copy link
Copy Markdown

Codex Automated Code Review

Code Review Summary

PR: v0.8.0 conformance model: incomplete becomes the floor, per-grade roadmap, sparse incomplete index, docs/examples restamp.

P0 - Critical Issues (Must Fix)

None found.

P1 - High Priority Issues (Should Fix)

  • tools/validator/internal/sheet/convert.go:223 still hard-fails when records.cutsheet_file is empty, but this PR moves product_family.cutsheet from schema-required to a graded core roadmap item (CHANGELOG.md:39, tools/validator/internal/grade/grade.go:479). That means ulc from-sheet still cannot produce the new intended incomplete record for a missing cutsheet; it exits during conversion instead of emitting an incomplete record with /product_family/cutsheet in the roadmap. Fix by making cutsheet_file optional in the converter, omitting product_family.cutsheet and the synthesized datasheet source when absent, and letting grading/reporting handle the core gap.

  • tools/validator/internal/sheet/convert.go:195 still defaults generated workbook records to ulc_version: "0.7.0" even though this PR declares the current release as 0.8.0 (README.md:129) and restamps examples to 0.8.0. New ulc from-sheet output will silently declare the previous spec version unless the workbook overrides it. Fix the default to 0.8.0 and add/update a test covering omitted ulc_version.

  • docs/methodology.md:120 lists the authoritative core requirements but omits product_family.cutsheet, while the actual rubric now gates core on /product_family/cutsheet (tools/validator/internal/grade/grade.go:479) and the changelog explicitly says this is now a graded core item (CHANGELOG.md:39). Fix by adding a cutsheet row to the core requirements table.

P2 - Medium Priority Issues (Consider Fixing)

  • schema/ulc.schema.json:5 says JSON Schema required is used “only for record identity,” but the root required array still includes generated/index and structural containers such as index, configuration, and source_files (schema/ulc.schema.json:164). That wording is now misleading for implementers trying to understand what is schema-required versus graded. Fix the description to say required covers the minimal record envelope/identity/generated index containers, or further relax the schema if identity-only literally means only identity fields.

P3 - Low Priority Issues (Optional)

  • tools/validator/cmd/ulc/main.go:97 and tools/validator/cmd/ulc/main.go:202 still describe the conformance report as “guidance toward the next” level. The PR changes output to a per-grade roadmap through full, so update the help/comment text to avoid stale CLI documentation.

Positive Observations

  • Schema static checks passed: both JSON files parse, all cross-file $refs resolve, required arrays point at real properties, and enum values are unique strings.
  • schema/ulc.schema.json#/$defs/Index.required and index.RequiredKeys are in parity after the sparse-index change.
  • The builder version bump to 0.5.0 and example index restamps are consistent.

I could not run go test ./... because this review environment is fully read-only, including Go’s build cache and /tmp.


Automated review by OpenAI Codex

@greptile-apps

greptile-apps Bot commented Jun 23, 2026

Copy link
Copy Markdown

Greptile Summary

This PR makes incomplete the true floor of the ULC conformance model: a record with identity but no source documents is now valid, always graded, and never refused by the tooling. The model becomes three grades (core, standard, full) above an incomplete floor, with the per-grade roadmap decomposed all the way to full.

  • Schema (ulc.schema.json, taxonomy.schema.json): Three additive loosenings — source_files.minItems 1→0, cutsheet removed from ProductFamily.required, and three photometric projection keys removed from Index.required. All existing records remain valid; Index.required now exactly matches builder.RequiredKeys.
  • Grader (grade.go): LevelNone removed; LevelIncomplete becomes the zero-value floor; hasCutsheet added as a graded core rubric rule; Report emits satisfied/gap/gated states per grade with correct blocker tracking.
  • Builder/CLI/Converter: conformance_level always stamped; all three subcommands exit 0 for incomplete records; from-sheet writes instead of suppresses; empty cutsheet_file accepted gracefully.

Confidence Score: 4/5

Safe to merge on the primary repo side; a companion update to ulcspec.org is needed before the change reaches integrators broadly.

The grading logic, schema changes, builder, and CLI are internally consistent and well-tested. The only gap is cross-repo: ulcspec.org PIM guides instruct integrators to suppress thin records from export, the opposite of what the new model calls for.

ulcspec/ulcspec.org src/pages/docs/pim/salsify.astro and src/pages/docs/pim/sap.astro — both advise skipping exports for missing data, contradicting the new incomplete-is-valid contract.

Important Files Changed

Filename Overview
tools/validator/internal/grade/grade.go Core grading logic rewritten correctly: LevelNone removed, LevelIncomplete becomes the zero-value floor, AchievedLevel no longer requires photometric anchors, hasCutsheet added as a core rubric rule, per-grade roadmap emission with satisfied/gap/gated states is logically sound.
tools/validator/internal/index/builder.go Builder always stamps conformance_level; RequiredKeys shrunk to identity + always-generated keys; Index.required and RequiredKeys are now in sync. BuilderVersion bumped to 0.5.0.
tools/validator/cmd/ulc/main.go All three subcommands correctly treat incomplete as a non-failure exit; the old 'none' fallback removed; from-sheet prints a notice for incomplete records without suppressing the write.
tools/validator/internal/sheet/convert.go Empty cutsheet_file gracefully accepted; nil cutsheetRef is never dereferenced because assembleSourceFiles guards on cutsheetFilename. ulc_version default bumped to 0.8.0.
schema/ulc.schema.json Three additive loosenings: minItems 1 to 0 on source_files, cutsheet removed from ProductFamily.required, three photometric projection keys removed from Index.required. No existing record becomes invalid.
schema/taxonomy.schema.json ConformanceLevel description reframed to three grades above an incomplete floor; enum values unchanged; no breaking change.
tools/validator/internal/findings/findings.go Two new INFO codes added (grade-satisfied, grade-gated); existing codes and Finding struct unchanged, so current JSON consumers are unaffected.
tools/validator/internal/grade/grade_test.go Extensive new test coverage: zero-document floor, cutsheet as core rule, satisfied/gap/gated per-grade states, blocker-naming cases, and per-tier decomposition to full.
tools/validator/cmd/ulc/main_test.go New CLI integration tests pin the floor contract end-to-end: identity-only record exits 0, missing catalog_model is rejected, from-sheet writes incomplete records with the correct conformance_level.
CHANGELOG.md 0.8.0 entry is accurate and complete: behavior change for consumers clearly called out, all schema loosenings listed, grading, builder, and CLI changes covered.

Reviews (5): Last reviewed commit: "docs(schema): state that source_files is..." | Re-trigger Greptile

Comment thread schema/ulc.schema.json
- Make ulc from-sheet accept a workbook record with an empty cutsheet_file:
  it converts the record (omitting product_family.cutsheet and the
  synthesized datasheet source-file entry) so the record grades incomplete
  with a roadmap, instead of failing during conversion.
- Default the from-sheet ulc_version to 0.8.0.
- Add the cutsheet to the methodology core-requirements table.
- Reframe the PIM mapping guides: omitting the cutsheet grades incomplete,
  not schema-invalid, since the cutsheet is a graded core requirement.
- Clarify the schema and CLI text (the schema envelope versus identity; the
  per-grade roadmap to full).
- Add tests: cutsheet-optional conversion, the malformed-versus-incomplete
  identity boundary, and the from-sheet incomplete write path.
- When an incomplete record has gaps at more than one lower grade, a gated
  higher grade (its own requirements met) now names the highest of those
  grades as the one to reach. Reaching a lower grade alone does not unlock it
  while an intermediate grade still has a gap.
- Add a test for the core-plus-standard dual-gap case.
@github-actions

Copy link
Copy Markdown

Codex Automated Code Review

Code Review Summary

PR: v0.8.0 conformance-floor change: incomplete becomes the always-emitted floor, sparse incomplete indexes are allowed, cutsheet moves from schema-required to graded core, docs/examples/tooling are restamped.

P0 - Critical Issues (Must Fix)

None found.

P1 - High Priority Issues (Should Fix)

None found.

P2 - Medium Priority Issues (Consider Fixing)

  • CHANGELOG.md:29 overstates the CLI behavior for ulc build-index. It says all three subcommands exit nonzero on schema-invalidity, but runBuildIndex only parses JSON, builds the index, and checks index.MissingRequiredKeys; it does not run JSON Schema validation (tools/validator/cmd/ulc/main.go:256, tools/validator/cmd/ulc/main.go:261, tools/validator/internal/index/builder.go:48). A record missing schema-required fields like ulc_version, record_status, product_family.family_id, product_family.manufacturer.display_name, configuration, or source_files can still build successfully if the index-required projections exist. Fix by either running schema validation in build-index before writing/checking, or narrowing the changelog claim to validate/from-sheet and index-required-key failures.

  • The identity/incomplete boundary is documented imprecisely and names generated index fields as if they were schema fields. schema/taxonomy.schema.json:1096, docs/methodology.md:101, and docs/how-it-works.md:63 say a record missing identity manufacturer_slug and catalog_model is schema-invalid and reported through MissingRequiredKeys. In the actual schema, manufacturer_slug is generated at index.manufacturer_slug; the authored schema fields are product_family.manufacturer.slug, product_family.manufacturer.display_name, product_family.family_id, and product_family.catalog_model (schema/ulc.schema.json:415, schema/ulc.schema.json:425, schema/ulc.schema.json:554). Fix the prose to distinguish schema validation from generated-index required-key checks.

  • The PIM emitter examples still hard-code ulc_version as 0.7.0, while this PR updates the current release and converter default to 0.8.0. A manufacturer copying these changed mapping guides would emit old-version records. Update the examples or use a named current-spec constant at mappings/pim/akeneo.md:141, mappings/pim/salsify.md:145, mappings/pim/custom-pim.md:189, and mappings/pim/sap.md:158.

  • tools/validator/internal/sheet/convert.go:192 says the default ulc_version is “per DESIGN.md” and now sets 0.8.0 at tools/validator/internal/sheet/convert.go:195, but the referenced design doc still says ulc_version(=0.7.0 default) at tools/validator/internal/sheet/DESIGN.md:73. Update the design doc or remove the “per DESIGN.md” claim to avoid drift in the converter contract.

P3 - Low Priority Issues (Optional)

None found.

Positive Observations

  • JSON syntax checks passed for both schema files.
  • tools/schema-drift-guard.py passed: all 219 cross-file $ref pointers resolve.
  • Static checks found no required entries pointing at missing local properties, no duplicate/non-string enum values, and no enum definitions leaking into schema/ulc.schema.json.
  • The generated-index examples were restamped consistently to builder 0.5.0.

Could not run go test ./... because the environment is read-only, including /tmp, so Go could not create its build cache.


Automated review by OpenAI Codex

Bump the PIM emitter examples and the from-sheet design note to declare
ulc_version 0.8.0, matching the converter default and every shipped
example so a copied integration emits a current-spec record.

Clarify the identity boundary: a record missing identity is malformed
rather than incomplete, naming the schema-required authored fields
(product_family.manufacturer.slug, product_family.family_id,
product_family.catalog_model) and noting it fails JSON Schema validation
and the builder cannot derive its required index keys
(MissingRequiredKeys), a rejection distinct from the incomplete grade.

Correct the changelog exit-code note: build-index runs no JSON Schema
validation and gates on the builder's required index keys, while
validate and from-sheet additionally fail on schema invalidity.
@github-actions

Copy link
Copy Markdown

Codex Automated Code Review

Code Review Summary

PR: v0.8.0 release update making incomplete the floor, loosening cutsheet/source-file/index requirements, and updating validator grading/reporting.

P0 - Critical Issues (Must Fix)

None found.

P1 - High Priority Issues (Should Fix)

  • schema/taxonomy.schema.json:1096 says “Required fields in the JSON Schema are limited to record identity,” but schema/ulc.schema.json still requires the record envelope (ulc_version, record_id, record_status, index, configuration, source_files) and configuration.photometric_scenario_id, not just identity. The same description lists schema-required identity as manufacturer.slug, family_id, and catalog_model, but product_family.manufacturer.display_name is also required by the schema. Fix by describing the required surface as “record envelope plus product-family identity,” and include product_family.manufacturer.display_name in the identity list.

  • docs/methodology.md:101 and docs/how-it-works.md:63 repeat the same incomplete identity boundary but omit product_family.manufacturer.display_name, even though it is schema-required. They also imply the builder rejection covers the whole listed identity set, while MissingRequiredKeys only catches manufacturer_slug and catalog_model; JSON Schema catches family_id and manufacturer.display_name. Fix the prose to distinguish schema validation from builder required-index-key checks.

P2 - Medium Priority Issues (Consider Fixing)

None found.

P3 - Low Priority Issues (Optional)

None found.

Positive Observations

  • The changed schema files are valid JSON, and static $ref resolution across schema/ulc.schema.json and schema/taxonomy.schema.json passed.
  • Index.required and tools/validator/internal/index/builder.go required-key parity appear aligned after the sparse-index change.
  • The examples only restamp ulc_version and builder_version; their generated index blocks remain marked with x-ulc-generated.
  • I could not run go test ./tools/validator/... because this environment is read-only, including /tmp, so Go could not create its build cache.

Automated review by OpenAI Codex

The ConformanceLevel description previously claimed JSON Schema required
fields are limited to record identity; the schema also requires the
record envelope (version, identifiers, status, the generated index
container, configuration, and source_files). Reword it to say required
covers the record envelope plus product-family identity, matching the
record schema description.

Complete the identity set in the conformance description and the
methodology and how-it-works incomplete sections by adding
product_family.manufacturer.display_name, which is schema-required.
Scope the builder's MissingRequiredKeys check to the two index keys it
projects from identity (manufacturer_slug, catalog_model) and attribute
the rest of the identity set to JSON Schema validation, so the two
rejection mechanisms are no longer conflated.
@github-actions

Copy link
Copy Markdown

Codex Automated Code Review

Code Review Summary

PR: v0.8.0 conformance-floor change: incomplete becomes the always-stamped floor, cutsheet/source-file requirements loosen, roadmap output becomes per-grade, examples re-stamp to builder 0.5.0.

P0 - Critical Issues (Must Fix)

None found.

P1 - High Priority Issues (Should Fix)

None found.

P2 - Medium Priority Issues (Consider Fixing)

  • tools/validator/README.md:17 still documents ulc validate as reporting roadmap guidance “to reach the next level.” This PR changes the output contract to a per-grade roadmap through full, with new conformance/grade-satisfied and conformance/grade-gated INFO codes. Update this README so CLI consumers do not implement against the old one-step roadmap model.

P3 - Low Priority Issues (Optional)

  • examples/README.md:17 still uses “Conformance levels” while the PR otherwise reframes the model as three grades above an incomplete floor. Rename to “Conformance Grades and Roadmap” for terminology consistency.

Positive Observations

  • Schema syntax and example JSON parse cleanly.
  • tools/schema-drift-guard.py passes: all 219 schema $ref pointers resolve.
  • No enum definitions leaked into schema/ulc.schema.json.
  • Index.required in schema/ulc.schema.json:247 matches index.RequiredKeys in tools/validator/internal/index/builder.go:48.
  • The cutsheet loosen/regrade behavior is covered at schema, builder, converter, CLI, and example levels. I could not run Go tests because the review environment is read-only for /tmp and the Go build cache.

Automated review by OpenAI Codex

…ADMEs [skip Codex]

Replace the one-step roadmap wording in the validator README (guidance
to reach the next level) with the per-grade roadmap through full,
including the conformance/grade-satisfied and conformance/grade-gated
INFO codes, so CLI consumers implement against the current output
contract.

Rename the examples README section from Conformance levels to
Conformance grades for terminology consistency with the three-grades
above-an-incomplete-floor model.
@github-actions

Copy link
Copy Markdown

Codex Automated Code Review

Code Review Summary

PR: v0.8.0 conformance model: incomplete as floor, sparse indexes, optional cutsheet in schema, per-grade roadmap.

P0 - Critical Issues (Must Fix)

None found.

P1 - High Priority Issues (Should Fix)

  • tools/validator/internal/grade/grade.go:479 / schema/ulc.schema.json:146 / mappings/pim/README.md:77
    The schema/docs say source files are part of the core/complete evidence model and PIM docs require the cutsheet in both product_family.cutsheet and source_files[], but the grader only checks product_family.cutsheet. A record can leave source_files empty, populate only product_family.cutsheet, satisfy every other core row, and grade core, contradicting the “complete record carries both places” guidance.
    Fix by adding a core rubric predicate requiring a source_files[] datasheet_pdf entry, ideally matching the product_family.cutsheet filename/hash, or relax the docs/schema description if source_files[] is intentionally not graded.

P2 - Medium Priority Issues (Consider Fixing)

  • tools/validator/internal/sheet/DESIGN.md:64
    The workbook “smallest valid” section is stale under the new floor model. It still says a valid workbook needs a source_files IES row, that flux/input-power/primary-category make a record indexable, and lists cutsheet_file as part of the schema structure. The PR now allows an identity-only/zero-document incomplete record and an empty cutsheet_file. It also says “the schema then wants an attestation_ref,” but the schema only describes that as expected, not required.
    Fix by splitting this into “smallest schema-valid/incomplete workbook” versus “smallest core/measured-photometry workbook,” and mark cutsheet_file as core-graded rather than schema-required.

P3 - Low Priority Issues (Optional)

  • tools/validator/cmd/ulc/main.go:451
    from-sheet prints see roadmap for incomplete records, but on successful writes it only prints the one-line summary and does not render the roadmap report.
    Fix by either printing the roadmap for incomplete outputs or changing the message to tell users to run ulc validate on the written record.

Positive Observations

  • JSON syntax is valid for both schema files.
  • tools/schema-drift-guard.py passes: all 219 $ref pointers resolve.
  • No enum definitions leaked into schema/ulc.schema.json.
  • Changed examples appear to be generated index/version restamps only.
  • No research/ files are present in the PR.

Validation note: I could not run go test ./tools/validator/... because this sandbox is fully read-only, including /home/runner/.cache and /tmp, so Go could not create a build cache.


Automated review by OpenAI Codex

…t pointer

The source_files description claimed source files are graded core items.
The grader gates the core cutsheet on the family-level pointer
product_family.cutsheet and does not grade the source_files array at all.
Reword the description so it states the array is neither schema-required
nor graded, and that product_family.cutsheet is the graded core cutsheet
item while source_files carries integrity-tracked provenance.
@foadshafighi foadshafighi changed the title feat(grade): incomplete becomes the floor with a per-grade roadmap feat(grade): incomplete becomes the floor with a per-grade roadmap [skip Codex] Jun 23, 2026
@foadshafighi foadshafighi merged commit f12624d into main Jun 23, 2026
4 checks passed
@foadshafighi foadshafighi deleted the release/v0.8.0 branch June 23, 2026 16:52
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant