Skip to content
30 changes: 30 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,36 @@ Releases are automated. To ship a release:

For emergency manual releases (bypassing the PR flow), trigger the `Release on merge` workflow manually via `workflow_dispatch`, providing the version input.

## 0.8.0 (2026-06-23)

`incomplete` becomes the true floor of the conformance model. The tooling never fails a record on data completeness: a record with identity but zero source documents is valid, grades `incomplete`, and carries a roadmap to core. The roadmap now decomposes per grade all the way to full, showing the grades a record already satisfies and, for each grade it has not yet reached, only that grade's own remaining fields. The model is now framed as three grades (`core`, `standard`, `full`) above an `incomplete` floor.

### Behavior change (important for consumers)

- Records that previously failed `ulc validate`, `ulc build-index`, or `ulc from-sheet` because they were anchorless or missing required index keys now SUCCEED (exit 0) and grade `incomplete` with a roadmap. Any external tooling or CI that treated a nonzero exit as "this record is unacceptable" must now treat `incomplete` as a valid, expected, below-core state. The subcommands still exit nonzero on malformed input, missing record identity, source-file integrity failures (hash mismatch, unreadable files), or a stored-vs-recomputed conformance drift; `ulc validate` and `ulc from-sheet` additionally exit nonzero on JSON Schema invalidity, while `ulc build-index` runs no schema validation and instead gates on the builder's required index keys. No data-completeness condition produces a nonzero exit.
- `index.conformance_level` is now always present, including `incomplete`. The internal below-floor sentinel (`none`) is removed: the grader never returns it and nothing renders it.

### Schema (additive, pre-1.0)

- Three `required` sites are loosened so an identity-only record is representable: the index drops the three photometric projections (`primary_category`, `nominal_total_lumens`, `nominal_input_power_w`) from its required keys, `product_family` drops `cutsheet`, and `source_files` may be empty (`minItems` 1 to 0). All three are loosenings, so no existing record becomes invalid.
- The `ConformanceLevel` description is reframed: `incomplete` is the floor (a record that has not yet met core), never a published grade, always traveling with a roadmap. The enum values are unchanged.

### Grading

- The cutsheet moves from a schema requirement to a graded core item, so an unattached cutsheet now grades `incomplete` with a roadmap entry naming `/product_family/cutsheet` instead of failing schema validation. This is the only rubric membership change; which fields gate which grade is otherwise unchanged. Example grades do not change.
- `AchievedLevel` floors at `incomplete` (the zero value) and never returns a below-floor sentinel.
- The roadmap is emitted per grade through full. Each grade reports one of three states: satisfied (a new `conformance/grade-satisfied` info finding), an outstanding delta (the existing `conformance/gap` roadmap), or gated (a new `conformance/grade-gated` info finding when a grade's own requirements are met but a lower grade is not, naming the grade to reach to unlock it). The structured `Finding` fields are unchanged, so existing JSON consumers keep parsing; they see two new info codes. A consumer keying on `conformance/grade-gated` must treat it as NOT achieved; the achieved grade is `index.conformance_level`.

### Builder and CLI

- The builder always stamps `conformance_level`. The index may be sparse for an `incomplete` record (photometric projections are omitted when their data is absent). `BuilderVersion` is `0.5.0`, so every stored index re-stamps on the next `ulc build-index`.
- `ulc from-sheet` accepts a workbook record with an empty `cutsheet_file`: it converts the record (omitting `product_family.cutsheet` and the synthesized datasheet source-file entry) instead of failing, so a cutsheet-less record grades `incomplete` with a roadmap. It writes an `incomplete` converted record to its output directory with a console notice that it is below core; it no longer skips such a record.

### Examples and docs

- The five reference records re-stamp to `ulc_version` `0.8.0` and `builder_version` `0.5.0`. Grades are unchanged (erco, selux, and both Lumenpulse records at `standard`; Vode at `core`); their roadmaps now render the full per-grade decomposition.
- `methodology.md`, `how-it-works.md`, `authoring-patterns.md`, the README, and the examples README are reframed from "four levels" to "three grades above an incomplete floor."

## 0.7.0 (2026-06-20)

The conformance rubric is redesigned from a thin 16-field check into an exhaustive, document-aware rule table, and the conformance ladder gains an `incomplete` tier below `core`. Stored indices recompute, so several records change level.
Expand Down
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ A ULC record is a single JSON document that conforms to the ULC schema. It carri

ULC does not embed source files. It identifies them. A consumer who obtains a source file through any channel can verify it matches the ULC record by comparing hashes.

Every record also carries a computed **conformance level** (`incomplete`, `core`, `standard`, or `full`), graded by the reference builder from the fields the record populates and stamped into the generated index, never declared by the author. The grading rubric applies its requirements conditionally through a layer of applicability predicates, so a fixture is only ever asked for the data its form actually has: a pure color-mixing fixture is not graded on CRI, and an indoor downlight is not graded on a BUG rating. The tiers are not arbitrary: they mirror the way a construction specification escalates what it asks of a manufacturer, from the mandatory product data and safety listing every fixture must provide, through the selection-grade performance specifications used to compare products, to the independently-certified test reports demanded of the most rigorous fixtures. The four tiers, the per-tier requirement tables, the predicate layer, and the rationale behind the tier cut points are documented in [the conformance rubric](docs/methodology.md#the-conformance-rubric).
Every record also carries a computed **conformance grade** (`core`, `standard`, or `full`, above an `incomplete` floor), graded by the reference builder from the fields the record populates and stamped into the generated index, never declared by the author. The grading rubric applies its requirements conditionally through a layer of applicability predicates, so a fixture is only ever asked for the data its form actually has: a pure color-mixing fixture is not graded on CRI, and an indoor downlight is not graded on a BUG rating. The tiers are not arbitrary: they mirror the way a construction specification escalates what it asks of a manufacturer, from the mandatory product data and safety listing every fixture must provide, through the selection-grade performance specifications used to compare products, to the independently-certified test reports demanded of the most rigorous fixtures. The three grades above an incomplete floor, the per-grade requirement tables, the predicate layer, and the rationale behind the grade cut points are documented in [the conformance rubric](docs/methodology.md#the-conformance-rubric).

## Source inputs

Expand Down Expand Up @@ -126,7 +126,7 @@ To check the current state of manufacturer adoption (which manufacturers have pu

## Project status

The current release is `0.7.0`. Conformance is computed rather than declared: the reference builder grades each record against the four-tier ladder (`incomplete` < `core` < `standard` < `full`) and stamps the result into the generated index. The toolchain ships the split schema and taxonomy, the drift-guard tooling, the reference command-line validator and compiler (`ulc`) with schema validation, builder parity, and source-file hash verification, the deterministic `from-sheet` workbook-to-record converter, the fill-in workbook template under `templates/workbook/`, and the PIM platform mapping guides under `mappings/pim/`. See `CHANGELOG.md` for the full release history.
The current release is `0.8.0`. Conformance is computed rather than declared: the reference builder grades each record against three grades (`core`, `standard`, `full`) above an `incomplete` floor and stamps the result into the generated index. The toolchain ships the split schema and taxonomy, the drift-guard tooling, the reference command-line validator and compiler (`ulc`) with schema validation, builder parity, and source-file hash verification, the deterministic `from-sheet` workbook-to-record converter, the fill-in workbook template under `templates/workbook/`, and the PIM platform mapping guides under `mappings/pim/`. See `CHANGELOG.md` for the full release history.

The specification will continue to evolve based on real-world use, industry feedback, and alignment with adjacent standards. See `CHANGELOG.md` for release notes.

Expand Down
2 changes: 1 addition & 1 deletion docs/authoring-patterns.md
Original file line number Diff line number Diff line change
Expand Up @@ -210,7 +210,7 @@ A single manufacturer may use different patterns for different product families.

The reference validator uses the patterns and primitives above as follows:

- **Conformance level is computed, not declared.** The builder grades each record against the rubric and writes the level it achieves into the generated index as `index.conformance_level`, so the level a record carries is the level its data actually reaches (a hand-edited value fails the build-parity check, like any other index field). The ladder has four tiers: `incomplete` is a real photometric record that has not yet met every core requirement (it indexes and gets a roadmap to core); `core` is a complete, legally-sellable luminaire with full identity, headline numbers, one-line colorimetry, and a market safety listing; `standard` adds the fuller specification a typical LM-79 report produces plus the white-light, directional, outdoor-site, and wet-location conditionals that apply; `full` adds exhaustive accredited characterization (zonal lumens, operating point, uncertainty, corrections, instrumentation depth, a method-backed lumen-maintenance projection, and TM-30 detail for white-light), mostly from a test report. Compliance beyond the core safety listing is tracked, not gated, and the safety gate checks for a self-asserted claim, not third-party verification. `ulc validate` reports the achieved level and the specific fields that would raise it, each naming its source document and standard. The level is never a pass/fail gate.
- **Conformance level is computed, not declared.** The builder grades each record against the rubric and writes the level it achieves into the generated index as `index.conformance_level`, so the level a record carries is the level its data actually reaches (a hand-edited value fails the build-parity check, like any other index field). There are three grades above an `incomplete` floor: `incomplete` is the floor, a record that has not yet met a core requirement (authoring can begin from identity alone, where the grader returns incomplete plus a roadmap to core, and a record publishes at core and above); `core` is a complete, legally-sellable luminaire with full identity, headline numbers, one-line colorimetry, and a market safety listing; `standard` adds the fuller specification a typical LM-79 report produces plus the white-light, directional, outdoor-site, and wet-location conditionals that apply; `full` adds exhaustive accredited characterization (zonal lumens, operating point, uncertainty, corrections, instrumentation depth, a method-backed lumen-maintenance projection, and TM-30 detail for white-light), mostly from a test report. Compliance beyond the core safety listing is tracked, not gated, and the safety gate checks for a self-asserted claim, not third-party verification. `ulc validate` reports the achieved grade and a per-grade roadmap to full: the grades already satisfied and, for each grade not yet reached, only that grade's own remaining fields, each naming its source document and standard. The level is never a pass/fail gate.

The following integrity checks are defined by the specification but are **not yet implemented in the reference validator**. The v1 validator runs schema validation, builder parity, source-file hashes, and conformance reporting only; these are planned for a post-pilot release:

Expand Down
6 changes: 3 additions & 3 deletions docs/how-it-works.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,14 +58,14 @@ Both tiers read the same ULC record. The difference is depth of lighting-domain

The reference command-line validator (`ulc`, in `tools/validator/`) checks a record end to end in one command: structure against the JSON Schema, builder parity (that the generated index matches what the builder would produce from the deep blocks), and source-file hashes.

The conformance level is computed and stamped into the generated index as `index.conformance_level` by the builder (`ulc build-index`), never hand-declared. As part of its end-to-end pass, `ulc validate` recomputes the level and checks it against the stored value (builder parity), then reports it. Grading is a cumulative gate: a record is the highest tier all of whose hard requirements it meets. The four levels are:
The conformance level is computed and stamped into the generated index as `index.conformance_level` by the builder (`ulc build-index`), never hand-declared. As part of its end-to-end pass, `ulc validate` recomputes the level and checks it against the stored value (builder parity), then reports it. Grading is a cumulative gate: a record is the highest grade all of whose hard requirements it meets. There are three grades above an `incomplete` floor:

- **incomplete**: a real photometric record (it carries the flux, input-power, and primary-category anchors) that has not yet met every core requirement. Those anchors make the record gradeable at this tier and earn it a roadmap naming the core fields it is missing; a fully valid index also requires the identity core-fields (`manufacturer_slug` and `catalog_model`), which the builder reports through `MissingRequiredKeys` when they are absent.
- **incomplete**: the floor, a record that has not yet met a core requirement. It grades incomplete, indexes, and carries a roadmap to core; a record missing identity (the schema-required `product_family.manufacturer.slug`, `product_family.manufacturer.display_name`, `product_family.family_id`, and `product_family.catalog_model`) is malformed rather than incomplete: `ulc validate` rejects it against the JSON Schema, and the builder additionally cannot derive the required index keys it projects from identity (`manufacturer_slug`, `catalog_model`), reported through `MissingRequiredKeys`. Either rejection is distinct from incomplete.
- **core**: a complete, identifiable, legally-sellable luminaire: full identity, headline photometric and electrical numbers, one-line colorimetry, and a market safety listing.
- **standard**: core plus the fuller specification a typical LM-79 report produces (full photometric geometry, materials, an LM-79 attestation, a lumen-maintenance framework, and the white-light, directional, outdoor-site, and wet-location conditionals that apply).
- **full**: standard plus exhaustive accredited characterization (zonal lumens, an operating point, measurement uncertainty, corrections, instrumentation depth, a method-backed lumen-maintenance projection, and TM-30 detail for white-light products), mostly from an accredited test report.

The safety-listing core gate checks for the presence of a self-asserted listing claim, not third-party verification of it; a conformance level is a data-completeness grade, never a safety certification. Compliance beyond the core safety listing is tracked, not gated. `ulc validate` reports the level a record achieves and the specific fields that would raise it to the next level, each naming its source document and governing standard. The level is verified from the data, not asserted by the author, and it is never a pass/fail gate. Because the index (including the conformance level) is a deterministic function of the deep blocks, a hand-edited value fails the builder-parity check like any other index field.
The safety-listing core gate checks for the presence of a self-asserted listing claim, not third-party verification of it; a conformance level is a data-completeness grade, never a safety certification. Compliance beyond the core safety listing is tracked, not gated. `ulc validate` reports the grade a record achieves and a per-grade roadmap to full: the grades it already satisfies and, for each grade not yet reached, only that grade's own remaining fields, each naming its source document and governing standard. The level is verified from the data, not asserted by the author, and it is never a pass/fail gate. Because the index (including the conformance level) is a deterministic function of the deep blocks, a hand-edited value fails the builder-parity check like any other index field.

## How to try it today

Expand Down
Loading