diff --git a/docs/README.md b/docs/README.md index 32dd32f16..6a99734f1 100644 --- a/docs/README.md +++ b/docs/README.md @@ -61,6 +61,8 @@ For developers of MediaWiki extensions that build on NeoWiki. an edit * [Revision policy](extending/revision-policy.md) — which revision NeoWiki publishes, for approval extensions * [Edit notices](extending/edit-notices.md) — a message before a user edits a Subject +* [Subject Sources](extending/subject-sources.md) — Subjects and Schemas supplied from outside this wiki's + revision slots * [Graph Database Backends](extending/graph-database-backends.md) — a store of your own that NeoWiki keeps in sync * [Using NeoWiki from PHP](extending/php.md) — read Subjects and run Cypher from hooks and special pages * [Using NeoWiki from JavaScript](extending/javascript.md) — the public JS API, displaying values, mounting Vue diff --git a/docs/adr/023-subject-sources.md b/docs/adr/023-subject-sources.md index a7d8d6e1e..83b068dad 100644 --- a/docs/adr/023-subject-sources.md +++ b/docs/adr/023-subject-sources.md @@ -2,7 +2,8 @@ Date: 2026-06-22 -Status: Accepted (2026-07-06) +Status: Accepted (2026-07-06). The Source contract section was amended 2026-08-07, resolving the "Source interface +contract" open question. Feedback remains welcome on the **Open questions** section below. @@ -25,6 +26,35 @@ store, another NeoWiki, or an external system, can also supply Subjects. A **Sou Source, which is the authority for its Subjects' capabilities, identity, and schema resolution. A wiki farm is simply more registered Sources. +#### The Source contract + +A Source answers for five things and no more: + +- **Fetch by id**, one Subject or a whole list. A list is one call, so a Source that can answer it in one round trip does. +- **Schema resolution by name**, because a Subject's Schema is resolved through the Schema's own Source. +- **Editability**, the one capability the model varies. +- **localId validation**, so the global id-format check can be source-delegated: the nanoid grammar is the local + Source's rule, not a rule about ids. Unconsulted so far — see Identity below. +- **A base URI**, the IRI prefix its Subjects are named under, which is also its RDF prefix entry. + +On top of those five it carries one obligation rather than a capability: **a Source vouches for what it returns**. +Everything it hands back is readable by every reader of this wiki, because a sourced Subject has no page here to +authorize against and NeoWiki performs no per-user authorization on it. A Source over data with restrictions of +its own must serve only the unrestricted part — a question it answers without knowing who is asking. The accepted +limitation: sourced data has no per-user granularity — [ADR 27](027-access-control.md) records this as its +source-attested read class; lifting it would be an additive context on this contract, not a redesign. + +Two absences are deliberate. There is **no write method**: write-back is end-of-roadmap (below), and a stub for it +would be a contract nobody can implement against, so it arrives with the feature. And **Sources take no part at query +time** — materialisation is the only gate on queryability, so a query never consults a Source. + +Editability is likewise stated ahead of its use: nothing reads it until sourced Subjects render. It belongs in the +frozen contract because a Source author must answer it from the start. + +Availability is not part of the contract either: a Source that cannot reach its store answers as though the Subject or +Schema is absent. Together with an unregistered source key resolving to nothing, a Subject from a Source this wiki +lacks degrades wherever it is read rather than breaking the page. + ### A source decides editability The distinction: @@ -52,8 +82,10 @@ they stay Page Properties. A Subject's id is a pair `(source, localId)`. The existing `SubjectId` ([ADR 14](014-improved-id-format.md)) is widened to carry a source, defaulting to local — a bare nanoid still means a local Subject. `localId` is opaque outside its -Source (a nanoid locally; a page id or a remote id / URI elsewhere); each Source owns its grammar, validation, and -minting. The pair is the canonical **reference** form (relation targets, view ids, fetch); IRI and CURIE forms are +Source (a nanoid locally; a page id or a remote id / URI elsewhere), and each Source owns its grammar and minting. +Validation is meant to follow: `Source::isValidLocalId()` is the seam for it, but nothing consults it yet — a +foreign localId is checked only against the shared serialization grammar, so a Source with a narrower rule of its +own is not yet held to it. The pair is the canonical **reference** form (relation targets, view ids, fetch); IRI and CURIE forms are projections of it, with the source-to-base-URI map doubling as the RDF prefix map. ADR 14's fixed-length and time-sortable guarantees hold only for local nanoids, not for arbitrary `localId`s. @@ -70,6 +102,12 @@ and a schema's source is independent of the subject's source. Rendering a source schema through *its* Source, which may differ from the subject's. When a schema cannot be resolved — a foreign, offline, or removed schema — rendering degrades gracefully rather than breaking the page. +Unlike a Subject id, a schema reference is not written as one string. A local schema name is a page title and may +itself contain a colon (`ISO:9001`), so a qualified string would be ambiguous with names that were legal before +Sources existed, and would read them back wrongly. A stored reference is therefore either a plain name, always +local, or an object carrying source and name ([Schema format](../api/schema-format.md#schema-references)). A +qualified `source:name` string survives only as a one-way rendering for people. + ## Consequences - One model spans local data, on-wiki SMW/Wikibase adoption, cross-wiki farm metadata, and external/federated data, @@ -89,7 +127,6 @@ Deferred and/or still being designed; consortium feedback is expected here. and [planning/OntologyMapping.md](../planning/OntologyMapping.md). - **Editing sourced Subjects (write-back)** — end-of-roadmap. How useful? Things like editing Wikibase Items via NeoWiki UI, or editing data from a remote NeoWiki -- **The Source interface contract** for by-id and query resolution. ## Alternatives Considered diff --git a/docs/adr/027-access-control.md b/docs/adr/027-access-control.md index cab4b7607..4831b16ac 100644 --- a/docs/adr/027-access-control.md +++ b/docs/adr/027-access-control.md @@ -2,7 +2,8 @@ Date: 2026-07-22 -Status: Accepted (2026-09-02) +Status: Accepted (2026-09-02). Amended 2026-09-11, resolving the "Cross-wiki subject display" open decision with a +fifth read class, source-attested (see Decision). ## Context @@ -70,15 +71,19 @@ Constraints the model rests on: - **Projections and dumps are generated without permission checks.** We may add such support later, enabling a public projection (a public store with a public query endpoint, holding no restricted content) alongside a private one that includes restricted content. +- **Source-attested reads are not gated per user.** A Subject or Schema supplied by a registered Source + ([ADR 23](023-subject-sources.md)) has no page on this wiki to authorize against. The Source vouches instead: + everything it returns is readable by everyone who may read this wiki at all, so the read surfaces serve it without + a per-row check, and a Source over data with restrictions of its own serves only the unrestricted part — a + question it answers without knowing who is asking. Read surfaces decide by the id's Source, never by whether a + page was found; local Subjects keep the page-attributable gate. Denied and absent are the same thing by + construction: what a Source does not vouch, it does not return. Sourced data therefore has no per-user + granularity; if that is ever demanded, it is an additive optional context on the Source contract, not a redesign. ## Open decisions These decisions remain open at acceptance; each is deferred to the tracking issue named with it. -- **Cross-wiki subject display.** Rendering a subject from another wiki goes through REST, not Cypher, so query-side - scoping does not cover it. Deferred to [#1341](https://github.com/ProfessionalWiki/NeoWiki/issues/1341): the - check and the degradation behavior when the schema or subject is not accessible. Relates to - [ADR 23](023-subject-sources.md). - **Default grant of `neowiki-query`.** The right is granted to `*` by default. Deferred to [#1342](https://github.com/ProfessionalWiki/NeoWiki/issues/1342): whether the default changes, and how deployments with restricted content are expected to configure it. @@ -91,8 +96,9 @@ These decisions remain open at acceptance; each is deferred to the tracking issu ## Consequences - Every new surface that exposes NeoWiki data must be classified: page-attributable (per-row gate), raw query - (whole-store semantics), projection/dump (no permission checks), or parse-time (parsing user's authority, - output keyed by access class). There is no unclassified option. + (whole-store semantics), projection/dump (no permission checks), parse-time (parsing user's authority, output + keyed by access class), or source-attested (the Source vouches, no per-row gate). There is no unclassified + option. - A page that reads Subjects or runs queries at parse time holds one parser-cache entry per access class among its viewers. Current-revision views of other pages are unaffected; old-revision views are keyed per class wiki-wide, because core's revision-output cache keys on every cache-varying option rather than the @@ -134,4 +140,6 @@ These decisions remain open at acceptance; each is deferred to the tracking issu - [rest-api.md Permissions](../api/rest-api.md), [query-api.md Permissions](../api/query-api.md), [graph-model](../api/graph-model.md) - Issues: [#1046](https://github.com/ProfessionalWiki/NeoWiki/issues/1046) (per-page read enforcement), - [#350](https://github.com/ProfessionalWiki/NeoWiki/issues/350) (slot-level access) + [#350](https://github.com/ProfessionalWiki/NeoWiki/issues/350) (slot-level access), + [#1341](https://github.com/ProfessionalWiki/NeoWiki/issues/1341) (cross-wiki subject display, resolved by the + source-attested class) diff --git a/docs/api/graph-model.md b/docs/api/graph-model.md index 30149da58..aa1638f58 100644 --- a/docs/api/graph-model.md +++ b/docs/api/graph-model.md @@ -53,13 +53,16 @@ Schema (e.g. `:Subject:Person`, `:Subject:Company`). The Schema label changes if | Property | Neo4j Type | Description | |----------|------------|-------------| -| `id` | string | Subject ID, 15 characters starting with `s` (unique) | +| `id` | string | ID of a Subject of this wiki, 15 characters starting with `s` (unique) | | `name` | string | Subject label, where it has one (see below) | | `wiki_id` | string | [MediaWiki Wiki ID](https://www.mediawiki.org/wiki/Manual:Wiki_ID) of the wiki that owns the Subject | Unlike page ids, Subject ids are globally unique nanoids ([ADR 14](../adr/014-improved-id-format.md)), so a Subject's identity is its `id` alone. The `wiki_id` is carried only for per-wiki query filtering in a shared graph. +Only Subjects of this wiki are projected, so `id` is always the bare form, never the `sourceKey:localId` form of a +Subject from another Source ([ADR 23](../adr/023-subject-sources.md)). See [Subject format](subject-format.md#ids). + A Subject need not have a label ([ADR 31](../adr/031-optional-subject-labels.md)). One with no label carries `name` only as its page's Main Subject, where the value is the prefixed page title and a page move keeps it current. A query that must name every Subject can fall back to the Schema label: @@ -138,6 +141,8 @@ backtick-escaped. When a Subject is removed while other Subjects still reference it, its node is kept as a [stub](#stub-subject-nodes) for as long as those references last. +Relation edges to Subjects from another Source ([ADR 23](../adr/023-subject-sources.md)) are not projected yet. + ## Constraints Two node uniqueness constraints apply: `(wiki_id, id)` on `:Page` nodes diff --git a/docs/api/rest-api.md b/docs/api/rest-api.md index 296c1b6fd..c3bb285db 100644 --- a/docs/api/rest-api.md +++ b/docs/api/rest-api.md @@ -47,7 +47,8 @@ The graph-store endpoints are gated by the `neowiki-admin` right. ### Subjects Read, change, and validate Subjects. New Subjects are created on a page — see -[Pages and Subjects](#pages-and-subjects). For the body shape, see [Subject format](subject-format.md). +[Pages and Subjects](#pages-and-subjects). For the body shape, see [Subject format](subject-format.md); for the form +of a `{subjectId}`, see [IDs](subject-format.md#ids). | Endpoint | Description | |---|---| diff --git a/docs/api/schema-format.md b/docs/api/schema-format.md index 326bcdaac..4d069b571 100644 --- a/docs/api/schema-format.md +++ b/docs/api/schema-format.md @@ -28,6 +28,25 @@ Per-type value constraints (`options`, ranges, string formats, `uniqueItems`) ar | `description` | string | No | Human-readable description of the schema | | `propertyDefinitions` | object | Yes | Map of property names to property definition objects | +## Schema references + +Wherever a Schema is named — a Subject's [`schema`](subject-format.md#subject-object) field, a relation property's +`targetSchema` — the value is a reference. + +A string names a Schema of this wiki by its page title in the Schema namespace. + +A Schema from another Source ([ADR 023](../adr/023-subject-sources.md)) is an object instead: + +```json +{ "source": "otherwiki", "name": "Company" } +``` + +`source` is a source key, written as for a [Subject ID](subject-format.md#ids). Naming this wiki's own Source is the +same reference as the bare name, and is stored as the bare name. + +A reference to a Source this wiki does not have resolves to no Schema, reported as +[`schema-not-found`](validation-codes.md#schema-not-found). + ## Property Definition Every property definition carries the common fields below plus the type-specific fields for its `type`. @@ -160,7 +179,7 @@ References to other Subjects. | Field | Type | Required | Default | Description | |-------|------|----------|---------|-------------| | `relation` | string | Yes | - | The relation type name | -| `targetSchema` | string | Yes | - | Name of the Schema that target Subjects must follow | +| `targetSchema` | string or object | Yes | - | [Reference](#schema-references) to the Schema that target Subjects must follow | | `multiple` | boolean or object | No | `false` | Allow multiple relations | ```json diff --git a/docs/api/subject-format.md b/docs/api/subject-format.md index b97c4c4e6..4660aab2c 100644 --- a/docs/api/subject-format.md +++ b/docs/api/subject-format.md @@ -44,7 +44,7 @@ the main one. | Field | Type | Required | Description | |-------|------|----------|-------------| | `label` | string | No | Human-readable label for the Subject. Omitted when the Subject has none. | -| `schema` | string | Yes | Name of the Schema the Subject follows (a page in the Schema namespace). | +| `schema` | string or object | Yes | [Reference](schema-format.md#schema-references) to the Schema the Subject follows. A string for a Schema of this wiki: its page title in the Schema namespace. | | `statements` | object | No | Map of property name to [Statement object](#statement-object). Omitted when the Subject has none. | A property mapped to `null` instead of a Statement object is skipped when the JSON is read. @@ -122,6 +122,17 @@ Subject and Relation IDs are 15-character nanoid-style strings, lexicographicall Subject IDs start with `s` (`s1demo5sssssss1`), Relation IDs with `r` (`r1demo5rrrrrrr1`). See [ADR 014](../adr/014-improved-id-format.md). +A Subject from another Source ([ADR 023](../adr/023-subject-sources.md)) is identified by `sourceKey:localId`, split +at the first colon — `wikibase:Q42`, `otherwiki:s1demo5sssssss1`. The source key is a letter followed by up to 63 +letters, digits, underscores or hyphens. The local ID is whatever that Source calls the Subject, up to 256 characters +of `A-Z a-z 0-9` and `. _ ~ - : @ ! $ ( ) * + , ; =` — further colons included. A Subject of this wiki is always +written bare, never `thisWikiId:s1demo5sssssss1`, and the two forms name the same Subject wherever both are accepted. + +The keys of a page's `subjects` map are these bare local IDs: a Subject from another Source is fetched from that +Source and is never stored in a page's content. An entry keyed otherwise is skipped when the page is read. + +The 15-character grammar, and the creation-time sorting that comes with it, hold for this wiki's Subjects only. + ## REST API ### Reading Subjects diff --git a/docs/api/validation-codes.md b/docs/api/validation-codes.md index a769af093..a0e70d396 100644 --- a/docs/api/validation-codes.md +++ b/docs/api/validation-codes.md @@ -158,8 +158,10 @@ so the Subject stays saveable. ### `schema-not-found` The Subject's Schema cannot be loaded — usually deleted or renamed since the Subject -was created, or the Subject was created or imported referencing a Schema that does not (yet) exist. -The write proceeds and reports the violation; creating or renaming the Schema page resolves it. +was created, or the Subject was created or imported referencing a Schema that does not (yet) exist. A +[reference](schema-format.md#schema-references) to a Source this wiki does not have reports the same. +The write proceeds and reports the violation; creating or renaming the Schema page, or registering the +missing Source, resolves it. Subject-level: `propertyName` is `null`. Returned by the update dry-run and both write endpoints. The create dry-run @@ -186,11 +188,30 @@ legitimately mint the target later. `args`: `[targetId]`. `valuePartIndex`: the offending target. `severity`: `warning` (fixed). +### `relation-target-unresolvable-source` + +On `relation` properties. The relation targets a [Subject ID](subject-format.md#ids) whose source key +names a Source this wiki has not registered, so the target cannot be reached at all. Reported instead of +[`relation-target-not-found`](#relation-target-not-found), which is about a Source that could answer and +did not. + +Only a violation the edit introduces blocks it, so a Subject that already carries such a target stays +editable. + +It is also the one code that is not Schema-scoped: it is reported for every relation value on the +Subject, including a Statement the Schema does not declare and a Subject whose Schema cannot be +loaded at all. Such a target would be unreadable from the moment it is written, which no later +Schema change fixes. + +`args`: `[targetId]`. `valuePartIndex`: the offending target. `severity`: `error` (fixed). + ## Out-of-schema Statements A Statement whose property is not declared on the current Schema is ignored — no violation. This is schema-drift tolerance: a property may have been removed from the Schema while Subjects still carry -old Statements. +old Statements. The one exception is +[`relation-target-unresolvable-source`](#relation-target-unresolvable-source), reported even for +Statements the Schema does not declare. ## Adding a new validation code diff --git a/docs/authoring/lua-api.md b/docs/authoring/lua-api.md index 071dc88bf..20f7faf64 100644 --- a/docs/authoring/lua-api.md +++ b/docs/authoring/lua-api.md @@ -130,7 +130,7 @@ Returns the full data of any Subject by its ID, regardless of which page it live | Parameter | Type | Description | |-----------|------|-------------| -| `subjectId` | string | Required. A Subject ID. | +| `subjectId` | string | Required. A Subject ID: 15 characters starting with `s` for a Subject of this wiki, or `sourceKey:localId` for one from another [Source](../api/subject-format.md#ids). | #### Returns @@ -141,6 +141,7 @@ with that ID, the ID is malformed, or its page is not readable (see [Permissions ```lua local subject = nw.getSubject('s1abc5def6ghi78') +local sourced = nw.getSubject('otherwiki:s1abc5def6ghi78') ``` ### `nw.getChildSubjects(pageName)` @@ -284,13 +285,14 @@ Returns a Schema as a Lua table so a module can inspect it at runtime. | Parameter | Type | Description | |-----------|------|-------------| -| `name` | string | Required. The Schema name (e.g. `'Company'`). | +| `name` | string or table | Required. A Schema name (e.g. `'Company'`), or a Subject's `schema` field, which is a table for a Schema from another Source. | #### Returns A Schema table, or `nil` if no Schema with that name exists or its page is not readable (see -[Permissions](#permissions)). An empty or whitespace-only `name` and the reserved names `page` and -`subject` also return `nil`. Guard with `if schema then`. +[Permissions](#permissions)). An empty or whitespace-only `name`, a table that is not a Schema +reference, a reference to a Source this wiki does not have, and the reserved names `page` and +`subject` all return `nil`. Guard with `if schema then`. Top-level fields: @@ -395,6 +397,9 @@ Notes: name when it is the page's Main Subject, and its Schema name otherwise. `storedLabel` carries the stored value and is `nil` when the Subject has none. The REST API splits these the other way: `label` is the stored value, `displayName` the display name. +- `schema` is a Schema name for a Schema of this wiki, and a table `{ source = ..., name = ... }` for + one from another Source. [`nw.getSchema`](#nwgetschemaname) takes either, so + `nw.getSchema( subject.schema )` works whichever it is. - A Subject fetched with [`nw.getSubject`](#nwgetsubjectsubjectid) comes without its page, so a label-less Main Subject is named after its Schema there. - `statements` is keyed by property name. `values` within each statement is 1-indexed. diff --git a/docs/authoring/parser-functions.md b/docs/authoring/parser-functions.md index 4b415cad1..a8a429a2a 100644 --- a/docs/authoring/parser-functions.md +++ b/docs/authoring/parser-functions.md @@ -47,7 +47,7 @@ and how. | Parameter | Description | |-----------|-------------| -| `` (positional) | Subject ID to render. Defaults to the current page's Main Subject. | +| `` (positional) | Subject ID to render: 15 characters starting with `s` for a Subject of this wiki, or `sourceKey:localId` for one from another [Source](../api/subject-format.md#ids). Defaults to the current page's Main Subject. | | `subject=` | Named alternative to the positional form. Cannot be combined with the positional form. | | `layout=` | Layout to apply. Without one, all properties are shown in schema order. | @@ -89,7 +89,7 @@ Returns the value of a single property from a Subject, formatted as a string. |-----------|-------------| | `propertyName` (positional) | The name of the property to read. Required. | | `page` | Read from the Main Subject of the named page. Defaults to the current page. Ignored when `subject` is also passed. | -| `subject` | Read from the Subject with the given ID. Takes precedence over `page`. | +| `subject` | Read from the Subject with the given ID, bare or `sourceKey:localId` (see [`{{#view}}`](#view)). Takes precedence over `page`. | | `separator` | Separator for multi-valued properties. Defaults to `, `. | ### Output by property type diff --git a/docs/extending/extending.md b/docs/extending/extending.md index fb10981d4..ad6ccb363 100644 --- a/docs/extending/extending.md +++ b/docs/extending/extending.md @@ -25,6 +25,8 @@ Contribute to NeoWiki: - [Revision policy](revision-policy.md) — which revision of a page NeoWiki publishes, when your extension decides what readers see. - [Edit notices](edit-notices.md) — a message shown before a user edits a Subject. +- [Subject Sources](subject-sources.md) — Subjects and Schemas from somewhere other than this wiki's revision + slots; they are read-only. - [Graph Database Backends](graph-database-backends.md) — a store of your own that NeoWiki keeps in sync; it gets no query surface of its own yet. diff --git a/docs/extending/graph-database-backends.md b/docs/extending/graph-database-backends.md index dfab5ea57..36f7d3a2d 100644 --- a/docs/extending/graph-database-backends.md +++ b/docs/extending/graph-database-backends.md @@ -1,6 +1,6 @@ --- title: Graph Database Backends -order: 7 +order: 8 --- # Graph Database Backends diff --git a/docs/extending/javascript.md b/docs/extending/javascript.md index 352a9a46e..47e145e34 100644 --- a/docs/extending/javascript.md +++ b/docs/extending/javascript.md @@ -1,6 +1,6 @@ --- title: Using NeoWiki from JavaScript -order: 9 +order: 10 --- # Using NeoWiki from JavaScript diff --git a/docs/extending/php.md b/docs/extending/php.md index 6e6b77a3a..1fef70491 100644 --- a/docs/extending/php.md +++ b/docs/extending/php.md @@ -1,6 +1,6 @@ --- title: Using NeoWiki from PHP -order: 8 +order: 9 --- # Using NeoWiki from PHP diff --git a/docs/extending/subject-sources.md b/docs/extending/subject-sources.md new file mode 100644 index 000000000..5c65abcbf --- /dev/null +++ b/docs/extending/subject-sources.md @@ -0,0 +1,39 @@ +--- +title: Subject Sources +order: 7 +--- +# Subject Sources + +A Source supplies Subjects and Schemas from somewhere other than this wiki's revision slots — another wiki of a +farm, an on-wiki SMW or Wikibase store, a remote instance. Implement `Source` and register it with +`NeoWikiRegistrar::addSource()`: + +```php +$registrar->addSource( 'myext_catalog', new CatalogSource() ); +``` + +The key is what a Subject id names before its colon (`myext_catalog:widget-7`), so it identifies your Source +across runs and installs: pick a stable one and namespace it to your extension. It must be a letter followed by up +to 63 letters, digits, underscores or hyphens; anything else throws. Registering a key already in use replaces that +Source. This wiki's own Source is registered under its +[Wiki ID](https://www.mediawiki.org/wiki/Manual:Wiki_ID) — that key is what a bare Subject id resolves to, and it +cannot be taken over. + +Registration runs under an [early hook](extending.md#backend-registration) on every request, whether or not +anything reads a Subject. If building your Source costs anything — a client, a connection, a file read — pass a +closure returning it instead of the Source itself; the closure is called at most once, the first time something +resolves that Source. + +Your Source answers fetch-by-id (one Subject or a list), Schema-by-name, whether its Subjects are editable, which +localIds it recognises, and its RDF base URI. It is never asked to run a query: a Subject becomes queryable by +being materialised in a graph store. + +A Source that cannot reach its store answers as though the Subject or Schema is absent, so a page that names it +degrades rather than breaking. Sourced Subjects are read-only, and rendering them in Views is not built yet, so +today a Source's Subjects are reachable by id and as relation targets. See +[ADR 23](../adr/023-subject-sources.md). + +Your Source vouches for everything it returns: NeoWiki serves it to every reader of the wiki and performs no +per-user authorization on it, because a sourced Subject has no page here to authorize against. If your data has +restrictions of its own, serve only the part that is unrestricted — a question you answer without knowing who is +asking. Per-user granularity for sourced data does not exist yet. diff --git a/docs/glossary.md b/docs/glossary.md index 9984b06af..30c35ed41 100644 --- a/docs/glossary.md +++ b/docs/glossary.md @@ -76,6 +76,14 @@ Each Relation has +## Source + +Where a Subject comes from ([ADR 23](adr/023-subject-sources.md)). A Source produces Subjects and resolves the Schemas +they use. The wiki itself is the default Source, and the one Subjects are created in; extensions register others, each +under a source key that prefixes the Subject IDs from it. + +Subjects from another Source are read-only. + ## Schema A Schema ([ADR 6](adr/006-schemas.md)) defines a type of Subject. Examples: Person, Company, Product, etc. diff --git a/docs/planning/SubjectSources.md b/docs/planning/SubjectSources.md index 4c96fa47d..bae004a04 100644 --- a/docs/planning/SubjectSources.md +++ b/docs/planning/SubjectSources.md @@ -94,7 +94,7 @@ A Subject id is a flat pair: **`(source, localId)`**. - **Local Subjects** are editable through the normal editor (subject to access rights) and versioned. - **Sourced Subjects** are **read-only** through NeoWiki for now. Writing back to a source (so an edit propagates to - the origin) is an **end-of-roadmap** option — kept open via an optional per-Source write capability, not built. + the origin) is an **end-of-roadmap** option — kept open, not built. Querying is independent of source: a Subject is Cypher-queryable once **materialised** in the graph (materialisation is mandatory for query). History follows the origin — the local slot is versioned; a sourced Subject's history, if @@ -225,7 +225,6 @@ id; rich chain-of-production provenance is a separate model. ### Still open -- **Source interface contract** for by-id and query (per-page being a deterministic by-id). - **Federation resolution** (fetch-at-read vs cache/materialise) and **shared-graph instance tagging** (the farm deliverable proper). - **History-page rendering** for non-history-correct sourced Subjects (show current values, or hide them?). @@ -236,9 +235,9 @@ id; rich chain-of-production provenance is a separate model. mechanism; the refresh-without-edit operation ([#889](https://github.com/ProfessionalWiki/NeoWiki/issues/889)); and multi-wiki node identity ([#905](https://github.com/ProfessionalWiki/NeoWiki/issues/905)) for per-wiki query filtering. Forward-compatible down-payments, not a separate system. -2. **Source foundation:** the `(source, localId)` identity, the Source registry/interface (by-id + query + - capabilities, including an optional write capability), and the local store refactored as the default `LocalSource`. - The single system everything else builds on. +2. **Source foundation:** the `(source, localId)` identity, the Source registry/interface (contract frozen in + [ADR 23](../adr/023-subject-sources.md): no query role, no write capability), and the local store refactored as + the default `LocalSource`. The single system everything else builds on. 3. **Source consumers (by demand, on the foundation):** sourced Subjects in Views (read-only); the on-wiki SMW/Wikibase source (read-only) — the easiest sourced case and an adoption/migration wedge; remote federation and RDF/IRI export (gated on ECHOLOT). diff --git a/extension.json b/extension.json index 1fed96864..2c7bd0ed2 100644 --- a/extension.json +++ b/extension.json @@ -517,6 +517,7 @@ "neowiki-field-unregistered-type", "neowiki-field-relation-target-schema-mismatch", "neowiki-field-relation-target-not-found", + "neowiki-field-relation-target-unresolvable-source", "neowiki-select-placeholder", "neowiki-select-no-results", "neowiki-select-unknown-option", diff --git a/i18n/en.json b/i18n/en.json index a270bfc79..c0ac14ba7 100644 --- a/i18n/en.json +++ b/i18n/en.json @@ -96,6 +96,7 @@ "neowiki-field-unregistered-type": "The type \"$1\" is not available on this wiki.", "neowiki-field-relation-target-schema-mismatch": "This relation must point to a subject using the \"$1\" schema, but the selected subject uses \"$2\".", "neowiki-field-relation-target-not-found": "The subject \"$1\" this relation points to could not be found.", + "neowiki-field-relation-target-unresolvable-source": "The subject \"$1\" this relation points to comes from a source this wiki does not have.", "neowiki-select-placeholder": "Select an option", "neowiki-select-no-results": "No matching options.", "neowiki-select-unknown-option": "(unknown option)", diff --git a/i18n/qqq.json b/i18n/qqq.json index a12bfe074..8e55537f6 100644 --- a/i18n/qqq.json +++ b/i18n/qqq.json @@ -256,6 +256,7 @@ "neowiki-field-max-length": "Validation error shown when a text value is longer than the property's maximum length. $1 is the maximum number of characters.", "neowiki-field-relation-target-schema-mismatch": "Validation error shown when a relation points to a subject whose schema is not the relation's declared target schema. $1 is the required target schema; $2 is the schema the target subject actually uses.", "neowiki-field-relation-target-not-found": "Validation warning shown when a relation points to a subject ID the wiki cannot resolve for this user, either because no such subject exists or because it lives on a page the user may not read. $1 is the target subject ID. Word it so it does not assert the subject is absent, which would be wrong in the second case. Non-blocking: the target may be created later (for example during import), mirroring wiki red links.", + "neowiki-field-relation-target-unresolvable-source": "Validation error shown when a relation points to a subject ID whose source key names a subject source this wiki has not registered. $1 is the target subject ID, in its \"sourceKey:localId\" form. Blocking: the reference cannot be resolved at all, so it is refused whether or not the wiki enforces validation.", "neowiki-select-placeholder": "Placeholder text shown in the select dropdown before any option is chosen.", "neowiki-select-no-results": "Message shown in the multi-select dropdown when the typed text does not match any available option.", "neowiki-field-invalid-number": "Validation error shown under a number field whose text cannot be interpreted as a number. Also shown as an error notification, titled with the property name, when an action is held because a number field holds such text.", diff --git a/resources/ext.neowiki/src/components/SchemaEditor/Property/RelationAttributesEditor.vue b/resources/ext.neowiki/src/components/SchemaEditor/Property/RelationAttributesEditor.vue index f250c553f..1812be6c2 100644 --- a/resources/ext.neowiki/src/components/SchemaEditor/Property/RelationAttributesEditor.vue +++ b/resources/ext.neowiki/src/components/SchemaEditor/Property/RelationAttributesEditor.vue @@ -25,10 +25,17 @@ {{ $i18n( 'neowiki-property-editor-target-schema' ).text() }} +