Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
55152b5
[maven-release-plugin] prepare for next development iteration
namedgraph Sep 29, 2026
142c5fb
Set the RDF library and CLI versions to 6.0.2-SNAPSHOT
namedgraph Sep 29, 2026
ae17e0d
Post-release version bump
namedgraph Sep 29, 2026
85fde19
Build linkeddatahub-rdf in CI instead of resolving it from a repository
namedgraph Sep 29, 2026
ab4389e
Build the release images natively per architecture, and cache the layers
namedgraph Sep 29, 2026
c033a75
Parse and serialise SPARQL in the browser with SPARQL.js alone (#401)
namedgraph Oct 1, 2026
6d7903e
A malformed Accept-Language is dropped at the door, so a 403 no longe…
namedgraph Oct 2, 2026
d839d26
Restoring a version from a historical view quotes the live document's…
namedgraph Oct 3, 2026
ba48e8e
Runtime settings are kept as changes, so a configuration edit reaches…
namedgraph Oct 3, 2026
8646e58
Link headers are built with jakarta.ws.rs.core.Link, through a subcla…
namedgraph Oct 3, 2026
1dd3779
A versioned document not yet written has an empty TimeMap, so its His…
namedgraph Oct 3, 2026
7ab7686
The history dialog named only the signed-in agent: any other author s…
namedgraph Oct 3, 2026
7ba7c85
The request access dialog offered the signed-in agent by their WebID …
namedgraph Oct 3, 2026
c4cf624
ORCID login was hardwired to sandbox.orcid.org: the endpoints now der…
namedgraph Oct 3, 2026
0b71d9d
DEBUG: rate-limit.spec logs the timeline of every over-fetched request
namedgraph Oct 3, 2026
8dd59ab
Clear the file drop overlay when a drag leaves before it takes over (…
namedgraph Oct 3, 2026
a647397
Hold the document's lock from the read to the write, so a stale If-Ma…
namedgraph Oct 3, 2026
2c223c3
Merge branch 'develop' of github.com:AtomGraph/LinkedDataHub into dev…
namedgraph Oct 3, 2026
e8712f5
A view lists what an aliased first variable binds, and says when it b…
namedgraph Oct 4, 2026
834192c
Drop the CLI's --base option; dataspace commands take the base URI as…
namedgraph Oct 4, 2026
57a0a6f
Merge branch 'develop' of github.com:AtomGraph/LinkedDataHub into dev…
namedgraph Oct 4, 2026
1dfca20
Set the development version to 6.1.0-SNAPSHOT
namedgraph Oct 4, 2026
93fffb8
The changelog lists everything develop holds since 6.0.1
namedgraph Oct 4, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions .github/workflows/http-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,14 @@ jobs:
- name: Add bin/ and its subdirectories to PATH
run: |
find "$GITHUB_WORKSPACE/bin" -type d >> "$GITHUB_PATH"
# cli/ resolves linkeddatahub-rdf by ${project.version}, and the library lives in this same
# checkout - so it is built here rather than fetched. Resolving a sibling through a remote
# repository would make every version bump wait on a publish: the post-release develop asks
# for a SNAPSHOT nothing has deployed yet, and the release tag asks for a version Central has
# not seen. `make cli` does the same thing locally.
- name: Build the linkeddatahub-rdf library
run: mvn -B install
working-directory: rdf
- name: Build the ldh CLI
run: mvn -B package # the CLI unit tests run here; the suite drives ldh for its fixtures
working-directory: cli
Expand Down
138 changes: 108 additions & 30 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,24 +5,110 @@ on:
tags:
- '*'

env:
PLATFORM_IMAGE: atomgraph/linkeddatahub
SEF_COMPILER_IMAGE: atomgraph/linkeddatahub-sef-compiler

jobs:
# one job per architecture, each on a runner of that architecture. A single multi-platform build
# runs the arm64 leg through QEMU, which puts javac, the Saxon SEF compile and npm at a fraction
# of native speed and serializes it behind the amd64 one. These push untagged manifests by
# digest; the manifest job below assembles them into the tagged manifest lists
docker:
runs-on: ubuntu-latest
strategy:
matrix:
include:
- platform: linux/amd64
arch: amd64
runner: ubuntu-latest
- platform: linux/arm64
arch: arm64
runner: ubuntu-24.04-arm
runs-on: ${{ matrix.runner }}
steps:
- name: Login to Docker Hub
uses: docker/login-action@v4
with:
username: ${{ vars.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}

- name: Set up QEMU
uses: docker/setup-qemu-action@v4

- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v4

# no checkout step: with no context input the action builds from the Git context, so buildx
# clones the tag itself

- name: Build and push the platform image
id: platform
uses: docker/build-push-action@v6
with:
platforms: ${{ matrix.platform }}
tags: ${{ env.PLATFORM_IMAGE }} # the repository alone: these manifests are pushed untagged, by digest
outputs: type=image,push-by-digest=true,name-canonical=true,push=true
cache-from: type=gha,scope=platform-${{ matrix.arch }}
cache-to: type=gha,mode=max,scope=platform-${{ matrix.arch }}

# the sef-compiler service composes each dataspace's client stylesheet with its package
# stylesheets, so a deployment that consumes images rather than building them needs it
# published under the same version as the platform it compiles for. Same Dockerfile, same
# tag, one stage earlier - the two images are only ever released together.
#
# It reads the platform scope too, which is where the maven stage the two share was exported.
# Separate scopes rather than one: each build exports only the stages it reaches, and a shared
# scope would have them overwrite each other's export on every run
- name: Build and push the SEF compiler image
id: sef-compiler
uses: docker/build-push-action@v6
with:
platforms: ${{ matrix.platform }}
target: sef-compiler
tags: ${{ env.SEF_COMPILER_IMAGE }} # the repository alone: these manifests are pushed untagged, by digest
outputs: type=image,push-by-digest=true,name-canonical=true,push=true
cache-from: |
type=gha,scope=sef-compiler-${{ matrix.arch }}
type=gha,scope=platform-${{ matrix.arch }}
cache-to: type=gha,mode=max,scope=sef-compiler-${{ matrix.arch }}

# one empty file per digest, named after it: the manifest job addresses the images it assembles
# by digest and needs nothing else from this one
- name: Export the digests
env:
PLATFORM_DIGEST: ${{ steps.platform.outputs.digest }}
SEF_COMPILER_DIGEST: ${{ steps.sef-compiler.outputs.digest }}
run: |
mkdir -p /tmp/digests/platform /tmp/digests/sef-compiler
touch "/tmp/digests/platform/${PLATFORM_DIGEST#sha256:}"
touch "/tmp/digests/sef-compiler/${SEF_COMPILER_DIGEST#sha256:}"

- name: Upload the digests
uses: actions/upload-artifact@v4
with:
name: digests-${{ matrix.arch }}
path: /tmp/digests/*
if-no-files-found: error
retention-days: 1

# assemble one tagged manifest list per image from the per-architecture manifests pushed above
manifest:
name: Assemble the multi-architecture manifests
needs: docker
runs-on: ubuntu-latest
steps:
- name: Login to Docker Hub
uses: docker/login-action@v4
with:
username: ${{ vars.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}

# the digest file names are unique per architecture, so the artifacts merge into one tree
- name: Download the digests
uses: actions/download-artifact@v4
with:
path: /tmp/digests
pattern: digests-*
merge-multiple: true

- name: Extract version parts
id: version
run: |
RAW_REF="${{ github.ref_name }}"
# strip the literal prefix
Expand All @@ -34,32 +120,24 @@ jobs:
echo "MINOR=$MAJOR.$MINOR" >> $GITHUB_ENV
echo "FULL_VERSION=$VERSION" >> $GITHUB_ENV

- name: Build and push the platform image
uses: docker/build-push-action@v6
with:
platforms: linux/amd64,linux/arm64
push: true
tags: |
atomgraph/linkeddatahub:latest
atomgraph/linkeddatahub:${{ env.FULL_VERSION }}
atomgraph/linkeddatahub:${{ env.MINOR }}
atomgraph/linkeddatahub:${{ env.MAJOR }}
- name: Create the manifest lists
run: |
create_manifest() {
image="$1"
refs=()
for digest in "/tmp/digests/$2"/*; do
refs+=("$image@sha256:$(basename "$digest")")
done
docker buildx imagetools create \
-t "$image:latest" \
-t "$image:$FULL_VERSION" \
-t "$image:$MINOR" \
-t "$image:$MAJOR" \
"${refs[@]}"
}

# the sef-compiler service composes each dataspace's client stylesheet with its package
# stylesheets, so a deployment that consumes images rather than building them needs it
# published under the same version as the platform it compiles for. Same Dockerfile, same
# tag, one stage earlier - the two images are only ever released together
- name: Build and push the SEF compiler image
uses: docker/build-push-action@v6
with:
platforms: linux/amd64,linux/arm64
push: true
target: sef-compiler
tags: |
atomgraph/linkeddatahub-sef-compiler:latest
atomgraph/linkeddatahub-sef-compiler:${{ env.FULL_VERSION }}
atomgraph/linkeddatahub-sef-compiler:${{ env.MINOR }}
atomgraph/linkeddatahub-sef-compiler:${{ env.MAJOR }}
create_manifest "$PLATFORM_IMAGE" platform
create_manifest "$SEF_COMPILER_IMAGE" sef-compiler

cli:
name: Attach the ldh CLI to the release
Expand Down
8 changes: 8 additions & 0 deletions .github/workflows/ui-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,14 @@ jobs:
- name: Add bin/ and its subdirectories to PATH
run: |
find "$GITHUB_WORKSPACE/bin" -type d >> "$GITHUB_PATH"
# cli/ resolves linkeddatahub-rdf by ${project.version}, and the library lives in this same
# checkout - so it is built here rather than fetched. Resolving a sibling through a remote
# repository would make every version bump wait on a publish: the post-release develop asks
# for a SNAPSHOT nothing has deployed yet, and the release tag asks for a version Central has
# not seen. `make cli` does the same thing locally.
- name: Build the linkeddatahub-rdf library
run: mvn -B install
working-directory: rdf
- name: Build the ldh CLI
run: mvn -B package # the suite builds its fixtures with ldh, the way tests/http does
working-directory: cli
Expand Down
26 changes: 26 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,29 @@
## [Unreleased]
### Migration
- **BREAKING**: `ldh` drops `-b`/`--base`; dataspace-level commands take the base URI as their positional, defaulting to `LDH_BASE`
- `Link` relation types are always quoted, e.g. `rel="timemap"`
- Runtime settings are kept as a change set in `settings/dataspaces.ru`; an existing `settings/dataspaces.trig` is converted once

### Changed
- `ldh add file`, `import csv`, `import rdf` and `push` derive the upload URI from the target document
- `Link` headers are built with `jakarta.ws.rs.core.Link`, keeping empty fragments such as `ns#`
- Release images are built natively per architecture, with a layer cache
- `MAX_CONN_PER_ROUTE`, `MAX_TOTAL_CONN` and `MAX_REQUEST_RETRIES` are passed as system properties
- The browser parses and serialises SPARQL with SPARQL.js alone; `SPARQLBuilder.js` is gone

### Fixed
- ORCID endpoints derive from `ORCID_ISSUER` (default `https://orcid.org`) instead of the hardwired sandbox
- Concurrent writes quoting the same `ETag` no longer both pass `If-Match`
- Runtime settings no longer mask later edits to `config/dataspaces.trig` and `config/system.trig`
- Restoring a version from a historical view no longer fails `412`
- An unwritten versioned document has an empty TimeMap instead of `404`
- The History dialog names every author, not only the signed-in agent
- The request access dialog names the signed-in agent instead of showing their WebID
- A malformed `Accept-Language` is ignored instead of causing a `500`
- Views support an aliased first variable, and say when it binds no resources
- The file drop overlay clears when a drag leaves the window early
- CI builds `linkeddatahub-rdf` from the checkout, so version bumps no longer break the suites

## [6.0.1] - 2026-09-30
### Added
- `linkeddatahub-rdf`, a separately published library of the vocabularies and the document shapes the HTTP API accepts, so the CLI and other clients build against one description of the request bodies
Expand Down
10 changes: 8 additions & 2 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,19 @@ RUN mkdir /jena && \
tar -xzf - -C /jena

# copy platform source code and POM
#
# the POM comes first and resolves the dependency tree in a layer of its own, so a source change
# does not invalidate it. go-offline does not cover every profile-bound plugin, which is why the
# install below stays online and fetches whatever is left rather than failing on it

WORKDIR /usr/src/platform

COPY src /usr/src/platform/src

COPY pom.xml /usr/src/platform/pom.xml

RUN mvn -B -Pstandalone dependency:go-offline

COPY src /usr/src/platform/src

RUN mvn -Pstandalone clean install
# ==============================

Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -356,7 +356,7 @@ LinkedDataHub includes an HTTP [test suite](https://github.com/AtomGraph/LinkedD
### Browser

* [Saxon-JS](https://www.saxonica.com/saxon-js/)
* [SPARQLBuilder](https://github.com/AtomGraph/sparql-builder)
* [SPARQL.js](https://github.com/RubenVerborgh/SPARQL.js)
* [OpenLayers](https://openlayers.org)
* [Google Charts](https://developers.google.com/chart)
* [xml-c14n-sync](https://github.com/AtomGraph/xml-c14n-sync)
Expand Down
24 changes: 20 additions & 4 deletions cli/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -111,9 +111,21 @@ Repeated options can be set once via environment variables:
|---|---|
| `LDH_CERT_FILE` | `-c`, `--cert` |
| `LDH_CERT_PASSWORD` | `-p`, `--cert-password` |
| `LDH_BASE` | `-b`, `--base` |
| `LDH_BASE` | `BASE_URI`, the positional of the dataspace-addressing commands |
| `LDH_PROXY` | `--proxy` |

The commands that address no document of their own take the dataspace's base URI as their positional
argument instead, where every other command puts its target: the `packages` family,
`admin clear ontology`, `admin create ontology`, `admin create group`, `admin create authorization`,
`admin import ontology` and `admin make-public`. `LDH_BASE` is its default, so with it exported the
argument is left out:

```bash
ldh packages add --package https://packages.linkeddatahub.com/editor/taxonomy/#this https://localhost:4443/
ldh admin clear ontology https://admin.localhost:4443/
ldh admin create group --name Editors --member "$AGENT_URI" https://admin.localhost:4443/
```

```bash
export LDH_CERT_FILE=ssl/owner/keystore.p12 LDH_CERT_PASSWORD=... LDH_BASE=https://localhost:4443/

Expand All @@ -138,7 +150,7 @@ without them (the taxonomy editor package rejects a concept with no `skos:inSche

- Commands that create or append to a document print its URL as the only line on stdout, so shell
pipelines keep working: `item=$(ldh create item ...)`. `add file` prints the content-addressed
upload URI (`{base}uploads/{sha1}`). All diagnostics go to stderr.
upload URI (`uploads/{sha1}` under the root of the document's origin). All diagnostics go to stderr.
- `push` writes many documents in one run and prints one line per written document URL or upload
URI, in write order, so the listing greps and cuts like `packages list` does. Progress
(`PUT <url> <- <path>`, `POST <url> <- <path>`) and `Skipping <path>` lines go to stderr. On the
Expand Down Expand Up @@ -210,7 +222,7 @@ ldh packages list | grep ^available | cut -f2

The registry defaults to `https://packages.linkeddatahub.com/` and `--registry` overrides it. It is
read through the application's Linked Data proxy rather than fetched directly, the same way the
application settings modal reads it, so `packages list` needs `--base` as much as the other two do.
application settings modal reads it, so `packages list` needs the application URI as much as the other two do.

The commands go through `PATCH /settings`, which is the live path: the change is in effect
immediately but lives in the running application's context dataset. Declaring the same
Expand All @@ -235,7 +247,7 @@ With `D` the URL of the directory being walked (`TARGET_URI` for the pushed dire
- Any other RDF file `name.ext` is `PUT` to `D/name/`, with its relative URIs resolved against
that URL (the same `turtle --base` resolution `put` applies).
- Every other file is uploaded into `D`, as `add file` would: title = file name, media type
detected, upload URI `{base}uploads/{sha1}`.
detected, upload URI `uploads/{sha1}` under the root of the document's origin.
- A subdirectory `name` maps to `D/name/`: its document is the RDF file `name.ext` beside it, its
files are uploaded into that document, and its subdirectories recurse.

Expand Down Expand Up @@ -309,6 +321,10 @@ shell scripts.

### Differences from the scripts

- `-b/--base` is gone. The commands that take a document URI never needed it (the scripts required it
but never read it, resolving relative URIs against the target instead; the upload URI `add file`,
`import csv`, `import rdf` and `push` print is `uploads/{sha1}` under the root of the target's
origin), and the ones that act on the dataspace itself take its base URI as the positional argument.
- `-f/--cert-pem-file` is now `-c/--cert` and takes either the PKCS12 keystore or the PEM the
scripts fed `curl -E`, whichever is at hand.
- `admin create group` writes the `--name` value into `foaf:name`/`dct:title` (the script wrote an
Expand Down
25 changes: 5 additions & 20 deletions cli/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

<groupId>com.atomgraph</groupId>
<artifactId>linkeddatahub-cli</artifactId>
<version>6.0.1</version>
<version>6.1.0-SNAPSHOT</version>
<packaging>jar</packaging>

<name>LinkedDataHub CLI</name>
Expand All @@ -17,28 +17,13 @@
<jersey.version>3.1.11</jersey.version>
</properties>

<!-- where linkeddatahub-rdf comes from between releases. This project has never needed a
repository before: everything it depends on is released to Maven Central, which does not serve
snapshots, and the platform pom's own declaration does not reach here because the two are
separate builds. Without this a clean checkout resolves the library only if it happens to be in
the local repository, which is how CI first failed. -->
<repositories>
<repository>
<id>central-portal-snapshots</id>
<url>https://central.sonatype.com/repository/maven-snapshots/</url>
<releases>
<enabled>false</enabled>
</releases>
<snapshots>
<enabled>true</enabled>
</snapshots>
</repository>
</repositories>

<dependencies>
<dependency>
<!-- the vocabularies and the document shapes, shared with the platform's other API
clients; this project adds the command line over them -->
clients; this project adds the command line over them. Built from rdf/ in this same
checkout - `make cli` and the workflows install it first - and deliberately resolvable
from nowhere else: a remote fallback would let a build quietly use an older published
snapshot instead of failing when that step is missing. -->
<groupId>com.atomgraph</groupId>
<artifactId>linkeddatahub-rdf</artifactId>
<version>${project.version}</version>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,14 +18,12 @@

import com.atomgraph.linkeddatahub.cli.BaseCommand;
import com.atomgraph.linkeddatahub.cli.http.LDHClient;
import com.atomgraph.linkeddatahub.cli.mixin.BaseMixin;
import com.atomgraph.linkeddatahub.rdf.Queries;
import com.atomgraph.linkeddatahub.rdf.vocabulary.SP;
import java.net.URI;
import java.nio.file.Files;
import java.nio.file.Path;
import picocli.CommandLine.Command;
import picocli.CommandLine.Mixin;
import picocli.CommandLine.Option;
import picocli.CommandLine.Parameters;

Expand All @@ -38,9 +36,6 @@
public class AddConstruct extends BaseCommand
{

@Mixin
private BaseMixin baseMixin;

@Option(names = "--title", required = true, paramLabel = "TITLE", description = "Title of the query")
private String title;

Expand All @@ -62,8 +57,6 @@ public class AddConstruct extends BaseCommand
@Override
public Integer call() throws Exception
{
baseMixin.require(getSpec()); // required by the script interface

core(getClient(), target, uri, title, Files.readString(queryFile), service, description);
print(target);

Expand Down
Loading
Loading