INHERIT v2 ε.ι Layer 2 architectural pattern: the LinkML YAML schema is the canonical metadata source-of-truth. Everything downstream is derived; metadata access flows from YAML, not from generated code.
Why: S3 spike 2026-05-02 verified that gen-typescript + gen-json-schema drop annotations entirely (LinkML 1.10 deliberate design — see feedback_linkml_annotation_surface_separation). gen-pydantic preserves via linkml_meta + Field.json_schema_extra. Catala scope-binding works end-to-end via YAML-direct shim. The architecturally-correct answer is not to fight LinkML’s separation — it’s to respect it and route metadata access via the appropriate surface.
Operational pattern for ε.ι Layer 2 (and any INHERIT v2 phase-gated / OntoUML-stereotyped class):
- YAML schema is canonical: author OntoUML stereotypes + phase metadata as
annotations:blocks at the LinkML YAML level. Useannotations:(LinkML community’s de-facto pattern), notextensions:(extensions:is for typed schema-extensions; same generator behaviour but signals different intent). - Python runtime via Pydantic: emit Pydantic with
~/tools/inherit-spike-env/bin/gen-pydantic --meta full schema.yaml > module.py 2> module.py.err. Access annotations viaMyClass.linkml_meta.root['annotations'](class-level) +MyClass.model_fields[<slot>].json_schema_extra['linkml_meta']['annotations'](attribute-level). Note that gen-pydantic emits files with hyphens (schema.py) but Python imports need underscores — workaround:cp schema.py schema_module.pyor rename. - Catala scope-binding via YAML-direct shim: build-pipeline shim reads YAML via
yaml.safe_load, filters classes by phase annotations, emits per-phase.catala_enfiles. Reference shim at/tmp/spike-s3-ontouml/scope_binding_shim.py(38 lines) + emitter at/tmp/spike-s3-ontouml/scope_binding_emitter.py(58 lines). Validation:~/tools/inherit-spike-bin/catala typecheck --no-stdlib emitted-phase-X.catala_enfor spike-level scope validation; production v2 build pipeline usesclerk startfor full Stdlib_en resolution. - TS + JSON-Schema are data-validation surfaces only: emit them with
gen-typescript schema.yaml > schema.ts+gen-json-schema schema.yaml > schema.schema.json. They do NOT carry OntoUML metadata. If browser-side or wire-format consumers need metadata, emit a sidecar*.metadata.json(Year-2+ uplift; ~10-line Python build script that parses YAML + projects annotations into a flat metadata map). - Frontmatter pin-drift hook validates
annotations:block schema: extend~/testatetech/scripts/check-frontmatter-pins.pyto enforceinherit:phase_activation_status∈{active, deferred, retired}+inherit:phase_activation_target_phasematches valid phase string. (richard-task #204 logged 2026-05-02.)
How to apply:
- For Phase-1 build pipeline scaffolding (
code-inherit-v2/Tier-3 standard): canonical pattern for any class needing OntoUML/inherit:phase metadata. Apply to all 9 i-ζ classes (per ε.ε lock) + future Phase-1.5 deferred classes. - For ε.ι A-131 amendment authoring (Phase E Task 13 lock-time): name this pattern explicitly. Cost-row at lock: ~£0.5-1K incremental tooling for the YAML-direct shim + sidecar JSON helper (vs S2 fallback’s manual hand-curation, this is automated build-pipeline tooling).
- For Catala scope authoring across modules (Wills, Probate, Trusts, etc.): per-module Catala scope files emitted from per-phase filtered LinkML YAML. Wills.Bequest scopes filter by attestation phase; Probate.ExecutedEstate scopes filter by registration phase; Trusts.Trust scopes filter by trust-type-active phase; Pension scope omitted Phase-1 + included Phase-1.5+ per PensionAsset deferred-activation pattern.
- For partner-firm REVIEW (Layer 4 of ε.ι universal-production-pipeline): partner-document includes the LinkML YAML fragment + the Pydantic class signature + the Catala scope file emitted at the partner’s target phase. This makes the phase-gating concrete + reviewable.
- For browser-side TS clients (Phase-1.5+ uplift candidate; not Phase-1 critical path): emit sidecar
*.metadata.json;inherit-pilot.metadata.jsonconsumed by build-time TS module that re-exports phase-gating helpers.
Tooling pin (verified 2026-05-02 at S3 spike):
- LinkML 1.10.0 →
gen-pydantic --meta full(NOT--include-annotations=True— that flag does not exist; older docs/plans had wrong flag name) - Catala 1.1.0 →
typecheck --no-stdlibfor spike-level validation; production viaclerk start - node 24.14.1
--experimental-strip-types --checkfor TS syntactic validation without tsc
Don’t:
- Don’t try to “fix” the TS + JSON-Schema annotation-drop via custom Jinja templates or LinkML-version chasing (LinkML 1.11.0-rc1 not validated; not a Phase-1 priority).
- Don’t read
gen-json-schemaoutput for metadata access — it has onlydescription:+ standard JSON Schema fields. YAML or Pydantic instead. - Don’t trust
gen-pydantic --meta auto(default) to behave identically to--meta fullacross all schema shapes — test with--meta fullexplicitly when annotations are load-bearing. - Don’t run
catala typecheckwithout--no-stdlibflag in spike contexts — fails on missing Stdlib_en. Production needsclerk start.
Source: T-spike-eps-iota-S3-ontouml-linkml-2026-05-02.md §3 (working configuration for ε.ι Layer 2 lock-time framing); arch-state v3.19 §11 S3 row + Changelog v3.19 row; plan v1.4 §1.8 + §2 Task 3 (Steps 3 + 7 corrected to use --meta full + --no-stdlib). PensionAsset (richard-task #213 deferred Year-2+) used as canonical phase-stereotype pilot class.
Related memories:
feedback_linkml_annotation_surface_separation— the LinkML 1.10 design choice that motivates this architectural patternfeedback_kill_condition_strict_vs_spirit_reading_via_outcome_MITIGATED— how S3 outcome was scored MITIGATED rather than KILL despite strict-clause metfeedback_universal_production_pipeline_sequence— Layer 4 of ε.ι; this YAML-as-canonical pattern is Layer 2 (ontological-grounding tooling)feedback_bold_front_loaded_synthesis_preference— the cost-row reasoning that ~£0.5-1K incremental tooling is rounding-error vs acquirer-narrative-value