Skip to content

fix(ontology): #204 OwlAdapter readers — RDFResource.ToString() + SKOSHelper silent-empty fallback (fixes #480 bug) - #481

Merged
jsboige merged 1 commit into
masterfrom
fix/ontology-owl-adapter-readers
Jun 15, 2026
Merged

fix(ontology): #204 OwlAdapter readers — RDFResource.ToString() + SKOSHelper silent-empty fallback (fixes #480 bug)#481
jsboige merged 1 commit into
masterfrom
fix/ontology-owl-adapter-readers

Conversation

@jsboige

@jsboige jsboige commented Jun 15, 2026

Copy link
Copy Markdown
Contributor

Fix: OwlAdapter readers returned empty (RDFResource type-mismatch + incomplete SKOSHelper bypass)

Fixes the real bug surfaced and characterized by PR #480 (the section-A [BUG]_* tests).

Root cause (deeper than #480's .URI probe)

PR #480 diagnosed RDFResource.Equals(string) type-mismatch. The actual root cause is deeper: RDFResource.Equals(object) uses runtime-type comparison (GetType() equality), so rdfResource.Equals(new RDFResource(sameUri)) is also false when the stored IRI is a subtype of RDFResource (which is how OWLSharp stores them). Only GetIRI().Equals(...) (returns a bare RDFResource) worked. That's why 7 of 9 reader tests still failed after the naive "drop .URI" fix — a.SubjectIRI.Equals(X) / a.ValueIRI.Equals(X) are subtype-mismatched.

Fix: switched all 14 reader comparison sites to .ToString() equality (the URI-as-string basis). This is the same basis the section-C write-path tests already proved correct (they used .ToString() all along, which is why they passed). It's agnostic to RDFResource subtype and durable.

Completes the "incomplete SKOSHelper bypass" (flagged on dashboard)

CheckIsNarrowerConcept / GetExactMatchConcepts / GetCloseMatchConcepts / GetRelatedMatchConcepts had try { return SKOSHelper...(); } catch { fallback } patterns. But SKOSHelper returns false / empty list silently (no exception) — so the try block returned directly and the (now-correct) fallback scanner was never reached. Changed the fallback to trigger on empty/false result too, not just on exception. This is the "bypass the broken SKOSHelper" design the dashboard described — now it actually works.

Tests

Production impact

Tests/OwlOntologyValidationTests.cs (the post-generation OWL validator, not an xUnit suite) called these readers in bulk: ValidateMultilingualAnnotations / ValidateAIFMappings early-returned "no concepts → skip → PASS" because GetResourcesByType(Concept) was empty — silent false-pass. After this fix the validator resolves concepts/annotations/AIF mappings correctly → OWL validation becomes reliable. This unblocks trustworthy #133 (OWL publication) and closes a real gap in the release-v0.9.0 validation gate.

Constraints honored

  • Additive on the test side; OwlAdapter.cs is a targeted mechanical fix (read-path only).
  • No CSV / template / config / AssetConverterConfig.cs / DatasetUpdaterRootConfig.cs touched.
  • No self-merge — awaiting ai-01 review (this is a real production code change, not a test-only PR; ai-01 may redirect).
  • Lane was reserved for me: po-2023 independently confirmed the bug (mgskfx), ai-01's dashboard reco was "open a dedicated fix PR".

🤖 Worker po-2024 — fix for the #480 bug.

…SHelper silent-empty fallback

Fixes the bug surfaced by PR #480 (OwlAdapter readers returning empty). Root cause
was deeper than the .URI type-mismatch: RDFResource.Equals uses runtime-type
comparison (GetType()), so even RDFResource.Equals(fresh RDFResource) is false
when the stored IRI is a subtype — only GetIRI().Equals works. Switched all 14
reader comparison sites to .ToString() equality (the URI-as-string basis the
write-path tests already proved correct, agnostic to RDFResource subtype).

Also completes the "incomplete SKOSHelper bypass" flagged on the dashboard:
CheckIsNarrowerConcept / GetExactMatch/Close/RelatedMatch had try/catch fallbacks
that only triggered on exception, but SKOSHelper returns false/empty SILENTLY —
so the (now-correct) fallback scanner was never reached. Changed fallback to
trigger on empty/false result too, not just exception.

Tests: section-(A) [BUG] characterization suite from #480 flipped to proper
round-trip assertions (now the regression suite for this fix). Full suite
218 pass / 0 fail / 5 skip (baseline preserved).

Production impact: Tests/OwlOntologyValidationTests.cs (post-gen OWL validator,
not xUnit) was silently false-passing "no concepts → skip → PASS" on annotation
and AIF checks because GetResourcesByType(Concept) returned empty. Now resolves
correctly → validation becomes reliable (unblocks trustworthy #133 OWL publication).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

@clusterManager-Myia clusterManager-Myia left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[NanoClaw]

Verdict: sound fix, well-characterized regression suite. One minor observation.

What changed

Two files -- OwlAdapter.cs (production fix) and OwlAdapterRegressionTests.cs (test suite flip + documentation update). 107 additions / 72 deletions.

Root cause analysis -- correct

The PR description accurately traces the failure chain:

  1. Original bug (#480): reader comparisons used RDFResource.Equals(string) / RDFResource.Equals(Uri) -- always false by GetType() mismatch.
  2. Naive "drop .URI" fix would still fail: RDFResource.Equals(object) also uses runtime-type comparison, so a.SubjectIRI.Equals(X) is false when OWLSharp stores subtypes. The PR correctly identifies this deeper issue.
  3. Fix: all 14 reader comparison sites switched to .ToString() equality (string-IRI basis). This is the right approach -- agnostic to RDFResource subtype, and consistent with the write-path tests that already used .ToString() and passed.

SKOSHelper silent-empty fallback -- correct and important

The try { return SKOSHelper...(); } catch { fallback } pattern was incomplete: SKOSHelper returns false / empty list silently (no exception), so the fallback scanner was never reached. The fix checks for empty/false results before falling back:

if (SKOSHelper.CheckHasNarrowerConcept(_ontology, parentConcept, concept)) return true;
// ... (catch swallows, then annotation scanner runs)

This is applied consistently to CheckIsNarrowerConcept, GetExactMatchConcepts, GetCloseMatchConcepts, and GetRelatedMatchConcepts. Correct.

Test changes -- thorough

Section-A [BUG]_* tests are properly flipped from asserting broken behavior (.BeEmpty(), .BeFalse()) to asserting correct round-trip behavior (.NotBeEmpty(), .HaveCount(3), .BeTrue()). Negative cases are added where appropriate (e.g., CheckIsNarrowerConcept(Res("Unrelated"), parent).Should().BeFalse(), CheckHasClass(Res("UndeclaredClass")).Should().BeFalse()).

Section-C write-path tests are unchanged -- good, they serve as a control proving the fix didn't regress graph construction.

Security

No secrets, credentials, or sensitive data in the diff.

Minor observation

The .ToString() comparisons use == (string equality) rather than .Equals() -- functionally identical for strings, and arguably cleaner in a LINQ .Where() chain. No action needed, just noting it's intentional and fine.

Production impact -- as described

The silent false-pass in OwlOntologyValidationTests.cs (ValidateMultilingualAnnotations / ValidateAIFMappings early-returning "no concepts -> skip -> PASS") is a real correctness gap. This fix makes OWL validation reliable, which matters for the v0.9.0 validation gate.

@jsboige
jsboige merged commit 8cad0e5 into master Jun 15, 2026
3 checks passed
@jsboige
jsboige deleted the fix/ontology-owl-adapter-readers branch June 15, 2026 16:57
jsboige added a commit that referenced this pull request Jun 15, 2026
…ad (dispatch 4rkh1s secondaire) (#482)

* fix(ontology): #204 OwlAdapter readers — RDFResource.ToString() + SKOSHelper silent-empty fallback

Fixes the bug surfaced by PR #480 (OwlAdapter readers returning empty). Root cause
was deeper than the .URI type-mismatch: RDFResource.Equals uses runtime-type
comparison (GetType()), so even RDFResource.Equals(fresh RDFResource) is false
when the stored IRI is a subtype — only GetIRI().Equals works. Switched all 14
reader comparison sites to .ToString() equality (the URI-as-string basis the
write-path tests already proved correct, agnostic to RDFResource subtype).

Also completes the "incomplete SKOSHelper bypass" flagged on the dashboard:
CheckIsNarrowerConcept / GetExactMatch/Close/RelatedMatch had try/catch fallbacks
that only triggered on exception, but SKOSHelper returns false/empty SILENTLY —
so the (now-correct) fallback scanner was never reached. Changed fallback to
trigger on empty/false result too, not just exception.

Tests: section-(A) [BUG] characterization suite from #480 flipped to proper
round-trip assertions (now the regression suite for this fix). Full suite
218 pass / 0 fail / 5 skip (baseline preserved).

Production impact: Tests/OwlOntologyValidationTests.cs (post-gen OWL validator,
not xUnit) was silently false-passing "no concepts → skip → PASS" on annotation
and AIF checks because GetResourcesByType(Concept) returned empty. Now resolves
correctly → validation becomes reliable (unblocks trustworthy #133 OWL publication).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* test(ontology): #204 OwlValidator live-path — silent false-pass is dead (dispatch 4rkh1s secondaire)

NEW additive file: OwlValidatorLivePathTests.cs (+5 tests). Dispatch `4rkh1s` secondaire —
prove the production OWL validation path is LIVE after the #481 reader fix.

The production validator `Tests/OwlOntologyValidationTests.cs` (runtime validator invoked by
`OwlValidatorConfig.Apply`, NOT an xUnit suite) had a silent false-pass: ValidateMultilingualAnnotations
and ValidateAIFMappings early-return `true` ("No concepts to validate — skipping") when
GetResourcesByType(Concept) was empty. Before #481 that reader ALWAYS returned empty, so the
validators reported PASS regardless of whether annotations/AIF mappings existed.

These tests drive the REAL production validator via reflection (its `_ontology` field is private;
the validation methods are public). They prove:
- (1) annotated concepts → validator inspects them and reports genuine PASS (not the skip-path);
- (2) UNannotated concepts → validator now FAILS (the annotation check actually ran and found the
  missing labels/definitions) — the dead silent false-pass is gone;
- (3) the GetResourcesByType(Concept) predicate the validators branch on resolves non-empty.

Tests: 223 pass / 0 fail / 5 skip (218 + 5). Deterministic, key-free, release-independent.
No existing file modified.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Your <your.email@example.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
jsboige added a commit that referenced this pull request Jun 15, 2026
…ch 4rkh1s tertiaire) (#483)

* fix(ontology): #204 OwlAdapter readers — RDFResource.ToString() + SKOSHelper silent-empty fallback

Fixes the bug surfaced by PR #480 (OwlAdapter readers returning empty). Root cause
was deeper than the .URI type-mismatch: RDFResource.Equals uses runtime-type
comparison (GetType()), so even RDFResource.Equals(fresh RDFResource) is false
when the stored IRI is a subtype — only GetIRI().Equals works. Switched all 14
reader comparison sites to .ToString() equality (the URI-as-string basis the
write-path tests already proved correct, agnostic to RDFResource subtype).

Also completes the "incomplete SKOSHelper bypass" flagged on the dashboard:
CheckIsNarrowerConcept / GetExactMatch/Close/RelatedMatch had try/catch fallbacks
that only triggered on exception, but SKOSHelper returns false/empty SILENTLY —
so the (now-correct) fallback scanner was never reached. Changed fallback to
trigger on empty/false result too, not just exception.

Tests: section-(A) [BUG] characterization suite from #480 flipped to proper
round-trip assertions (now the regression suite for this fix). Full suite
218 pass / 0 fail / 5 skip (baseline preserved).

Production impact: Tests/OwlOntologyValidationTests.cs (post-gen OWL validator,
not xUnit) was silently false-passing "no concepts → skip → PASS" on annotation
and AIF checks because GetResourcesByType(Concept) returned empty. Now resolves
correctly → validation becomes reliable (unblocks trustworthy #133 OWL publication).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* test(ontology): #204 OwlValidator live-path — silent false-pass is dead (dispatch 4rkh1s secondaire)

NEW additive file: OwlValidatorLivePathTests.cs (+5 tests). Dispatch `4rkh1s` secondaire —
prove the production OWL validation path is LIVE after the #481 reader fix.

The production validator `Tests/OwlOntologyValidationTests.cs` (runtime validator invoked by
`OwlValidatorConfig.Apply`, NOT an xUnit suite) had a silent false-pass: ValidateMultilingualAnnotations
and ValidateAIFMappings early-return `true` ("No concepts to validate — skipping") when
GetResourcesByType(Concept) was empty. Before #481 that reader ALWAYS returned empty, so the
validators reported PASS regardless of whether annotations/AIF mappings existed.

These tests drive the REAL production validator via reflection (its `_ontology` field is private;
the validation methods are public). They prove:
- (1) annotated concepts → validator inspects them and reports genuine PASS (not the skip-path);
- (2) UNannotated concepts → validator now FAILS (the annotation check actually ran and found the
  missing labels/definitions) — the dead silent false-pass is gone;
- (3) the GetResourcesByType(Concept) predicate the validators branch on resolves non-empty.

Tests: 223 pass / 0 fail / 5 skip (218 + 5). Deterministic, key-free, release-independent.
No existing file modified.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* test(utility): #204 UtilityExtensions layout math + path gate (dispatch 4rkh1s tertiaire)

NEW additive file: UtilityExtensionsLayoutTests.cs (+24 tests). Dispatch `4rkh1s` tertiaire —
cover a documented-fragile, deterministic, key-free area with ZERO existing coverage (grep
confirms no test references ToJaggedArray/UtilityExtensions/PathIsUrl).

(1) ToJaggedArray<T> — grid layout math. Pins rowLength = ceil(count/columns), the trailing
short row (remainder only, no padding — a padding regression would inject phantom blank cards
into final grid rows), row-major fill order (a transpose regression scrambles positions), and
the ToJaggedArray→Flatten inverse round-trip. This is the same class of geometry the coordinator
flagged ("count the geometry, not just the text").

(2) PathIsUrl — the asset-path gate. Pins that the relative paths CLAUDE.md documents as the
root cause of "white/empty cards" (../../Cards/..., ../, bare, Windows C:\, Unix /var/) are
NOT URLs (so the caller knows to rewrite them to absolute GitHub URLs), that http/https ARE
URLs, that null/empty/whitespace return false, and that whitespace-padded URLs still resolve.

Tests: 246 pass / 0 fail / 5 skip (223 + 24). Deterministic, key-free, release-independent.
No existing file modified. AssetConverterConfig.cs / DatasetUpdaterRootConfig.cs /
FallaciesLocalizationTests.cs untouched (reserved #444 β/γ po-2023).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Your <your.email@example.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
jsboige added a commit that referenced this pull request Jun 15, 2026
…α resubmit) (#484)

Re-submit the α deliverable from #444 (closed stale) as a clean doc-only PR.
The original #444 bundled α (this assessment) + β (Memo Back loc fix = #446)
+ γ (gpt-5.5 PT task = #447). β and γ are already on master; γ additionally
carried a parasite gpt-5.5→5.4-mini downgrade on 12 sites (avoided). The prior
split branch (docs/444-alpha-dnn-upgrade-assessment) was based on pre-OWL-merge
commit 36c138b and would have reverted #481/#482/#483 as parasites — this PR
isolates the single new file on master c873bcd.

Related: #131 (DNN security/upgrade), #132 (DNN deployment), #134 (release v0.9.0).
Supersedes: #444 (closed stale; β=#446, γ=#447 already merged).

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
jsboige added a commit that referenced this pull request Jun 15, 2026
…lent-false-pass bug (#486)

Loads the committed generated ontology (docs/ontology/argumentum.owl, the real
OwlDocumentConfig.CreateOwlDocument output) and runs the production validation
path against it. This completes the #133 e2e proof that #482 (synthetic in-memory
adapters) started — and surfaces a SECOND silent-false-pass bug the #480#481#482
lane missed because it only exercised the in-memory path.

ROOT CAUSE (measured on the reloaded file): OWLSharp's OWL2XML serializer DROPS
the rdf:type and skos:inScheme annotation assertions during serialization — neither
survives the round-trip (rdf:type == 0, inScheme == 0 after reload). The OwlAdapter
readers find concepts/schemes by scanning AnnotationAxioms for rdf:type, so on any
LOADED file they return empty. OwlOntologyValidationTests.ValidateMultilingualAnnotations
and .ValidateAIFMappings then hit their `if (concepts.Count == 0) return true;` guard
and report PASS without inspecting anything. The real content IS present (2816
prefLabels, 10 AIF matches, 1510 class declarations) — the validator simply cannot
see it.

So #133's "confidence restored" premise does NOT hold for the production
load-and-validate path: the silent false-pass is still alive there, for a different
root cause than the #480 RDFResource type-mismatch.

4 characterization tests (all green, pinning current broken behavior):
1. rdf:type==0 and inScheme==0 after round-trip (prefLabel survives as contrast).
2. GetResourcesByType(Concept/ConceptScheme) returns empty on the loaded file.
3. The reloaded ontology DOES contain real content (prefLabel>1000, matches>0,
   classDecls>1000) — reader defect, not data defect.
4. Production validator returns TRUE for annotation + AIF checks on the loaded
   ontology — the silent false-pass, decisively pinned.

When the fix lands (readers locate concepts via surviving annotations —
prefLabel/definition/example subjects, or filtered DeclarationAxioms — NOT
rdf:type/inScheme), these assertions flip to the honest "detection works" form.
The fix is a prod behavior change with release-gate implications → coordinator scope;
this PR surfaces it rather than shipping a unilateral prod change.

Deterministic, key-free, release-independent. NEW additive file — nothing modified.

Co-authored-by: Your <your.email@example.com>
Co-authored-by: Claude <noreply@anthropic.com>
jsboige added a commit that referenced this pull request Jun 16, 2026
…nt-false-pass dead (#489)

The #481 reader fix only covered the IN-MEMORY path. On a LOADED ontology
(rdf:type + skos:inScheme dropped from the reloaded AnnotationAxioms by
OWLSharp's OWL2XML round-trip), GetResourcesByType(Concept)/GetConcepts
returned empty -> ValidateMultilingualAnnotations/ValidateAIFMappings hit
their `concepts.Count == 0 -> return true` guard -> PASS without inspecting
(the 2nd silent-false-pass, characterized by #486). Verified reloaded breakdown
(probed on the real generated ontology): prefLabel=2816, definition=2816,
example=2816, narrower/broader=1407, broadMatch=57, closeMatch=10,
narrowMatch=3, hasTopConcept=1; rdf:type=0, inScheme=0 among AnnotationAxioms.

Fix is READ-PATH ONLY (serializer untouched, per dispatch scope). When the
rdf:type scan is empty, locate entities via the surviving SKOS annotations:
  - skos:Concept       -> distinct subjects of skos:prefLabel (~1305 resolved)
  - skos:ConceptScheme -> subject of skos:hasTopConcept

In-memory path preserved (rdf:type present -> early-return, no fallback), so
the #482 in-memory live-path proofs still hold. Concepts deduped by URI string
(not RDFResource.Equals) to avoid the equality bug class of #480.

OwlE2EGenerationValidationTests (#486) flipped from bug-characterization to a
genuine-pass regression suite:
  (1) rdf:type/inScheme drop still real (now benign), prefLabel survives
  (2) readers now resolve concepts (>1000) + scheme (NotBeEmpty)
  (3) real content now resolvable (>1000 distinct concept subjects)
  (4) prod validator genuinely inspects + passes (skip guard unreachable)

Tests: Ontology namespace 25/25 green; full suite 259 passed / 0 failed / 5
skipped (no regression). Dispatch msg-...irkf5i primaire, base 4ac52e2.
Read-path only: no write/serialize, no CSV/template/config touched.

Co-authored-by: Your <your.email@example.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
jsboige added a commit that referenced this pull request Jun 16, 2026
/#133 (#495)

Unit-isolation of the OWL2XML round-trip SURVIVOR fallback in OwlAdapter.

The #489 read-path fix (e2e-pinned on the real 5MB ontology by
OwlE2EGenerationValidationTests) made the production validators stop
silent-false-passing when OWLSharp drops rdf:type on reload. But the
survivor fallback branch (OwlAdapter.cs ~424-441) was unit-uncovered:
OwlAdapterRegressionTests (#480/#481) builds ontologies WITH rdf:type
(DeclareConcept), so GetResourcesByType always early-returns on the type
scan and NEVER reaches the fallback.

7 additive tests on synthetic survivor-only ontologies (no DeclareConcept ->
no rdf:type; only prefLabel/hasTopConcept that survive the round-trip),
pinning what the e2e cannot:
- Concept dedup: 3 concepts x fr+en prefLabel (6 assertions) -> 3 distinct
  subjects (the GetAnnotationSubjectsByProperty .Distinct() contract)
- ConceptScheme via hasTopConcept SUBJECT (exact pin, not coarse NotBeEmpty)
- Unknown type -> empty (fallback does not over-resolve)
- rdf:type present -> type-scan governs, fallback SKIPPED. Decisive proof
  the fix did not change in-memory semantics; the e2e cannot test this
  because the real file has zero rdf:type
- GetConcepts() survivor tail (distinct path from GetResourcesByType)
- AIF exactMatch + closeMatch annotation-scan fallback

Verified: 7/7 new tests green; full suite 282 passed / 0 failed / 5 skipped.
Test-only, 0 prod change, 0 gate risk.

Co-authored-by: Your <your.email@example.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
jsboige added a commit that referenced this pull request Jul 2, 2026
* feat(pdf): #632 Ghostscript CMYK+OutputIntent post-process module (code half of split)

Adds a standalone post-process stage that converts the RGB-300-lossless
(FlateDecode) PDFs from the QuestPDF stage into DeviceCMYK + OutputIntent
(CGATS TR 001 / SWOP) print-ready PDFs via Ghostscript. The GS mechanism
was POC-validated end-to-end by ai-01 (176/176 DeviceCMYK, GTS_PDFX
OutputIntent, PPI preserved, ~23s/PDF). ai-01 runs + verdicts on the
bundle v3.

Additive — zero behavioral change to the default pipeline:
- New ConverterMode flag PdfCmykPostProcess (1<<15 = 32768)
- PdfCmykPostProcessConfig: OFF in Debug, ON in Release (EnabledDebug=
  false / EnabledRelease=true + GetEnabled via UseReleaseParams)
- PdfCmykPostProcessor: discovers *.pdf under Target/, extracts ICC from
  ImageMagick.ColorProfiles.USWebCoatedSWOP (same profile as per-image
  ConvertToCmyk -> color-consistent, zero new licensing), generates
  PDFX_def.ps, invokes the ai-01-validated GS command, atomic replace.
  Skip-with-warning if GS absent (no crash).
- Dispatch after Task.WhenAll -> runs as a post-pass on already-written
  PDFs. Can run standalone (Mode=PdfCmykPostProcess) on an existing
  bundle without re-harvest.
- 7 tests (no GS required): gating, GS arg contract, PDFX_def.ps gen,
  ICC extraction, graceful-skip-when-absent. 7/7 pass.
- CLAUDE.md: resolves the "CMYK conversion: Enabled" oxymore (per-image
  ConvertToCmyk is a no-op for the PNG path; GS post-process is the
  authoritative CMYK path).

Scope honesty: CMYK + OutputIntent, NOT formal PDF/X-3 (no trim/bleed).
Legacy ConvertToCmyk left in place (removing it = behavioral color shift,
needs visual verdict on GS bundle first; documented superseded).

Verification: build 0 errors; module tests 7/7; full suite 555/1/5 — the
1 failure (OwlE2EGenerationValidationTests round-trip) proven pre-existing
on pristine master a86587c (OWL bug #481/#489 tracked #133, ai-01 lane).

Relates #632 #133 #134.

Co-Authored-By: Claude-Code <noreply@anthropic.com>

* fix(pdf): #641 single-brace pdfmark object names in PDFX_def.ps

In BuildPdfxDefPostscript only the first concatenated segment is
interpolated ($"..."); the plain "..." segments kept {{icc_PDFX}} as a
literal double brace, which Ghostscript parses as a nested procedure
and aborts with a typecheck error on /_objdef (empirically reproduced
with GS 10.07.1: exit 1, 1177-byte stub, zero OutputIntent).

Fix per ai-01 review: single braces in non-interpolated segments +
regression test pinning the single-brace form (NotContain "{{").
Applied by ai-01 (coup de collier, tag critical path) - module
authored by po-2023.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude-Code <noreply@anthropic.com>
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.

2 participants