Files
ng-eventually/.project/concepts/app-contract/knowledge_what-an-iri-says-upstream.md
T

9.4 KiB

type, summary, last_checked
type summary last_checked
knowledge What NextGraph's own IRI vocabulary is, what this package writes instead, and why scaffolding triples and durable application data must not share a namespace 2026-08-21

What an IRI says upstream, and what ours says

Read at the source in nextgraph-rs (213338f6). Every claim points at a symbol or a module rather than a line, so git grep settles any doubt after the code moves. Two questions are answered here and they are not the same one: what does the target's vocabulary look like? and what does a triple this package writes promise about its own future?

Upstream defines exactly one namespace, and it is the engine's

NG_ONTOLOGY = "did:ng:x:ng#" (engine/verifier/src/types.rs) is the only vocabulary namespace anything upstream declares as a constant. Three terms sit beside it — NG_ONTOLOGY_ABOUT (#a), NG_ONTOLOGY_TITLE (#n), NG_ONTOLOGY_CLASS (#c) — a document's description, its title, its class.

Those three are not the whole namespace. The same did:ng:x:ng# carries engine protocol state, written as inline string literals rather than named constants: ng:site / ng:protected and their _inbox counterparts, which import_contact_from_qrcode writes into a contact document (engine/verifier/src/request_processor.rs), and ng:social_query_id / ng:social_query_forwarder / ng:social_query_started / ng:social_query_sparql, which the social-query machinery reads and writes (engine/verifier/src/inbox_processor.rs). So the namespace is engine-owned in a broader sense than "document metadata": it is where the engine keeps what it needs to keep.

Everything else that looks like a NextGraph vocabulary lives only in test fixtures: did:ng:x:contact#…, did:ng:x:core#… and did:ng:x:contact:class#… in sdk/rust/src/tests/big_contact_schema.json, contact_data.sparql, orm_create_patches.rs, orm_creation.rs. No engine code writes them. There is no registry, no parser, no stability promise behind any of them.

x is not an address — vocabulary and addresses share a scheme and nothing keeps them apart

The NURI grammar knows o v b c d a g h j k l n s t u and the bare did:ng:i — the regexes in engine/net/src/types.rs and every constructor of NuriV0 (engine/net/src/app_protocol.rs). Neither x nor z nor ex is among them: a vocabulary IRI is simply a string that starts like an address and matches no rule.

The fixtures make the point sharper. They also use did:ng:k:contact:tag#… and did:ng:k:contact:phoneNumber#… for controlled values — and k is in the grammar, as the key half of a :j:…:k:… or :c:…:k:… pair. The letter is already spent; the vocabulary form matches nothing anyway. Nobody arbitrated that collision because nobody had to: no registry exists to arbitrate it in.

did:ng:x:core#value is not "a field's value"

It is the payload slot of a reified value node, and reading it as a generic field accessor gets the model backwards. The shape, verifiable in sdk/rust/src/tests/contact_data.sparql:

  • the entity carries did:ng:x:contact#email → a value node (its own IRI);
  • the value node carries did:ng:x:core#value → the literal;
  • the value node also carries the qualifiers: core#source (where this value came from), core#type (an IRI such as did:ng:k:contact:phoneNumber#home), core#hidden, core#selected.

The field name lives in the parent's predicate, never in the value node. And the payload slot splits by RDF term kind rather than by field: core#value holds a literal, core#valueDateTime / core#valueDate a typed date, core#valueIRI an IRI. :class# (as in did:ng:x:contact:class#SocialContact) is a convention observed in exactly one vocabulary, the contact fixture.

There is no extension convention, and nothing upstream filters or reserves

No reserved segment, no procedure to claim one, no prohibition — nowhere in the tree. What the examples do instead is either PREFIX ex: <did:ng:z:> (sdk/js/orm/src/tests/shapes/shex/testShape.shex, the shex under sdk/js/examples/, sdk/rust/src/tests/video_schema.json) or plain web ontologies — vcard, foaf, schema.org, which the engine itself uses for a contact's name and email. One example spells did:ng:n:g:x:social:contact# (sdk/js/examples/react-ldo/src/.shapes/contact.shex and the generated files beside it), an older form matching neither the fixtures nor the grammar: the scheme is not self-consistent across the tree.

Nothing upstream filters or reserves a namespace. The engine stores and returns arbitrary IRIs. The single prefix-sensitive path is in engine/verifier/src/commits/transaction.rs, which matches NG_ONTOLOGY_ABOUT / NG_ONTOLOGY_TITLE among a commit's inserts and removes only to push a tab-info update when a title or description changed — and only for updates whose branch_type.is_header(). The triples stay in the store and stay queryable; nothing is hidden from a reader. Separation upstream is by BRANCH, not by prefix — a header branch is a different CRDT with its own topic, which is why upstream needs no reserved prefix at all.

The urn: strings that appear upstream are unit-test fixtures and nothing else: urn:g, urn:child:dg, urn:child:sg inside #[cfg(test)] mod tests in engine/verifier/src/orm/graph/utils.rs, and urn:test:… under sdk/rust/src/tests/. They are not vocabulary and imply nothing.

What this package writes

urn:ng-eventually:MACHINERY_NS (packages/polyfill/src/emulated-verifier/machinery.ts), in two families: urn:ng-eventually:shim:…, the compartments shared-wallet/account-registry.ts uses to emulate a repo's branches inside one document, and urn:ng-eventually:inbox:…, the deposit envelope and the deposit kinds emulated-verifier/inbox-dispatch.ts dispatches on.

Triples under it are dropped on read. isMachinerySubject is called from readUnion (packages/polyfill/src/surface/read-model.ts), which skips every row whose subject starts with the prefix before the caller ever sees it. By subject only — a machinery predicate on an ordinary subject comes straight through.

That filtering has no upstream counterpart: it is an invention, and the source annotates it as one (kind=invention level=none). It exists because this package has no branches, so a compartment upstream keeps in a separate CRDT has to live as a reserved subject in the same graph. Do not read our reservation as mirroring anything upstream — nothing there is filtered out of a document's own graph.

The index arm writes another package's namespace. urn:ng-helpers:index:field and urn:ng-helpers:index:value (packages/polyfill/src/emulated-verifier/index-deposit.ts) are the IRIs @ng-helpers/indexing already writes, kept letter for letter because renaming them would orphan every index already written. It is odd for this package to write a namespace it does not own. Recorded here as an oddity; it is not this leaf's to fix.

Two kinds of triple, two fates — the distinction this leaf exists for

Scaffolding is what this package writes to emulate what the engine will do: the branch compartments, the deposit envelope. It is transitory. It disappears at migration together with the read filter that hides it, and being invisible to a caller is exactly right for it. urn:ng-eventually: is its home.

Durable application data is what an application means: an index entry written into a public document, read by third parties, which must survive migration. It is deliberately not under urn:ng-eventually: — which is precisely why the index IRIs pass the read filter and reach the caller.

Naming them alike would say the wrong thing about both: it would promise the scaffolding a future it does not have, and it would put the durable data one widened filter away from disappearing. The test at every new IRI: does this triple die at migration, or does it have to outlive it? Scaffolding goes under urn:ng-eventually:; anything an application or a third party reads must not. This is the triple-level twin of knowledge_what-an-app-deletes-at-migration — there, the two fates of a published symbol; here, the two fates of a written triple.

What is settled, and what is a bet

Settled, read at the source. Nothing upstream reserves, filters or validates a namespace; the engine stores and returns arbitrary IRIs; there is no extension convention to comply with and no procedure for claiming a segment. Nothing upstream binds our choice, and nothing upstream breaks if we keep a urn: scheme.

A bet. That upstream will not later claim urn: or begin separating by prefix. Nothing says it will; nothing says it will not — and the absence of an implementation says nothing about what the target will do (rule_no-divergence-from-nextgraph). The exposure is small and it falls on the scaffolding side, which is the half designed to disappear.

A bet, and the more expensive one. Writing urn:ng-helpers: from here. Two packages now share one data format with no owner and no agreement about who may change it.

Not a bet — a divergence, if it were ever done. Putting our own terms under did:ng:x:…. That segment is the target's vocabulary space, did:ng:x:ng# is the engine's own, and x means nothing to the address grammar — so borrowing it would dress an invention in the target's vocabulary, which rule_no-divergence-from-nextgraph names as the most dangerous kind of all.