Skip to content

The flat bundle and the holon name the same things differently #1397

Description

@jfrench9

What

ref/serialization.md states the invariant that the flat JSON-LD bundle and the holon "are two forms of one Dataset and name the same things the same way". It is violated in two places, by the same mechanism.

Concepts. Expanded to absolute IRIs, from the committed coffee-roaster sample:

library concept
  flat   https://robosystems.ai/taxonomy/rs-gaap/v1/Assets
  holon  https://robosystems.ai/taxonomy/rs-gaap/v1/Assets          ✓ same node

tenant concept
  flat   https://robosystems.ai/concept/driftline:InventoryRawMaterials
  holon  https://robosystems.ai/taxonomy/driftline/InventoryRawMaterials   ✗ two nodes

Five concepts in the SaaS demo, eight in the coffee one. The flat form's IRI also carries a colon inside a path segment, which no consumer will read as a QName.

Structures. The same report's equity statement, from the two forms:

flat   structureName / prefLabel = "rs-gaap — Statement of Changes in Equity — Roll Forward (Total)"
holon  structureName / prefLabel = "0004 - Statement - Statement of Changes in Equity"

The holon's writer sets both to the composed sort key, so the form name — Classified, Multi-step, Indirect, Roll Forward — is absent from the holon entirely. For a ledger report that is not decoration: it says which of several presentations of the same accounts this is. It is recoverable only from the role-URI slug (…/Equity-rollforward). There is no rs:roleDefinition or documentation term beside it. This is finding 3 of specs/xbrlkit/three-renderings-disagree.md, folded in here.

Why

rdf/jsonld.py::_concept_uri resolves a prefix through a static _PREFIX_NS map and falls through when it misses:

def _concept_uri(qname: str) -> URIRef:
  if ":" not in qname:
    return URIRef(f"https://robosystems.ai/concept/{qname}")
  prefix, local = qname.split(":", 1)
  ns = _PREFIX_NS.get(prefix)
  if ns is None:
    return URIRef(f"https://robosystems.ai/concept/{qname}")   # ← every tenant
  return URIRef(str(ns) + local)

A tenant namespace is https://robosystems.ai/taxonomy/<tenant>/, one per tenant and minted with it, so a static table can never list them — the rule has to be general. The holon path already is: xbrlkit's writer mints …/taxonomy/<tenant>/<local> and declares the prefix in the document's @context.

This is the third instance of one defect class. The same static-table assumption produced the label loss fixed in #1391 (Element.name unread for tenant concepts) and the bare-local-name display fixed in report-components#54 (qname() could not compact a tenant namespace). Both of those were renderer- or label-side; this one is identity.

The structure half has a different mechanism but the same shape: one writer composes a name the other doesn't, and neither carries the field the other has.

Scope - Affected

  • robosystems/operations/serialization/rdf/jsonld.py_concept_uri, and whether the emitted @context should declare the tenant prefix the way the holon's does
  • xbrlkit's holon writer — structureName / skos:prefLabel from the producer's own name, the composed definition under its own term, and the reader restoring both (this also removes the rs:structureOrder drift on round-trip: the reader takes structureName as the definition and the numeric prefix is gone)
  • examples/*/sample_output/ — one full five-set regeneration covers both halves
  • tests/operations/serialization/test_sample_artifact_invariants.pytest_both_forms_name_the_same_concepts is xfail(strict=True) on the two demos that author concepts; a structure-name invariant belongs beside it

Scope - Out of Scope

  • The XBRL 2.1 package's link:definition (finding 2 of the same spec). No reader: the flavor is write-only in the platform, nothing calls classify_network on a package, and the demos' only reader is Arelle checking structure. Stays parked in that spec with its own trigger.
  • report-components' display compaction — fixed in 0.6.0, a different layer.

Acceptance Criteria

  • A tenant concept expands to the same absolute IRI in both forms.
  • A structure reads with the same name in both forms, and the form qualifier survives into the holon.
  • test_both_forms_name_the_same_concepts passes with the xfail marker removed, and a structure-name invariant is added and passes.
  • A library concept's IRI and a filing's holon are unchanged (no churn where there is no tenant).
  • The flat bundle still conforms to SHACL; Arelle is unaffected (the XBRL arm excludes these notes anyway).

Notes

The concept half was found by the invariant gate built for #1393 — written to catch content drift, it surfaced a live defect on its first run.

Both halves want the same five-set sample regeneration, which is the expensive part (~2.5× the 10,372/10,552-line diff that two demos cost in #1395). That is the reason they are one issue and not two.

Worth deciding at the same time whether @context in the flat form should declare the tenant prefix. The holon does, which is why its driftline:X round-trips; without it the flat form would carry a long absolute IRI where the holon carries a compact one — same node, different ergonomics.

Related Issues

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions