Skip to content

Develop - #358

Draft
namedgraph wants to merge 7 commits into
masterfrom
develop
Draft

Develop#358
namedgraph wants to merge 7 commits into
masterfrom
develop

Conversation

@namedgraph

Copy link
Copy Markdown
Member

No description provided.

namedgraph and others added 7 commits August 19, 2026 20:18
* Client-side constructor instantiation: one prototype per instance

An edited resource is one rdf:Description with N rdf:types, so its
constructor template must be one rdf:Description with those N types and
the union of their constructor properties - the template mirrors the
instance. /ns?forClass= minted a fresh bnode prototype per class, and
every consumer downstream assumed the single-prototype shape:
document-order-dependent property picks (the intermittently vanishing
app-settings Description field), parent-type match misses,
ldh:SetResourceID orphaning N-1 prototypes in EDIT flows, and
xs:anyURI? cardinality errors on multi-range predicates.

Instead of asking the server to construct, the client fetches the
constructor queries with one SPARQL SELECT (rdfs:subClassOf* closure,
DISTINCT - cycle-safe, shared constructors dedup) and expands the
CONSTRUCT templates onto a single instance:

- ldh:constructor-query builds the SELECT; ldh:construct-instance
  parses each sp:text with SPARQL.js (no execution - constructors must
  have an empty WHERE clause; others are skipped with a warning),
  substitutes ?this with one shared instance label, prefixes marker
  bnodes per constructor, collapses same-(predicate, range) duplicates,
  and serializes through ldh:triples-to-descriptions
- ldh:load-constructed-doc/set-constructed-doc rewired to the SELECT +
  instantiation; all EDIT/violation/CREATE chains covered unchanged
- ldh:construct-forClass split by use-when: SAXON keeps /ns?forClass=
  (SSR), SaxonJS instantiates client-side - covers the tunnel-less
  bs2:FormControl default (SSR EditMode, ldh:DocumentForm,
  ldh:AppSettingsForm)
- add-value flow tokenizes multi-type @typeof (was a latent 500)
- forClass cardinalities relaxed to xs:anyURI* where a predicate can
  have several ranges across the type set (spin:query - sp:Query vs
  sp:Select); bs2:FormControlTypeLabel iterates all ranges like its
  marker-bnode sibling
- GET-ns-constructors.sh pins the constructor SELECT server contract

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* Remove /ns?forClass= - constructor instantiation is client-side only

The client-side instantiation left server-side rendering as the last
consumer of /ns?forClass=. SSR's constructor controls were transient
paint - the client re-render replaces the form on load - so the SAXON
ldh:construct-forClass becomes an empty-document stub (the ac:construct
stub pattern): server-rendered edit forms show data properties, the
client supplies the constructor controls.

- BREAKING: Namespace no longer serves ?forClass= constructed
  instances; it serves SPARQL queries and the raw ontology graph only.
  Removes the Constructor usage and the unmapped-IRIException 500 on
  malformed class URIs
- GET-namespace-forClass-rdfs.sh removed with the feature; the
  constructor SELECT contract is covered by GET-ns-constructors.sh
- CacheInvalidationFilter's forClass ban stays: it serves the typeahead
  ?forClass= filtering on document URLs, not the removed construct
- $ac:forClass in client.xsl stays: the shadowed Web-Client templates
  compiled into the SEF still reference it

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* Read SPARQL.js parse output via JSON round-trip

SaxonJS does not marshal plain JS arrays for ixsl:get() access - the
'where' array came back as an empty sequence and the next ixsl:get()
failed with a cardinality error. Serialize the parse tree with
JSON.stringify + json-to-xml (the form.xsl SELECT-builder idiom) and
read the CONSTRUCT template as json:map elements instead.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* Keep violation machinery types out of the row-form instance type set

ldh:row-form-submit-violation swept rdf:types from every non-document
Description in the violation response, including the
spin:ConstraintViolation / http:Response ones. With per-class
prototypes that was harmless (junk classes just yielded no prototype),
but the union-typed single prototype inherits those types and then
fails bs2:FormControl's every-type-satisfies subset test against the
edited resource - constructor controls vanished from the violation
re-render. Filter the swept Descriptions with the same type list the
bs2:RowForm/bs2:Form suppression templates use.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* Single-source the violation machinery type list as $system-types

One global drives the bs2:RowForm/bs2:Form suppression matches and the
row-form violation type sweep, so the list cannot silently diverge.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* Drop base-uri override from the modal violation re-render

ldh:render-modal-form-violation forced base-uri to the violation
response's base URI, a carry-over from before the render dispatchers
gained the about-fallback (both from 197ab9c). In the creation/edit
flows the two values coincide, but in the app-settings flow the
subject is a urn: outside the response's HTTP space, so the override
flipped bs2:FormControl's $show-subject default and exposed the URI
control on constraint violations while the initial render kept it
hidden. The submit-handler context carries no base-uri, so the
dispatchers now fall back to $ctx('about') on the violation path
exactly as on the initial render.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
#359)

Remaining leftovers of the 197ab9c shared-chain refactor, sibling
fixes to the base-uri override drop:

- property-uris harvested from everything except the edited resource
  (inverted predicate, contradicting the function's own comment and
  ldh:load-edited-resource) - property labels degraded to the
  local-name fallback on modal violation re-renders while metadata was
  fetched for spin:/sh: machinery terms instead. Flip to
  @Rdf:about = $about.
- object-uris (modal + row-form chains) and the row-form property-uris
  swept the whole response body including the violation machinery
  Descriptions, causing junk label lookups. Exclude $system-types-typed
  Descriptions, mirroring the instance type harvest.
- the shared violation render-ctx stamped the Container/Item required
  function for all flows while the app-settings initial chain passes
  always-true; masked today by ldh:AppSettingsForm's hardcoded
  required=true(). Stamp 'required' per flow in the response handlers
  alongside 'render-fn', and correct the stale 'only used for
  Container/Item instances' comment that hid this.

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
* Self-federation: origin validators through the proxy, foreign write targets, federation suite

The federation test (First Principles ch. 18): one instance's client
browses, queries and writes against another, the wire carrying only
spec-terms. Most of the machinery already existed - the proxy forwards
arbitrary methods with bodies and delegated agent identity, panes carry
data-base/data-endpoint from forwarded Link headers so ldt:base() and
sd:endpoint() (and with them the ns-bound constructor SELECT and
metadata fetches) already resolve against the remote app on remote
panes. What was missing:

- the proxy's Model branch stamped re-serialization validators, so a
  client editing a proxied document sent an If-Match the origin had
  never issued - every cross-instance write 412ed. The origin's
  ETag/Last-Modified now forward (replacing locally stamped values);
  the ResultSet branch keeps builder validators.
- the Add-data flow rejected foreign targets with a guard written when
  the remote end refused proxied writes; with delegation the target
  instance's ACL is the arbiter, so the guard is gone and a remote 403
  surfaces as the form error. Import-ontology keeps its local-target
  guard for a different reason, now stated: the constructor derivation
  is scoped to the local /sparql endpoint.
- nothing proved any of it. New http-tests/federation/ suite runs the
  browse/query/write triad between the root and test dataspaces at two
  origins: Link-header endpoint discovery, SPARQL against the
  discovered endpoint, constructor SELECT against the remote ns,
  ETag parity between proxied and direct reads, a graph-scoped SPARQL
  Update PATCH under the origin's If-Match (stale precondition 412s),
  and the unauthenticated negative. The dataspaces share a triplestore
  below the HTTP surface in the test config; the meeting surface
  exercised is the full HTTP stack.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* Forward conditional request headers through the proxy; fix federation negative

CI caught two gaps in the cross-instance write path:

- the proxy set only Accept and User-Agent on the outbound request, so
  If-Match never reached the origin - a proxied preconditioned write
  ran unconditionally (stale If-Match got 204 instead of 412, losing
  optimistic concurrency). Forward the conditional request headers
  (If-Match, If-None-Match, If-Modified-Since, If-Unmodified-Since);
  identity stays delegated (not via Authorization/Cookie) and Range is
  excluded (byte offsets don't survive Model re-serialization).

- the negative test used an anonymous request, which is not actually
  anonymous at the origin: a proxied request with no user certificate
  rides the server's own credential on the proxy->origin leg. Replace
  it with a delegated write by the signed-up agent, which is a known
  federation identity not authorized on B - so B's ACL refuses it,
  which is the property the test means to assert. Both PATCH tests now
  echo the actual status codes (debug-first).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* Relay proxied 4xx/5xx error responses verbatim

The header-forwarding fix made the origin actually evaluate proxied
write preconditions - and exposed that the proxy could not relay the
resulting error responses. A 4xx/5xx from the origin carries a
diagnostic body; getResponse routed it through the Model branch, where
readEntity(Model.class) throws on the non-RDF/error body, and the catch
turned it into 502. So a proxied stale If-Match (origin 412) and an
unauthorized delegated write (origin 403) both surfaced as 502.

Relay CLIENT_ERROR/SERVER_ERROR responses raw - status, Content-Type
and origin validators forwarded, body piped - skipping the
re-serialization branches that only make sense for negotiable success
representations. Existing proxy error tests assert status only and are
unaffected; the acl#agent leak checks are on direct (non-proxied)
requests.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* Match Accept on the conditional PATCH to the read's variant

The valid If-Match returned 412 because LDH ETags are variant-specific
(getVariantEntityTag folds the negotiated media type into the tag): the
test read the ETag with Accept: application/rdf+xml but the conditional
PATCH sent none, so the origin computed the precondition tag for a
different variant. The local edit flow already sends Accept:
application/rdf+xml on its PATCH for this reason. Align the test's
conditional PATCHes with the read. Not a proxy defect - the proxy
forwards Accept and If-Match faithfully (stale correctly 412s).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
* Declarative packages: ldh:import in dataspace settings replaces install endpoints

An application imports a package with a single <app> ldh:import <package-uri>
triple - in config/dataspaces.trig (permanent, applied on restart) or live via
PATCH /settings. Package components are discovered from the package's Linked
Data description; the stylesheet is loaded from its source URL and composed
into the application stylesheet in memory at compile time, per dataspace, with
no restart and no webapp file mutation.

- XsltExecutableFilter composes the app stylesheet document with package
  xsl:imports (SecureXML-parsed, import-set-derived cache key, fallback to
  the plain stylesheet on compile failure)
- LocalStylesheetResolver serves app-origin /static/ xsl:import URLs from the
  webapp instead of HTTPS round-trips through nginx
- packages/install and packages/uninstall endpoints, the admin packages/
  container and ACL entries, the package Actions UI, CLI scripts and their
  tests removed; XSLTMasterUpdater and Package.getStylesheetPath() deleted
- Package catalog is data at https://packages.linkeddatahub.com/ (bundled
  one-entry copy served via the proxy's mapped-URI resolution)
- Application settings modal lists the packages with Installed checkboxes;
  Save applies the change set as one DELETE/INSERT/WHERE PATCH on /settings
- ac:stylesheet values in dataspaces.trig are absolute per-origin URLs
- Known limitation: package ontologies are not yet auto-imported from
  ldh:import (manual owl:imports for now)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* Package checkboxes join the settings form as RDF/POST inputs - one Save

The packages fieldset moves inside the application settings form: each row's
Installed checkbox is a pu/ou input pair that serializes an ldh:import triple,
so the form's single Save submits settings and package imports as one PATCH.
The separate packages Save button, the change-set computation and the
SPARQL-generating handlers are removed.

- ldh:parse-rdf-post skips unchecked checkboxes (successful-controls rule of
  HTML form submission, which client-side DOM serialization bypasses)
- the ldh:import hidden-input round-trip is suppressed in the settings form -
  the checkboxes are the sole representation, so unchecking uninstalls
- the settings form response reloads the page on success: the composed
  stylesheet only manifests through a full server-side render

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* Keep package checkboxes across the settings violation re-render

The modal violation chain had no package-catalog load pair, so the
re-rendered settings form lost the package table - and since the
checkboxes are the sole ldh:import representation (the hidden-input
round-trip is suppressed), a Save after fixing a validation error
would submit no ldh:import triples and uninstall every package.

ldh:settings-form-response stamps the package-catalog load pair as
'load-pairs' alongside 'render-fn'/'required', and
ldh:modal-form-submit-violation joins stamped pairs onto its shared
list. Every pair must bake a request, so optional fetches ride per
flow rather than sitting in the shared list. The re-rendered
checkboxes reflect the submitted state: PackageList's installed set
comes from the echoed ldh:import triples in the violation body.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* Package ontologies join the imports closure, derived from ldh:import

The ontology half of declarative packages, mirroring the stylesheet
composition: when OntologyFilter assembles an application's ontology
closure, each imported package's ontology (from ldt:ontology in the
package description) is assembled as its own owl:imports closure and
added as a union member. Nothing is materialized - no owl:imports
triple is written anywhere, and the raw ontology graph served by /ns
stays untouched. A package ontology that fails to load is skipped so
a broken package cannot take the application ontology down.

Invalidation stays on the plain ontology-URI key that ClearOntology
already removes and rebuilds (now package-aware): a /settings PATCH -
the only way the ldh:import set changes live - evicts the assembled
closure, and the next request re-derives it. The trig-file route
implies a restart, which starts from empty caches anyway.

getPackage moves from XsltExecutableFilter to the system Application
(the filter delegates) so both composition halves share the package
description resolution. New http-test asserts the skos:Concept
constructor appears on /ns after the ldh:import PATCH and disappears
after removal - closure derivation and eviction in one round trip.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* Instrument package-ontology removal path (temporary diagnostics)

PATCH-settings-package-ontology.sh fails only on removal: the package
ontology's skos:Concept constructor stays in the closure after the
ldh:import is deleted (adding works). Eviction fires and updateApp
persists to the live context dataset, so static reading can't explain
it. Log what getPackageOntologies actually sees after the DELETE, and
have the test report whether /settings still carries the ldh:import
triple - one CI run then tells us persist-vs-cache. To be reverted once
the cause is fixed.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* Settings PATCH evicts the ontology from the repository too, not just the closure

Root cause of PATCH-settings-package-ontology.sh failing only on
removal, confirmed by the CI diagnostics: after the DELETE, /settings
carries 0 ldh:import triples and getPackageOntologies returns [] - the
derivation input is correct - yet the skos:Concept constructor stays on
/ns. The OWL2 imports build materializes the package's terms into the
cached base graph in the OntologyRepository; Settings evicted only the
closure union (getOntologyGraphs), leaving that polluted graph cached,
so the remove-phase rebuild read skos straight back. Adding worked
because the first build ran over a clean cache.

Clear both caches, matching ClearOntology (repository graph + fragment-
stripped document alias + closure union), minus the proxy purge since
settings responses are not cached. Revert the temporary
getPackageOntologies logging.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* Settings delegates ontology invalidation to ClearOntology via ResourceContext

Replaces the inline in-memory eviction with a call-through to the shared
ClearOntology logic (the pattern the old InstallPackage used):
getResourceContext().getResource(ClearOntology.class).post(uri, null).
This gets the full invalidation - repository graph + closure union +
varnish BAN/XKEY-PURGE + eager reload - so a settings-driven package
add/remove no longer leaves stale /ns SPARQL responses in the proxy
caches (the in-memory-only eviction fixed the closure but not varnish;
the http-test masked it with a manual purge).

ClearOntology becomes context-agnostic: it resolved the end-user app by
assuming the current app is admin (getApplication().as(AdminApplication)),
which threw for the end-user-scoped /settings. It now resolves both apps
from whichever the request matched and derives the admin backend from the
resolved admin app rather than the current one, so both backends are
purged either way.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* Import ontology persists annotations only - vocabulary via scratch document (#362)

The flow's output becomes the artifact packages ship statically: an
annotation ontology holding the derived class constructors plus
owl:imports of the canonical vocabulary URI. The fetched vocabulary is
scaffolding: PUT into a scratch document (UUID-slugged, under the app
root) that scopes the construct-constructors CONSTRUCT, then deleted -
on the error paths too (client: error wrapper armed once the scratch
exists; CLI: EXIT trap).

The vocabulary resolves live through the graph repository (bundled
mapping or HTTP), identically to package ontologies. This also fixes
constructor derivation for bundled vocabularies: previously the
shipped file shadowed the local copy that held the constructors, so
they never reached the closure (the old test's TO-DO documented it);
now the annotation document enters the closure under its own URI and
imports the vocabulary. Wiring changes accordingly:
add-ontology-import.sh --import <annotation-document>, not the source.

ontologyQuery in web.xml is untouched - the describing-document branch
keeps resolving own-origin ontologies; this flow simply stops minting
shadowing copies.

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
The bs2:Row GraphMode branch applied bs2:Graph without the required
canvas-id param, crashing the block render with a cardinality error.
Pass it, and initialize the 3D force graph after the row is rendered
by reusing ldh:InitDocumentGraph3D, fed the whole loaded document to
match the bs2:Graph rendering.

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