3. Edge DSP Validation with alexandria-client
Deploy alexandria-client in front of a Dataspace Connector, verify S1–S13 background synchronization, and validate DSP messages at the edge.
Tutorial 3: Edge DSP Validation with alexandria-client
In a sovereign dataspace, connectors cannot afford to query a central Vocabulary Hub synchronously on every Dataspace Protocol (DSP) message—doing so would introduce a single point of failure and leak participant traffic metadata.
Instead, alexandria-client runs as an edge sidecar alongside your Connector:
- Background Sync (
S1–S13): Periodically downloads and cryptographically verifies signed listings and PVP packages (ni:///sha-256;...) fromalexandria-server, persisting them to disk. - Hot-Path Isolation (
REQ-SYS-002): Validates inbound (dsp-ingress) and outbound (dsp-egress) DSP traffic strictly in memory from verified local packages. - Byte-for-Byte Proxy (
REQ-SYS-003): Forwards valid requests untouched and rejects non-compliant requests with normative DSP error bodies (ContractNegotiationError,TransferError,CatalogError) and an RFC 9651VHub-Verdictheader.
Step 1: Launch fake-connector and alexandria-server with a Shared Clock
To control time deterministically without waiting days for propagation windows (new_content_from), alexandria-server and alexandria-client support an injectable file clock (-clock-file, DP-9).
Create /tmp/vhub-edge and initialize the clock at 2026-09-01T10:00:00Z:
make build
mkdir -p /tmp/vhub-edge
echo "2026-09-01T10:00:00Z" > /tmp/vhub-edge/clockStart the simulated upstream connector (bin/fake-connector) on port 18282:
./bin/fake-connector -listen 127.0.0.1:18282 &Write tokens.json and server.json and start alexandria-server on port 18080 reading /tmp/vhub-edge/clock:
cat << 'EOF' > /tmp/vhub-edge/tokens.json
{
"editor-token": {"principal": "steward", "roles": ["editor"]},
"releaser-token": {"principal": "releaser", "roles": ["releaser"]},
"member-token": {"principal": "client", "roles": ["member"]}
}
EOF
cat << 'EOF' > /tmp/vhub-edge/server.json
{
"listen": "127.0.0.1:18080",
"public_base_url": "http://127.0.0.1:18080",
"did": "did:web:127.0.0.1%3A18080",
"sqlite_path": "/tmp/vhub-edge/hub.db",
"blob_dir": "/tmp/vhub-edge/blobs",
"signing_key_path": "/tmp/vhub-edge/hub.key",
"tokens_path": "/tmp/vhub-edge/tokens.json",
"compiler": "dsl"
}
EOF
./bin/alexandria-server -config /tmp/vhub-edge/server.json -clock-file /tmp/vhub-edge/clock &Step 2: Publish Release 1.0.0 on the Hub and Advance Past Propagation Window
Publish conformance/f6/drafts/v1.0.0.yaml (which defines odrl:use and ex:access as the only valid actions in https://example.com/odrl/profile/f6):
DRAFT_ID=$(curl -s -X POST http://127.0.0.1:18080/v1/drafts \
-H "Authorization: Bearer editor-token" \
--data-binary @conformance/f6/drafts/v1.0.0.yaml | jq -r .id)
CAND_ID=$(curl -s -X POST http://127.0.0.1:18080/v1/drafts/${DRAFT_ID}:propose \
-H "Authorization: Bearer editor-token" | jq -r .id)
curl -s -X POST http://127.0.0.1:18080/v1/candidates/${CAND_ID}:approve \
-H "Authorization: Bearer releaser-token" -d '{}' | jq .Notice in the approval response that:
published_atis2026-09-01T10:00:00Znew_content_fromandenforced_fromare2026-09-03T10:00:00Z(after the 48-hour propagation window).
Advance the file clock to 2026-09-05T12:00:00Z so version 1.0.0 is in the Enforced state:
echo "2026-09-05T12:00:00Z" > /tmp/vhub-edge/clockStep 3: Configure and Start alexandria-client
Create the client's credentials file (credentials.json) and configuration (config.json):
echo '{"token":"member-token"}' > /tmp/vhub-edge/credentials.json
chmod 600 /tmp/vhub-edge/credentials.json
cat << 'EOF' > /tmp/vhub-edge/config.json
{
"space": "https://example.com/spaces/demo",
"trust": {
"hub_did": "did:web:127.0.0.1%3A18080",
"hub": "https://127.0.0.1:18080",
"credentials": "file:///tmp/vhub-edge/credentials.json"
},
"upstreams": {
"dsp": "http://127.0.0.1:18282"
},
"listeners": {
"dsp-ingress": { "bind": "127.0.0.1:18443" },
"dsp-egress": { "bind": "127.0.0.1:13128" },
"admin": { "bind": "127.0.0.1:19900" }
},
"routes": [
{
"listener": "dsp-ingress",
"match": { "method": "POST", "path": "/negotiations/request" },
"message": "ContractRequestMessage",
"gates": ["dsp_message", "policy_intake"],
"mode": "enforce",
"body": { "select": "$.offer" }
}
],
"events": {
"sink": "file:///tmp/vhub-edge/events.jsonl"
}
}
EOFLaunch alexandria-client (passing -did.allow_insecure_localhost because our lab Hub runs over cleartext http://127.0.0.1:18080, per DP-5):
./bin/alexandria-client \
-config /tmp/vhub-edge/config.json \
-state-dir /tmp/vhub-edge/state \
-clock-file /tmp/vhub-edge/clock \
-did.allow_insecure_localhost &Step 4: Inspect Client Status & Synchronized Versions (admin Port)
Query the local Admin API on port 19900 (which is strictly isolated from DSP traffic listeners per REQ-CLI-019):
curl -s http://127.0.0.1:19900/status | jq .
curl -s http://127.0.0.1:19900/versions | jq .You will see:
- Operational
state:"normal" last_accepted_listing.sequence:1(or2after scheduler re-issuance)- Retained package
1.0.0in state"Enforced"
Step 5: Send Valid and Invalid DSP Messages Through dsp-ingress
Now send two ContractRequestMessage payloads to alexandria-client's dsp-ingress listener (http://127.0.0.1:18443/negotiations/request):
1. Compliant Offer (ex:access is defined in 1.0.0)
curl -i -s -X POST http://127.0.0.1:18443/negotiations/request \
-H "Content-Type: application/json" \
-d '{
"@context": [
"https://w3id.org/dspace/2025/1/context.jsonld",
"https://example.com/odrl/profile/f6/context.jsonld"
],
"@type": "ContractRequestMessage",
"consumerPid": "urn:uuid:c-1",
"offer": {
"@id": "urn:uuid:offer-1",
"@type": "Offer",
"profile": "https://example.com/odrl/profile/f6",
"target": "urn:uuid:ds-1",
"permission": [{ "action": "ex:access" }]
},
"callbackAddress": "http://consumer/cb"
}'Result: HTTP/1.1 200 OK — Forwarded byte-for-byte to fake-connector without adding a VHub-Verdict header (REQ-CLI-013).
2. Non-Compliant Offer (odrl:read is NOT defined in 1.0.0)
curl -i -s -X POST http://127.0.0.1:18443/negotiations/request \
-H "Content-Type: application/json" \
-d '{
"@context": [
"https://w3id.org/dspace/2025/1/context.jsonld",
"https://example.com/odrl/profile/f6/context.jsonld"
],
"@type": "ContractRequestMessage",
"consumerPid": "urn:uuid:c-1",
"offer": {
"@id": "urn:uuid:offer-1",
"@type": "Offer",
"profile": "https://example.com/odrl/profile/f6",
"target": "urn:uuid:ds-1",
"permission": [{ "action": "odrl:read" }]
},
"callbackAddress": "http://consumer/cb"
}'Result: HTTP/1.1 400 Bad Request — Blocked at the edge! Notice the RFC 9651 VHub-Verdict header and the normative DSP ContractNegotiationError body:
HTTP/1.1 400 Bad Request
Content-Type: application/json
Vhub-Verdict: violation;gate=policy_intake;code="VERDICT_VIOLATION"
{
"@context": "https://w3id.org/dspace/2025/1/context.jsonld",
"@type": "ContractNegotiationError",
"providerPid": "urn:uuid:00000000-0000-0000-0000-000000000000",
"consumerPid": "urn:uuid:c-1",
"code": "VERDICT_VIOLATION",
"reason": ["..."]
}3. Untrusted @context Injection (CONTEXT_NOT_IN_PACKAGE)
If a caller tries to inject an external JSON-LD @context not listed in the signed package manifest (REQ-CLI-010), alexandria-client immediately blocks it without making any network call:
curl -i -s -X POST http://127.0.0.1:18443/negotiations/request \
-H "Content-Type: application/json" \
-d '{
"@context": ["https://evil.example/ctx.jsonld"],
"@type": "ContractRequestMessage",
"consumerPid": "urn:uuid:c-1",
"offer": {"@id":"urn:uuid:o1","@type":"Offer","profile":"https://example.com/odrl/profile/f6","target":"urn:uuid:ds-1","permission":[{"action":"ex:access"}]}
}'Result: HTTP/1.1 400 Bad Request with VHub-Verdict: violation;gate=policy_intake;code="CONTEXT_NOT_IN_PACKAGE".
Step 6: Inspect Verdict Events & Prometheus Metrics
Every non-observe evaluation emits a structured JSON event conforming to Appendix A.4 (verdict-event.schema.json) and increments Prometheus counters:
# View structured JSONL verdict events:
cat /tmp/vhub-edge/events.jsonl | jq .
# Query Prometheus metrics on the admin port:
curl -s http://127.0.0.1:19900/metricsWhen finished experimenting, clean up background processes:
pkill -f "alexandria-client|alexandria-server|fake-connector" || trueNext Tutorial
In Tutorial 4: Version Transitions, Grace Windows & Revocations (Scenario F6), you will see how alexandria-server and alexandria-client handle breaking releases (1.0.0 -> 2.0.0), transition grace windows (sunset), contract agreement leases, and emergency revocations!
2. Publishing & Governance on alexandria-server
Step-by-step tutorial on running alexandria-server, creating drafts, proposing candidates, approving signed releases, and dereferencing /pub artifacts.
4. Breaking Changes, Grace Windows & F6
Walk through the complete normative Scenario F6: breaking profile upgrades, transition warnings, contract agreement leases, and version revocations.