ULC v1.3.0
ulc CLI v1.3.0
Download a single-file binary for your platform below. See CHANGELOG.md
at the tag for what landed in this release.
An integrity release. Default ulc validate output now includes hash-verification findings for the family cutsheet (product_family.cutsheet) and the emergency photometry reference (emergency.photometry_reference): on records whose files are absent locally this adds INFO findings, and a locally present file whose hash does not match its declared value is an ERROR that fails validation, where previously only source_files entries were checked. ulc validate --json now escapes <, >, and & as \u003c, \u003e, and \u0026, so its bytes change for records containing those characters in echoed strings, aligning it with ulc scope; JSON semantics after parsing are identical. A new opt-in flag, ulc validate --verify-evidence, byte-verifies attestation evidence documents. ulc from-sheet now bounds workbook archives (entry count, per-part and total inflated size, compression ratio) and rejects a file exceeding one of them with an error naming the limit, while the CSV bundle path is unchanged. from-sheet now resolves symbolic links before hashing a referenced file and rejects any reference that resolves outside the assets root. Text output now renders control characters in record-supplied text as visible escapes, so a crafted filename cannot forge report lines or alter terminal state; JSON output, which already escapes the control characters below U+0020, is unchanged. Schema validation now asserts the schema's declared string formats: the eight format: date fields must be ISO 8601 dates (YYYY-MM-DD) and the two format: uri fields must be absolute URIs. These declarations were previously annotations that no check enforced, so a malformed date (for example a spreadsheet date serial where an ISO date belongs) or a relative URL validated cleanly; it now fails validation, in ulc validate and in ulc from-sheet, which validates every record before writing it. There is no schema or taxonomy change and no computed value change: grades and completeness findings are byte-identical, every golden file is byte-identical, and no example record changed a byte.
For consumers
If you pin exit codes, hash verification adds one new case: a record whose cutsheet or emergency photometry file sits beside it locally and does not match its declared hash now exits 1 instead of 0. That is the intended correction, and on the eight shipped examples nothing moves, because no source file is distributed alongside them.
If you parse --json, expect one additional finding object per record whose cutsheet is not reachable locally, and summary.infos one higher. The escaping change moves bytes only for records that carry <, >, or & in an echoed string such as a filename or a record id; no shipped example does, so all eight are byte-identical apart from the added finding.
A source-file/not-found-locally INFO at a record's source_files entry is expected and documented when a manufacturer publishes an evidence reference while withholding the document itself. It is not a defect, the record still validates, and the theme still reads documented. See "Withholding the evidence document" in docs/authoring-patterns.md.
--verify-evidence is opt-in and absent from default runs. Its real coverage is evidence documents that a record references but does not also list among its source files; where a record follows the shipped guidance and dual-writes the document into source_files, the default walk already covers those bytes and the flag adds a second pointer rather than a second check.
Schema validation now enforces declared string formats, which adds one exit-code class: a record carrying a malformed date (a spreadsheet date serial such as 46082, a 3/1/26, a 2026-13-45) in any of the eight format: date fields, or a relative or otherwise non-absolute URL in either of the two format: uri fields, exits 1 where it previously exited 0. Well-formed records do not move: all eight shipped examples are byte-identical under the assertion. The check catches malformed values, not missing ones; every date field is optional, and an absent date behaves exactly as before.
Validator
- File-reference hash verification now runs at every site the schema defines a reference:
source_files[].reference,product_family.cutsheet, andemergency.photometry_referenceon every run, plusattestations[].source_document_refandproduct_family.shared_attestations[].source_document_refunder--verify-evidence. The sites are named by a declared registry, and a schema-derived test fails the build if a future schema mounts a reference the registry does not name, so a new reference field cannot ship unverified by oversight. Findings reuse the existingsource-file/*codes with the site's own JSON Pointer as the discriminator; no code or message template changed. - New flag
ulc validate --verify-evidence. A locally absent evidence document stays INFO, so a record that withholds the document still validates; a document whose SHA-256 does not match is an ERROR and fails validation. Absent from default runs, so default output and the goldens are untouched by it. ulc from-sheetbounds.xlsxarchives: 1024 entries, 16 MiB inflated per part, 32 MiB inflated across every part the reader opens, and a 100:1 compression ratio above a 1 MiB floor. A workbook exceeding one is rejected with an error naming the limit. The byte limits are derived from the measured amplification of worksheet XML into the workbook model. Parts the reader never opens are not charged, and the CSV bundle path parses no archive and is unchanged.ulc from-sheetresolves symbolic links before hashing and rejects a reference that resolves outside the assets root. A reference to a file that is not present behaves as before: an error unless--allow-missing-filesis set, which stamps the zero sentinel and marks the record a draft.ulc validate --jsonescapes<,>, and&, matchingulc scope.ulc build-index --stdoutstill does not, because its output must preserve the record's own byte shape.- Text output sanitizes record-supplied strings at the render boundary, covering control characters (C0, DEL, and C1) and the bidirectional formatting characters that reorder rendered text without any control character present, so a filename cannot display as a different name from the one whose bytes were hashed. Escapes are
\xNNbelow U+0100 and\uNNNNabove it. Message text is unchanged; consumers needing exact bytes use--json. ulc from-sheetassembles a shared string from its formatting runs in one pass. The previous accumulation reallocated per run, so a workbook inside every size limit could still hold the reader at full CPU for minutes; nothing bounds how many runs a single string may carry.- Schema validation asserts declared formats. The eight
format: datefields (record_status_as_of,revision_dateon file references,introduced_aton product families,original_issue_dateandexpiration_dateon sustainability declarations, andissued_date,valid_until, andlist_dateon attestations) must parse as ISO 8601YYYY-MM-DDdates, and the twoformat: urifields (urlon a file reference,end_of_life_program_url) must be absolute URIs. A malformed value fails validation with aschema/violationfinding at its own JSON Pointer, so a record can no longer pass while--expirysilently skips an unparseable date, andulc from-sheetrefuses to write a record whose workbook carried one.
Docs
README.md: the source-inputs section names the primary source documents and points at the fullSourceFileTypevocabulary, which covers twelve document types rather than the three the section used to list.docs/how-it-works.md: Path 2 describes emitting from the systems that hold the data rather than from a PIM alone, and the CLI bullet mentions--verify-evidence.docs/authoring-patterns.md: new guidance on withholding an evidence document while still documenting a theme, and a newsource_filesprimitive subsection covering what the array asserts, including document-grade commercial references.docs/methodology.md: the trust-boundary section states what a withheld evidence document does and does not change.mappings/README.mdandmappings/pim/README.md: product attributes and compliance evidence commonly live in different systems; a new "Where the evidence lives" section describes joining them at emit time.tools/validator/README.md: records the widened verification, the new flag, the archive limits, the assets-root containment rule, and the escaping alignment.templates/workbook/README.md: the notes for.xlsxauthors cover formatting date and identifier columns as Text before entry, why a spreadsheet silently re-types an ISO date into a serial number and an identifier such as1200.40into1200.4, and that schema validation rejects a date that is notYYYY-MM-DD.mappings/pim/salsify.mdandmappings/pim/akeneo.md: the asset mapping takes the date portion ofupdated_atforreference.revision_date, which is one of the eightformat: datefields.ROADMAP.md: the active-version heading now names the current line, the minors paragraph records that this release is the first to move default output, and the attestation-document byte-verification and JSON escaping-consistency items move out of deferred work because this release ships them.