Website From WebDAV
Use this skill to take a static, already-generated HTML/CSS/JS site — the output of an RDF-Turtle + SHACL + Jinja2 pipeline such as sites/openlink/rdf/build.py — and publish it to a Virtuoso WebDAV root with a mapped public route. The site's content model (ontology, SHACL shapes, ordering) is authored and validated upstream of this skill; this skill's job starts at "here is a generated/ folder of finished HTML, get it live."
This is deliberately narrower than weblog-from-webdav or osdi-inclusion-engine: those skills render content live on every request (WebDAV-collection enumeration inside a bespoke VSP, or a shared XSLT skin wrapping WebDAV-sourced HTML). This skill's content is already fully rendered before it arrives — publishing it is a plain WebDAV PUT plus a static-file VHOST_DEFINE, no VSP execution and no live SPARQL query involved. See references/webdav-website-engine-gate.md for why that choice was made and when it would change.
Blocking Gate — A Local File Is Not a Valid Completion
When the user asks to publish, deploy, or "make live" a generated site, a description of what would happen, or a claim that files exist locally, is not a valid completion unless the user explicitly asks only for a local/preview check. The default deliverable is either:
- a live, verified published site: the target route returns the same content as the local
generated/output, or - a complete deployable bundle:
generated/*.html, the site'sassets/(site.css,theme.js), a path-substitutedtemplates/deploy-website-route.sql, and a README/run-notes file — withscripts/validate_generated_website_bundle.pypassing against it.
Read references/webdav-website-engine-gate.md before claiming either. If deployment credentials (WebDAV and/or isql) are unavailable, stop at the deployable bundle and say plainly that live publication is blocked by missing authenticated access — do not describe hypothetical success.
First decisions
Establish these before publishing anything:
- Source pipeline directory (e.g.
{LLM_ROOT}/Claude Generated/sites/openlink/) and whetherrdf/build.pyneeds to be (re)run first, orgenerated/is already current. - Target DAV collection, e.g.
/DAV/home/kidehen/sites/openlink/. - Public route, e.g.
https://linkeddata.uriburner.com/sites/openlink/. - Identity mode: SQL username/password,
isqlWebID-TLS, WebID-TLS plus delegation, WebDAV username/password, WebDAV mTLS, or WebDAV mTLS plusOn-Behalf-Of. - Whether this is a first-time route bootstrap (
isql+deploy-website-route.sqlrequired) or a content-only republish of an existing route (WebDAV publish only, perreferences/webdav-publish-mode.md's out-of-scope boundary — it never touches route configuration).
Workflow
- Confirm the source
generated/output is current: if asked to (re)build, run the pipeline's ownrdf/validate_shacl.pythenrdf/build.pyunchanged — this skill does not reimplement or bypass that validation. Seereferences/rdf-content-mode.md. - Before writing anything, search prior memory/work for
validate_generated_weblog_bundle,VHOST_DEFINE,DAV_RES_UPLOAD_STRSES_INT,website-from-webdav; reuse proven fixes rather than rediscovering them. - Determine whether the target route already exists (query
DB.DBA.HTTP_PATHandWS.WS.SYS_DAV_RESfor the target path viaisql). If not, bootstrap it: patchtemplates/deploy-website-route.sqlwith the real DAV path/route/title, then run it withisql. Seereferences/isql-route-mode.md. - Validate the bundle with
scripts/validate_generated_website_bundle.pybefore any publish. This is blocking. - Publish via WebDAV using
scripts/publish_static_bundle.py— PUTsgenerated/*.htmlandassets/*recursively, verifies each write with a follow-up GET/PROPFIND compare. Seereferences/webdav-publish-mode.md. - Verify: fetch the live route, confirm the served bytes match the local
generated/file (allowing only whitespace/attribute-order differences, same tolerance the pipeline's own Phase 1 verification uses), confirmassets/site.css/theme.jsload with correct MIME types. - Report exactly what happened: route bootstrapped or reused, files published with their DAV paths, verification result — or, if credentials were missing, the deployable bundle location and the explicit statement that live publication did not happen.
Mode references
- For route bootstrap and
isqlTLS/WebID syntax, readreferences/isql-route-mode.md. - For WebDAV publication and its out-of-scope boundary, read
references/webdav-publish-mode.md. - For the pipeline boundary (what this skill does and does not touch in
rdf/), readreferences/rdf-content-mode.md. - For why live SPARQL/VSP rendering was not chosen, read
references/webdav-website-engine-gate.md. - For agent-callable operations, read
references/opal-tool-mode.md— not implemented in v1; documented as the extension point.
Practical guardrails
- Never run
rdf/build.py's SHACL validation with warnings suppressed or--skip-validate-style shortcuts; a failing gate means the content is not ready to publish, not an obstacle to route around. DB.DBA.DAV_RES_UPLOAD_STRSES_INT(if used for any non-static-file step) needs astring_output()stream populated viahttp(content, stream), not a plain string.- Avoid
$1,$2, and similar tokens in any SQL loaded throughisql; they can trigger macro substitution. - A static-file
VHOST_DEFINEusesis_dav=>1,is_brws=>0,def_page=>'index.html'— do not setdef_page=>'index.vsp'unless a VSP entry point genuinely exists; adef_pagepointing at a nonexistent VSP resource serves raw source or a 404, not the site. - Delete any prior conflicting
VHOST_DEFINEfor the same host/path before redefining it, same asweblog-from-webdav's route-remove-then-redefine pattern. - Treat the certificate-bearing identity and the delegated principal as separate facts:
-X/--certidentifies the calling agent,-W/On-Behalf-Ofidentifies the WebID principal whose ACL rights should be evaluated. - Never write credentials into the skill, generated SQL, logs, or committed files.
- Remote uploads and removals follow the same HTTP → WebDAV → iSQL order and principal PKCS#12 pattern (
howto/remote-webdav-upload.ttl,howto/remote-resource-removal.ttl, prefs Steps 215–216). URIBurner writes/deletes go to port 5443. Report:5443GET and public 443 GET separately after PUT; after DELETE, verify the user-named URL, slash variants,:5443GET, and the folder listing before claiming removal.
Safe Virtuoso ISQL Error Output
Whenever this workflow invokes OpenLink's native Virtuoso isql client, pass the bare client option COMMAND_TEXT_ON_ERROR=OFF, for example isql {host}:{port} {user} {password} COMMAND_TEXT_ON_ERROR=OFF {script.sql}. Place it before SQL input, script arguments, or -i. OFF maps to the client's internal value 0 and prevents the client from appending the full failed command to error output. The client accepts OFF/ON; literal 0 is rejected. Do not use -u for this setting (-u populates the U argument array). This does not redact secrets from server diagnostics, shell history, or process arguments, so keep credentials out of SQL text and logs. Do not apply this Virtuoso-specific option to unixODBC/iODBC isql clients.