Skip to content

Commit bb328d0

Browse files
authored
feat(openlabel-v2): migrate PAS 1883 to ISO 3450x with improved SHACL messages (#70)
* chore(linkml): pin submodule to feat/envited-x-pipeline Pin linkml submodule to the consolidated feature branch containing all ENVITED-X pipeline enhancements: - feat(generators): deterministic RDFC-1.0 output with WL hashing - feat(generators): --normalize-prefixes flag - feat(generators): --default-language flag - feat(gen-shacl): --message-template for sh:message - feat(gen-shacl): sh:sparql from rules + exclusive-value patterns - fix(owlgen): covering axiom edge cases - fix(shaclgen): minCount/maxCount 0 for zero cardinality - fix(shaclgen): sh:pattern in any_of - fix(generators): normalize trailing newline in serialization - fix(generators): bundle rdf_canonicalize in linkml package Pin: 3d3a52a3 * feat(openlabel-v2): migrate PAS 1883:2020 to ISO 34503/34504 Migrate the openlabel-v2 LinkML schema from PAS 1883:2020 to the published ISO 34503:2023 and ISO 34504:2023 standards. Schema updates: - Rename Road/Lane enums to match ISO 34503/34504 terminology - Add EdgeNone mutual exclusion SPARQL rule - Add correct-spelling alias for IntersectionGradeSeperated - Add {comments} to SHACL --message-template for richer messages - Strip trailing blank lines from generator output in Makefile Artifacts: - Regenerate OWL, SHACL, and JSON-LD context with new compiler Test improvements: - Add comprehensive valid instances for openlabel-v2 - Add cross-domain test instance - Add fail12_edge_none_mutual_exclusion invalid test - Update .expected files for improved SHACL messages Documentation: - Add linkml-modelling-guidelines.md for future reference - Update agent instruction files * fix(build): use POSIX-compatible sed for trailing newline removal The inline sed pattern `sed '${/^$/d}'` can fail on macOS BSD sed. Split into multiple -e expressions which is guaranteed POSIX-compliant and works on both GNU sed (Linux) and BSD sed (macOS). --------- Signed-off-by: Carlo van Driesten <carlo.van-driesten@bmw.de>
1 parent 4939d7e commit bb328d0

28 files changed

Lines changed: 5155 additions & 3350 deletions

.github/copilot-instructions.md

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,48 @@ Read these BEFORE making changes:
123123
-**Don't silently return `None`** - Raise specific exceptions
124124
-**Don't use `print()` for logging** - Use `logger` from `core/logging.py`
125125
-**Don't duplicate path normalization** - Use `normalize_paths_to_list()` or `normalize_path_for_display()`
126+
-**Don't commit with `--no-verify` without verifying** - If you must skip hooks (e.g., Windows CRLF loop), manually ensure generated artifacts have LF line endings and no trailing empty lines in `.context.jsonld`
127+
-**Don't assume generator output is CI-ready on Windows** - LinkML generators produce CRLF on Windows; CI runs on Linux with LF
128+
129+
## Generated Artifacts & Line Endings (Windows/Linux CI)
130+
131+
CI verifies that committed artifacts exactly match `make generate` output on Linux.
132+
On Windows, LinkML generators produce CRLF and trailing newlines that differ.
133+
134+
**The CRLF commit loop on Windows:**
135+
1. `generate-linkml` hook produces CRLF → reports "files modified" → commit fails
136+
2. `mixed-line-ending` hook fixes to LF → reports "files modified" → commit fails
137+
3. Retrying repeats the cycle endlessly
138+
139+
**Correct workflow on Windows:**
140+
141+
```bash
142+
# Generate, then normalize before staging
143+
make generate DOMAIN=openlabel-v2
144+
145+
# Fix CRLF → LF in generated files
146+
python -c "
147+
import glob
148+
for f in glob.glob('artifacts/openlabel-v2/*'):
149+
with open(f, 'rb') as fh: c = fh.read()
150+
c = c.replace(b'\r\n', b'\n')
151+
with open(f, 'wb') as fh: fh.write(c)
152+
"
153+
154+
# Remove trailing empty line from .context.jsonld (pretty-format-json removes it)
155+
python -c "
156+
f = 'artifacts/openlabel-v2/openlabel-v2.context.jsonld'
157+
with open(f, 'rb') as fh: c = fh.read()
158+
c = c.rstrip() + b'\n'
159+
with open(f, 'wb') as fh: fh.write(c)
160+
"
161+
162+
# Now commit normally (hooks should pass)
163+
git add artifacts/ && git commit -s -S -m "feat: ..."
164+
```
165+
166+
If hooks still fail, `--no-verify` is acceptable but **verify CI passes after push**.
167+
The committed state must be byte-identical to Linux `make generate` + hook normalization.
126168

127169
## Git Commit Policy
128170

AGENTS.md

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,62 @@ Read these before making changes; they are authoritative for repo workflows.
4848
- `registry_updater.py` writes catalogs (and is the only place using `file_collector.py`); `registry_resolver.py` reads catalogs.
4949
- Missing catalog entries should fail fast with clear errors; no silent fallbacks.
5050

51+
## Generated Artifacts & Line Endings (Windows/Linux CI)
52+
53+
This repo's CI runs on Linux and verifies that committed artifacts exactly match
54+
`make generate` output. When developing on **Windows**, the LinkML generators
55+
(`gen-owl`, `gen-shacl`, `gen-jsonld-context`) produce CRLF line endings and
56+
sometimes trailing newlines that differ from Linux output.
57+
58+
**Pre-commit hooks involved:**
59+
- `generate-linkml` — regenerates artifacts (produces CRLF on Windows)
60+
- `mixed-line-ending` — normalizes to LF
61+
- `pretty-format-json` — reformats `.context.jsonld` (may add/remove trailing newline)
62+
63+
**The problem:** On Windows, committing triggers a loop:
64+
1. `generate-linkml` hook produces CRLF artifacts → hook reports "files modified"
65+
2. `mixed-line-ending` fixes to LF → hook reports "files modified"
66+
3. Commit fails because hooks modified files; retry triggers step 1 again
67+
68+
**Correct workflow when committing generated artifacts on Windows:**
69+
70+
```bash
71+
# 1. Generate artifacts
72+
make generate DOMAIN=openlabel-v2
73+
74+
# 2. Normalize line endings manually (Python one-liner)
75+
python -c "
76+
import glob
77+
for f in glob.glob('artifacts/openlabel-v2/*'):
78+
with open(f, 'rb') as fh: c = fh.read()
79+
c = c.replace(b'\r\n', b'\n')
80+
with open(f, 'wb') as fh: fh.write(c)
81+
"
82+
83+
# 3. For .context.jsonld specifically: ensure no trailing empty line
84+
# (gen-jsonld-context adds one; pretty-format-json removes it)
85+
python -c "
86+
f = 'artifacts/openlabel-v2/openlabel-v2.context.jsonld'
87+
with open(f, 'rb') as fh: c = fh.read()
88+
c = c.rstrip() + b'\n'
89+
with open(f, 'wb') as fh: fh.write(c)
90+
"
91+
92+
# 4. Stage and commit (hooks should now pass cleanly)
93+
git add artifacts/
94+
git commit -s -S -m "feat: ..."
95+
```
96+
97+
**If hooks still loop**, use `--no-verify` but then immediately verify:
98+
```bash
99+
git commit -s -S --no-verify -m "feat: ..."
100+
# Push and check that CI "Generate Artifacts" job passes
101+
```
102+
103+
**Key rule:** The committed state must be byte-identical to what Linux
104+
`make generate` + pre-commit normalization produces. CI enforces this via
105+
the "Verify no changes" step.
106+
51107
## Commit & Pull Request Guidelines
52108

53109
- Recent history favors short, imperative subjects with optional prefixes like `feat:`, `fix:`, `docs:`, or scoped forms like `feat(ontology): ...`.

CLAUDE.md

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -256,3 +256,44 @@ Update both files **before** presenting the final result to the user. If a sessi
256256
- Silent `None` returns instead of raising exceptions
257257
- Using `print()` for internal progress (use `logger`)
258258
- Leaking absolute paths in output (use `normalize_path_for_display`)
259+
- Committing generated artifacts with CRLF or trailing newlines on Windows (CI fails)
260+
261+
## Generated Artifacts & Line Endings (Windows/Linux CI)
262+
263+
CI regenerates artifacts on Linux and diffs against the committed state. On Windows,
264+
LinkML generators (`gen-owl`, `gen-shacl`, `gen-jsonld-context`) produce CRLF line
265+
endings, causing pre-commit hooks to enter an infinite fix-loop.
266+
267+
**Symptoms:**
268+
- Pre-commit hooks report "files were modified" on every attempt
269+
- `generate-linkml` → CRLF → `mixed-line-ending` fixes → hooks fail → repeat
270+
- CI "Generate Artifacts" job fails with "make generate produced different artifacts"
271+
272+
**Fix (Windows development):**
273+
274+
```bash
275+
# After generating artifacts, normalize before committing:
276+
python -c "
277+
import glob
278+
for f in glob.glob('artifacts/<domain>/*'):
279+
with open(f, 'rb') as fh: c = fh.read()
280+
c = c.replace(b'\r\n', b'\n')
281+
with open(f, 'wb') as fh: fh.write(c)
282+
"
283+
284+
# For .context.jsonld: strip trailing empty line (CI doesn't produce one)
285+
python -c "
286+
f = 'artifacts/<domain>/<domain>.context.jsonld'
287+
with open(f, 'rb') as fh: c = fh.read()
288+
c = c.rstrip() + b'\n'
289+
with open(f, 'wb') as fh: fh.write(c)
290+
"
291+
```
292+
293+
**Key invariant:** Committed artifacts must be byte-identical to what Linux
294+
`make generate` + pre-commit normalization produces. When in doubt, use
295+
`--no-verify` to commit and verify that CI's "Generate Artifacts" job passes.
296+
297+
**Root cause:** Python's LinkML generators inherit the platform's default line
298+
separator. The repo has no `.gitattributes` forcing `eol=lf` on generated files
299+
(potential future fix).

Makefile

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -208,9 +208,9 @@ _generate_default:
208208
for domain in $$DOMAINS_TO_BUILD; do \
209209
echo " Processing $$domain..."; \
210210
mkdir -p artifacts/$$domain; \
211-
"$(GEN_OWL)" --deterministic --normalize-prefixes --xsd-anyuri-as-iri --no-metadata --default-language en --ontology-uri-suffix "" linkml/$$domain/$$domain.yaml 2>/dev/null | tr -d '\r' > artifacts/$$domain/$$domain.owl.ttl; \
212-
"$(GEN_SHACL)" --deterministic --normalize-prefixes --no-metadata --default-language en --message-template "{name} ({class}): {description}" linkml/$$domain/$$domain.yaml 2>/dev/null | tr -d '\r' > artifacts/$$domain/$$domain.shacl.ttl; \
213-
"$(GEN_JSONLD_CONTEXT)" --deterministic --normalize-prefixes --no-metadata --exclude-external-imports --xsd-anyuri-as-iri linkml/$$domain/$$domain.yaml 2>/dev/null | tr -d '\r' > artifacts/$$domain/$$domain.context.jsonld; \
211+
"$(GEN_OWL)" --deterministic --normalize-prefixes --xsd-anyuri-as-iri --no-metadata --default-language en --ontology-uri-suffix "" linkml/$$domain/$$domain.yaml 2>/dev/null | tr -d '\r' | sed -e '$${' -e '/^$$/d' -e '}' > artifacts/$$domain/$$domain.owl.ttl; \
212+
"$(GEN_SHACL)" --deterministic --normalize-prefixes --no-metadata --default-language en --message-template "{name} ({class}): {description} {comments}" linkml/$$domain/$$domain.yaml 2>/dev/null | tr -d '\r' | sed -e '$${' -e '/^$$/d' -e '}' > artifacts/$$domain/$$domain.shacl.ttl; \
213+
"$(GEN_JSONLD_CONTEXT)" --deterministic --normalize-prefixes --no-metadata --exclude-external-imports --xsd-anyuri-as-iri linkml/$$domain/$$domain.yaml 2>/dev/null | tr -d '\r' | sed -e '$${' -e '/^$$/d' -e '}' > artifacts/$$domain/$$domain.context.jsonld; \
214214
done
215215
@echo "[OK] Artifacts generated"
216216

0 commit comments

Comments
 (0)