Skip to content

Add the Source contract, registry, and LocalSource#1042

Draft
malberts wants to merge 4 commits into
subject-id-source-pairfrom
source-registry-and-local-source
Draft

Add the Source contract, registry, and LocalSource#1042
malberts wants to merge 4 commits into
subject-id-source-pairfrom
source-registry-and-local-source

Conversation

@malberts

Copy link
Copy Markdown
Contributor

For #993 (work order T2). Builds on #1005 and targets
its branch, so the diff shows only this track.

This PR freezes the Source interface contract that ADR 23 left open. The contract, its whys, and the
alternatives are recorded in docs/adr/027-source-interface-contract.md; the highlights:

  • The Source contract (Domain\Source): getSubject( SubjectId ), getSchema( SchemaName ),
    isEditable(), isValidLocalId( string ), getBaseUri(). Sources play no part at query time:
    queryability is materialisation into the graph (ADR 23). Schema resolution enters the contract now
    because interface additions break third-party implementers and T3 needs it.
  • SourceRegistry: byte-exact key comparison (two Linux MySQL/MariaDB dbnames can differ only in
    case, and the graph's wiki_id stores the wiki ID verbatim, so case-folding is foreclosed),
    LogicException on duplicate registration, bare ids map to the local source.
  • LocalSource wraps the existing local persistence behind lazily constructed lookups and returns
    the per-wiki RDF base (NeoWikiConfig->rdfBaseUri, from the native RDF projection) as its base URI.
  • Read-side routing only: the subjectLookup seams (GetSubjectQuery, GetPageSubjectsQuery,
    both SubjectResolver sites) resolve through the registry; write paths and the revision-scoped
    PointInTimeSubjectLookup stay direct, since sourced Subjects are read-only per ADR 23.
  • Registration point: NeoWikiRegistrar::addSource() in the NeoWikiRegistration hook, with core
    registering LocalSource under the wiki ID before the hook fires, so colliding extension
    registrations fail fast. The lazy construction wrappers (LazySubjectLookup, LazySchemaLookup)
    are load-bearing here: an eagerly built dependency chain fires the registration hook re-entrantly,
    and the hook would observe an empty registry (probe-verified in both process orders).
  • Unresolvable source keys degrade to not-found plus one logged warning, never fatally. REST GET
    keeps the pinned not-found contract (HTTP 200 with {"subject":null}).
  • Deliberately preserved: on a wiki with no graph backend, {{#neowiki_value}} and Lua keep
    failing loudly (GraphBackendNotConfiguredException is rethrown through SubjectResolver's
    catches); the softer degradation belongs to the Finish the graph database plugin system #895 track, not this one.
  • Deferred by design: no unused write-capability stub. Write-back, when designed, arrives as a
    WritableSource extends Source extension, which is non-breaking for implementers.

AI-authored — Claude Code, Fable 5 (max); standing directive from @malberts to complete the #993 tracks without blocking on inter-track human review, design calls delegated; diff not yet human-reviewed; TDD with watched failures, three adversarial AI review lenses with live hook-order and backendless probes, make ci and make ts-ci green locally, branch CI pending at posting time.

Production notes

Design, contract decisions, and review adjudication by Fable 5 (max); implementation and the fix
round by Opus 4.8 (max) subagents; three parallel Opus 4.8 (max) review lenses (contract
fidelity, runtime correctness, test quality) stood in for the usual pre-PR human review at
@malberts' direction. The correctness lens probe-confirmed the hook-ordering hazard and drove the
lazy-construction fix and the loud-backendless preservation described above.

malberts and others added 4 commits July 15, 2026 10:18
Introduce the Source interface (by-id Subject and Schema resolution,
editability, localId validation, and IRI base URI), a SourceRegistry
that maps source keys to Sources with fail-fast duplicate registration
and bare-id-to-local routing, and LocalSource wrapping the existing
local Subject and Schema lookups as the default editable Source.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Add SourceRoutingSubjectLookup, which resolves a Subject id through the
Source its key maps to and degrades an unregistered source key to
not-found plus one logged warning. Wire it into the read-side query and
resolver seams (GetSubjectQuery, GetPageSubjectsQuery, the neowiki_value
parser function, and the Lua library); write paths keep the local
repository since sourced Subjects are read-only, and revision-scoped
resolution stays local. Register the SourceRegistry on NeoWikiRegistrar
with addSource() so extensions contribute Sources through the
NeoWikiRegistration hook, with core's LocalSource seeded first.

The local lookup is wrapped in a LazySubjectLookup so building the
registry during extension registration does not require a graph backend,
keeping backend-less edit and parser-registration paths working. The
schema lookup is deferred the same way (LazySchemaLookup) because
LocalSource is built while the registry is being assembled, and a
dependency that fires the NeoWikiRegistration hook at that point would
hand handlers a still-empty registry, letting an extension claim the
local key before core registers LocalSource. SubjectResolver rethrows
GraphBackendNotConfiguredException instead of swallowing it, so a
missing graph backend stays loud for {{#neowiki_value}} and Lua rather
than degrading to silent nulls.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Record the frozen five-method Source interface, the SourceRegistry
semantics, and the unresolvable-source degradation as ADR 27. Add a
Sources section to the extending guide covering addSource, and a Source
entry to the glossary.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
ADR 027 stated T4 validates relation targets via isValidLocalId; the
shipped guard checks Source registration through the registry and does
not call isValidLocalId, which has no consumer yet. Also drops a
work-order token from the Source docblock that means nothing once merged.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
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