Records that the SharedSchema resolver is fully consumed at deploy, runtime and in
the UI value-entry forms, but has no authoring path: SchemaBuilder is the only
schema editor on all four surfaces and offers no library-reference option, so the
page creates entries nothing can point at. SchemaBuilder also collapses a $ref it
is shown to {"type":"string"} — currently unreachable, since no ref exists and
there is no UI or CLI to make one, but a trap for the first one authored.
Includes live usage counts (zero entries, zero refs, both environments) and the
resulting recommendation: leave dormant with a trigger, rather than finish or
delete it now.
10 KiB
Schema Library (SharedSchema) — wiring audit
Date: 2026-08-11 Question: the Schema Library admin page doesn't seem wired to anything. Verdict: the read path is complete and careful; the write path doesn't exist. One of the three gaps is active silent data loss, not just a missing feature.
1. What IS wired
The {"$ref":"lib:Name"} resolver has four real consumers, plus guarded CRUD:
| Consumer | Location | Role |
|---|---|---|
| Deploy-time flattening | DeploymentManager/FlatteningPipeline.cs:150 |
Pre-loads the library once into a name→JSON map; an unresolved ref fails the deploy |
| Template validation | TemplateEngine/Validation/ValidationService.cs:91,149 |
Dangling / cyclic / over-depth ref validation |
| Inbound API runtime | InboundAPI/SchemaRefResolver.cs → ParameterValidator.cs:35, ReturnValueValidator.cs:41 |
Resolves refs when validating live request/response bodies; gated on InboundApiSchema.MightContainRef so ref-free methods pay nothing |
| Central UI value entry | CentralUI/Components/Shared/ParameterValueForm.razor:166 via ISchemaLibraryQueryService |
Inlines referenced schemas so value-entry forms render the real shape |
Supporting cast, all present and correct:
- Entity
Commons/Entities/Schemas/SharedSchema.cs, repoISharedSchemaRepository, EF mappingSharedSchemaConfiguration, registration inConfigurationDatabase/ServiceCollectionExtensions.cs:63. - CRUD dispatched through
ManagementActor(ManagementActor.cs:2818-2884) — the guard-running path, never a direct repo write from the UI. - Nav entry
NavMenu.razor:35, pagePages/Design/SchemaLibrary.razor, testsSchemaLibraryPageTests.cs/SchemaLibraryHandlerTests.cs.
So the library is genuinely consumed at deploy time, at runtime, and in one UI surface.
2. Gap 1 — no UI can produce a lib: reference
SchemaBuilder is the only schema editor on every authoring surface:
Pages/Design/ApiMethodForm.razor:84,90(parameters + return)Pages/Design/SharedScriptForm.razor:71,76Pages/Design/TemplateScriptDialog.razor:141,146Pages/Design/SchemaLibrary.razor:46— the library page itself
Its type picker is SchemaBuilderModel.PrimitiveTypes (SchemaBuilderModel.cs:37-38):
{ "string", "integer", "number", "boolean", "object", "array" }
No library-reference option, at root or property level. There is also no raw-JSON escape hatch
for schemas — the Monaco editor on those forms edits the C# script body, and
ParameterValueForm's raw mode edits values, not the schema.
Consequence: the Schema Library page creates entries that nothing else in the UI can point at. The only authoring routes for a ref are a direct DB edit or a bundle import.
3. Gap 2 — a ref authored outside the UI is silently destroyed on the next save
ParseSchema inspects only the type property (SchemaBuilderModel.cs:96-100). A $ref node
carries no type, so the node defaults to "string", and Serialize writes that back.
Verified by running the round-trip through SchemaBuilderModel directly (throwaway probe, since
removed):
{"$ref":"lib:Address"}
→ {"type":"string"}
{"type":"object","properties":{"addr":{"$ref":"lib:Address"},"name":{"type":"string"}},"required":["addr"]}
→ {"type":"object","properties":{"addr":{"type":"string"},"name":{"type":"string"}},"required":["addr"]}
Both root-level and nested (SchemaBuilderModel.cs:131-133) refs collapse.
Consequence: open any ApiMethod / SharedScript / TemplateScript whose definition carries a ref, change anything at all, save — and the contract is silently widened to a bare string. Deploy still passes (the ref is gone, so there is nothing left to dangle) and nothing warns.
Sharpest edge: the Schema Library page edits entries with SchemaBuilder Mode="value", so a
library entry that references another library entry — which ManagementActor.cs:2921 explicitly
permits — is flattened to {"type":"string"} the moment an operator edits it.
Reachability — correction. An earlier draft of this report called this "worse than merely
unfinished: the feature is reachable, and touching it destroys data." That overstates it. The
corruption requires a $ref to exist first, and per §6 no $ref exists anywhere in either
environment — and since there is no UI path (§2) and no CLI path (§7) to author one, the only way
to create one today is a hand-edit of the database. So this is a trap laid for the first ref,
not an active defect. It becomes live the moment §5.2 ships, or the moment someone hand-authors a
reference.
4. Gap 3 — library entries don't travel between environments
SharedSchema appears nowhere in src/ZB.MOM.WW.ScadaBridge.Transport/.
Contrast its sibling SharedScript, which is a first-class Transport artifact across
Serialization/EntityDtos.cs, Serialization/EntitySerializer.cs, Export/BundleExporter.cs,
Export/ResolvedExport.cs, Export/DependencyResolver.cs, Import/ArtifactDiff.cs and
Import/BundleImporter.cs.
Consequence: a ref that resolves in dev dangles after import into prod. The import-time script trust gate won't catch it — that gate covers script bodies, and this is a schema.
5. Remediation, if the feature is kept
Three separable slices, in dependency order. Read §6 first — on current evidence none of these is urgent, and the prior question is whether the feature should exist at all.
5.1 Close the round-trip trap (small, independent)
Give SchemaNode a passthrough for subtrees the model doesn't understand, so an unrecognised node
($ref included) round-trips verbatim instead of collapsing to {"type":"string"}. Closes
Gap 2 outright, needs no new UI, and is a hard prerequisite for 5.2 — shipping the authoring UI
without it would make the corruption reachable for the first time.
Test to add alongside: a round-trip guard asserting a $ref node survives parse→serialize at both
root and property level — run it against the current code first and observe it fail, or it proves
nothing.
5.2 Make refs authorable (the actual "wire it up")
Add a library-reference entry to the SchemaBuilder type picker, populated from the existing
ISchemaLibraryQueryService (already registered, CentralUI/ServiceCollectionExtensions.cs:142).
Emits {"$ref":"lib:Name"}; with 5.1 in place it round-trips. This is what actually connects the
Schema Library page to the rest of the system.
5.3 Carry SharedSchema in Transport bundles (separate, larger)
DTO + serializer + exporter + dependency resolution (walk definition bodies for lib: refs so
referenced entries are pulled into the bundle) + differ + importer. Mirrors the SharedScript
shape throughout. Worth its own pass, and worth deciding whether an unresolved ref on import is a
blocker row or an advisory warning — the existing name-resolution severity split (template =
advisory, ApiMethod = hard error) is the precedent.
6. Is the feature needed at all?
The gaps above describe an unfinished feature. The prior question — asked after the audit — is whether it should be finished, and the evidence says not on current usage.
6.1 Usage: zero, in both environments
Queried live against scadabridge-mssql on 2026-08-11:
ScadaBridgeConfig (docker rig) |
ScadaBridgeConfig2 (docker-env2) |
|
|---|---|---|
SharedSchemas rows |
0 | 0 |
Definitions containing $ref |
0 | 0 |
ApiMethods |
0 | 0 |
TemplateScripts |
31 | 15 |
| …of those, carrying any parameter definition | 5 | 4 |
Not only has the library never been used — most scripts don't declare a typed parameter shape at all, which is the precondition for ever wanting a reusable one.
(Production wonder-app-vd03 was deliberately not queried; see §7.)
6.2 Origin: speculative, not requirement-driven
It was not built to satisfy a stated need. The schema-editor design doc lists it under deferred
future work — reuse of $ref across templates "could be a future template-level schema library"
(docs/plans/2026-05-12-script-schema-editor.md:117-118). It was built later as M9/T32. The
Scope column is likewise documented as being for future namespacing — a speculative field on a
speculative feature.
6.3 The benefit is authoring-time DRY only
Resolution is inlining, not indirection: FlatteningPipeline and SchemaRefResolver expand
lib:Name into the schema body at deploy / validation / runtime. The library therefore adds no
runtime capability whatsoever — an inline schema does the entire job. Its single benefit is not
retyping a shared shape at authoring time.
That benefit also carries a cost that grows with adoption: there is no reverse index. Editing
lib:Address silently changes every referencing contract at the next deploy, with no
"what references this?" view and no versioning. Tolerable at 2 references, unpleasant at 20. Any
decision to finish the feature should budget for an impact-analysis view alongside 5.2.
6.4 Options
- Delete it. The resolver plumbing (4 consumers), the page, entity, table, repository, ManagementActor handlers and tests are all carried for zero users. Defensible on the evidence.
- Leave it dormant, fix nothing. Zero cost today, because per §3 the defect cannot fire without a ref and no ref exists. Revisit when a genuinely repeated payload shape appears.
- Finish it (5.1 + 5.2 + 5.3, plus an impact-analysis view per 6.3). Justified only by known upcoming work with a repeated non-trivial shape — e.g. one MES order envelope shared across several inbound methods.
Recommendation: option 2, with a hard trigger. If a lib: ref is ever authored, §5.1 must land
in the same change or the builder will destroy it. If nothing needs the library within roughly six
months, take option 1 rather than leaving permanent unexercised surface in the codebase.
7. Notes
- Nothing in this report was changed. No fix has been applied; the verification probe used in §3 was deleted after use.
- The CLI has no schema-library commands either (
src/ZB.MOM.WW.ScadaBridge.CLI/has noSharedSchemareference), so there is no scriptable authoring path as a workaround. This is why §3's trap is currently unreachable: hand-editing the database is the only way to create a ref. - The §6.1 counts are from the two local docker environments only. Production
(
wonder-app-vd03) was not queried — that is an operator action, not one to take unasked. If that box carries inbound methods with$refdefinitions, §3 is live there and the §6.4 recommendation changes.