Skip to content

i3x: expose Data Access datasets as i3X objects (FPLUS-44) - #730

Draft
AlexGodbehere wants to merge 3 commits into
mainfrom
ago/i3x-datasets
Draft

i3x: expose Data Access datasets as i3X objects (FPLUS-44)#730
AlexGodbehere wants to merge 3 commits into
mainfrom
ago/i3x-datasets

Conversation

@AlexGodbehere

Copy link
Copy Markdown
Contributor

Exposes Data Access datasets over i3X, closing the cross-server reference gap tracked as FPLUS-44: the mock MES hands consumers a producedDataset reference (this i3X server + a dataset UUID), and until now that reference resolved to nothing.

What it does

  • Every Data Access dataset (Dataset class in the ConfigDB) becomes an i3X object under a synthetic Datasets folder, elementId = its ConfigDB UUID, with a shared Dataset ObjectType carrying a real JSON schema.
  • GET /objects/:id/value returns a descriptor: the dataset type (SparkplugSrc / SessionLimits / Union), its structure (source device, coverage window, or member list), and a DCAT-shaped content block pointing at the Data Access export (POST /v1/data/:uuid, application/zip, csv-per-device). Structure configs are read per request, so a union that grows while a run is live is always current.
  • GET /objects/:id/related?relationshipType=i3x:rel:has-component walks the dataset graph: union → members, session window → its source dataset, device stream → the device object in the main tree.
  • GET /objects/:id/history on a dataset answers 404 pointing at the content href. The i3X history shape (one metric per object, flat VQT array) cannot express a multi-metric dataset — recorded as TID L5, with the deliberate spec-breaking experiment split out to FPLUS-45.
  • New env var I3X_DATA_ACCESS_EXTERNAL_URL: the public Data Access base URL for content hrefs. Unset → descriptors carry no content block, everything else still works.

The store is deliberately separate from ObjectTree — the device pipeline (reactive refresh, UNS preservation, diffing) is untouched; APIv1 consults the store as a fallback on every object route. Dataset membership re-polls on a timer for now, same as devices did before the L1 notify work.

How to test

  • npx jest — 350 tests, 14 suites (8 new in test/datasets.test.ts covering load/filtering, all three descriptor shapes, fresh union reads, HasComponent walks, and the no-URL case).
  • Live: deploy with I3X_DATA_ACCESS_EXTERNAL_URL set, then GET /v1/objects/bf62a1c3-8afe-4eaa-af79-12ae88a7384c (the demo CNC's machine dataset on fpd-ago) should answer 200 where it 404s today, and its /value content href should download the export.

🤖 Generated with Claude Code

AlexGodbehere and others added 3 commits August 20, 2026 11:58
Datasets are ConfigDB objects (Dataset class + one structure app per
type), so the store builds its object set from three list calls and
class membership, re-polled on a timer. Each dataset sits under a
synthetic Datasets folder with a shared Dataset ObjectType carrying a
real schema. /value returns a descriptor: the dataset type, its
structure, and a DCAT-shaped content block with the Data Access export
href. Structure configs are read per request so a growing union stays
current. HasComponent walks union -> members, session -> source
dataset, device stream -> device (via an injected resolver).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MtATkkjtiFF8L6z98NFD8A
Objects, types, list, value, related and history all fall back to the
DatasetStore. Dataset history answers 404 pointing at the content href
in the value descriptor - the i3X history shape cannot express a
multi-metric dataset. related routes become async for the store's
per-request ConfigDB reads. I3X_DATA_ACCESS_EXTERNAL_URL configures
the public export base; unset means descriptors carry no content
block.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MtATkkjtiFF8L6z98NFD8A
@AlexGodbehere

Copy link
Copy Markdown
Contributor Author

Review walkthrough

Three commits, reviewed in order.

1. lib/datasets.ts + lib/constants.ts + test/datasets.test.ts — the DatasetStore

Start with the header comment in datasets.ts; it states the whole design. The load path (load()) is four ConfigDB calls — class members plus one list_configs per structure app — and builds the object map with an atomic swap, the same discipline as ObjectTree. Note what it does not do: no per-dataset config fetches at load (745 datasets on fpd-ago today), and no reactive watch yet — a 60s re-poll, matching where devices were before the L1 notify work.

getValue() is the substantive method: it reads the structure config fresh on every request (a union grows while a run is live) and assembles the descriptor. The content block only appears when a Data Access URL is configured. getRelated() handles the three component semantics: union → members, session → source dataset, device stream → the device, the last resolved through the injected resolve callback so the store never reaches into ObjectTree directly.

The ConfigDB UUIDs in constants.ts are copied from acs-data-access/lib/constants.js and were verified against the live fpd-ago entries before writing the parser (the three structure shapes in the tests are real cluster data).

2. lib/api-v1.ts + lib/routes.ts + bin/api.ts — wiring

The pattern throughout is fallback: lookupObject/lookupObjectType try the tree first, then the store, so nothing changes for existing objects. Watch two behavioural changes: get_object_related and related_objects become async (the store reads the ConfigDB per request) — both are now wrapped in asyncHandler, and the sync objectTree path inside them is unchanged. And both history endpoints now answer 404 for a dataset with a message pointing at the content href; that is deliberate, not a gap (see the TID entry in commit 3).

Subscriptions intentionally do not know about datasets: registering a dataset elementId reports 404 in the bulk envelope, which is honest — there is no value stream behind it.

3. Docs

design.md gains a datasets section; to-improve.md gains L5, recording that the i3X history shape cannot express a multi-metric dataset. L5 is the working-group evidence the FPLUS-45 experiment builds on.

What to poke at

  • The 404-on-history choice: an empty values: [] would also be defensible; 404-with-pointer was chosen so a consumer can't mistake "no shape for this" for "no data".
  • The synthetic Datasets folder uses typeElementId: "isa95-level" — reusing the existing synthetic container type rather than minting another. Shout if that reads as abuse.
  • pollInterval default of 60s: fine for the demo, should move to the notify-v2 pipeline like L1 eventually.

@AlexGodbehere
AlexGodbehere marked this pull request as draft August 27, 2026 08:34
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.

1 participant