18 commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
| 707f1d8ec2 |
feat(client): mirror mode — access-triggered subtree walker + listing cache
--mode mirror layers an access-triggered walker on top of the cache
pipeline. When an incoming request's URL falls under one of the
configured --mirror-subtree paths, the scheduler kicks off a recursive
walk of that subtree iff (a) no walk for that subtree is in flight and
(b) now - last_walk_at >= --mirror-min-interval (default 1h). Walks
run in a goroutine; the user's request never blocks on scheduling.
Why access-triggered: a naive "walk on a fixed timer" would produce
thundering-herd polls on a master from many vendor mirrors most of
which are idle most of the time. Demand-triggering means idle mirrors
generate zero upstream traffic until someone hits them; active
mirrors stay current as a side effect of normal use.
The walk:
1. Recursively fetches JSON listings under the subtree, persisting
each at <dir>/.zddc-listing.json so directory browsing works
offline for walked subtrees.
2. For each file, fires a conditional If-Modified-Since GET (bounded
parallelism; default 4 concurrent) — 304 no-op, 200 overwrites,
403/404 purges the local cache.
3. After enumeration, per-directory orphan purge: local files absent
from upstream's filtered listing are removed (handles upstream
deletes + ACL revocations).
State persists at <root>/.zddc-mirror-state.json as
{subtrees: {<path>: {last_walk_at}}}. In-flight tracking is in-memory
only — a crash mid-walk lets the next access retry without manual
cleanup. Subtree path matching is longest-prefix-wins; "/" is a
catch-all (full mirror, the default when --mode=mirror is set without
explicit --mirror-subtree).
The cache layer also gained directory-listing caching (independent of
mirror mode but enabled by it). Directories are now stored at
<dir>/.zddc-listing.<html|json> sidecars, varied by Accept header.
Hit/miss/offline semantics mirror the file pipeline. Phase 2's
limitation that directories always proxied live (no offline browse)
is now resolved for any directory the user has visited or that mirror
mode has walked.
Mirror scope falls out of auth: the walker uses the local instance's
bearer, so it sees exactly what the user can see at upstream. Admin
bearer → full mirror; vendor bearer → vendor's permitted subtree;
no code distinguishes the cases.
New flags (also as ZDDC_* env vars), ignored when --mode != mirror:
- --mirror-subtree <csv> — repeatable subtrees (comma-separated);
empty + --mode=mirror = "/" (full mirror)
- --mirror-min-interval <duration> — default 1h
Tests (15 new in walker_test.go, 3 new in cache_test.go): subtree
normalization, longest-prefix matching, root-as-catch-all, walk
fetches all files in scope, out-of-scope URLs are no-op, rate-
limiting prevents double-walks within min-interval, walks re-fire
after interval elapses, orphan purge removes local-only files,
state file survives restart, concurrent triggers don't double-walk,
end-to-end ServeHTTP-kicks-mirror-on-access, listing format varies
by Accept, listing offline serves stale, persisted state atomic
write + corrupt-input handling. Full suite + go vet clean.
Doc updates: zddc/README.md flags table gains the two new entries
plus a "Mirror mode (access-triggered subtree walker)" subsection
with trigger semantics and properties; the "What client mode is NOT,
yet" list shrinks accordingly. AGENTS.md env-var table gains the
two new entries. ARCHITECTURE.md "Master + proxy/cache/mirror"
section now documents the walker scheduler / walk algorithm / state
file in a "Mirror walker (access-triggered)" subsection.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
|||
| ca00904f1e |
feat(client): cache mode — on-demand fetch + persist + offline fallback
zddc-server can now run as a downstream client of another zddc-server. Set --upstream <url> and the master-side machinery (archive index, apps server, watcher, OPA decider, ACL middleware, token store) is bypassed entirely; cmd/zddc-server/main.go short-circuits to runClient(cfg) which uses zddc/internal/cache/Cache as the entire request handler. Three modes via --mode <proxy|cache|mirror>: - proxy: forward upstream live, no disk persistence - cache (default): persist responses on access; subsequent hits serve from disk + background If-Modified-Since revalidate - mirror: accepted but currently behaves like cache; the access- triggered walker lands in phase 3 Cache directory layout is intentionally a normal ZDDC root: a file fetched from <master>/foo/bar.txt is stored at <root>/foo/bar.txt with no sidecar metadata. The local file's mtime is set to the upstream's Last-Modified header so revalidation reflects the master's notion of file age, not local fetch time. Running zddc-server --root <cache-dir> without --upstream serves the cached files as a plain master — useful for portable offline snapshots. A small .zddc-upstream marker is written once on first persist for provenance. Pipeline (GET/HEAD only — writes deferred): - Hit → http.ServeContent serves directly (range-aware, 304-aware) + background revalidate (304 no-op, 200 overwrite, 403/404 purge) - Miss → forward to upstream with the configured bearer; tee response body to client + tmp-file atomically renamed into the cache - Network error + cached → serve stale + X-ZDDC-Cache: offline - Network error + no cache → 503 + X-ZDDC-Cache: offline - Directories always proxy live (no listing cache yet — phase 3) - Cache-Control: no-store / private and non-200 responses bypass cache Range requests work end-to-end (Range/If-Range headers forwarded on miss; http.ServeContent handles them natively on hit). Hop-by-hop headers per RFC 7230 §6.1 are dropped from forwarded responses. New flags (also as ZDDC_* env vars), all ignored when --upstream is empty (so master deployments are untouched): - --upstream <url> - --mode proxy|cache|mirror (default cache) - --bearer-file <path> (0600 file with the master-issued token) - --skip-tls-verify (separate from --no-auth; for self-signed dev) Validation: --upstream must be http(s)://...; trailing / is trimmed. Mode validated to one of the three known values. The startup no-root-.zddc check is skipped in client mode (the cache directory starts empty by design). The plain-HTTP-on-non-loopback check is also skipped (the local instance never reads the email header to decide anything; auth is forwarded to upstream as a Bearer). Tests: zddc/internal/cache/cache_test.go runs httptest.NewServer as the upstream and covers miss-then-hit, proxy-mode-no-persist, directory-never-cached, HEAD-no-body, offline-with-cache, offline-no-cache → 503, bearer forwarding, query-string preservation, no-store bypass, path-traversal rejection, error-status forwarding, revalidate-on-403/404/200/304, range-on-hit, concurrent-same-URL, cache-path boundary cases. 23 new tests, full suite + go vet clean. Live two-instance smoke verified: master at 127.0.0.1:18443, client at :18444 with --mode cache, miss→hit→hit transitions work, file materialises under cache root with parent dirs created, marker file written once, range-on-hit returns 206, master sees background 304s on every hit, killing master leaves cached files serving from disk and never-cached files returning 503 + offline header. Doc updates: zddc/README.md gains a "Client mode" section with the modes table, flag reference, pipeline summary, two-instance recipe, and explicit list of phase-2 limitations; AGENTS.md adds the four new env vars to the reference table and a "Client mode" subsection with smoke-test recipe and a pointer to the cache package; ARCHITECTURE.md adds "Master + proxy/cache/mirror" before "Bearer token issuance," covering the topology, the persist/warm switches, the cache-IS-a-ZDDC-root invariant, the request pipeline, and the v1-out-of-scope multi-tenancy note; CLAUDE.md's zddc/ entry expanded to mention both deployment shapes so future agents pick it up by default. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
|||
| 97ffaac13b |
feat(server): self-issued bearer tokens + --no-auth flag
zddc-server now issues its own bearer tokens for non-browser callers (CLI tools, scripts, downstream proxy/cache/mirror instances). No external IDP, no JWKS rotation. Self-service flow: sign in via the browser, visit /.tokens, click "Create token," paste the resulting plaintext into a 0600 file, and pass --bearer-file <path> to whatever calls back into the server. Storage is <ZDDC_ROOT>/.zddc.d/tokens/<sha256-hex>, YAML per token with email/created/expires/description. Filename is the *hash* of the plaintext, never the plaintext itself — a leak of the tokens directory exposes hashes, not credentials. Mode 0600 / 0700, atomic writes via temp+rename. Already shielded from public serving by the existing dot-prefix guards in dispatch and fs.ListDirectory. ACLMiddleware now recognises Authorization: Bearer <token>. On valid token, sets the request email from the token file and falls through to the existing ACL chain. On any failure (unknown / expired / store unavailable / Bearer with no validator), returns 401 — no silent fallback to anonymous, so a misconfigured client fails loudly. JSON API at /.api/tokens (GET list, POST create, DELETE /<id> revoke) backs a small inline HTML self-service page at /.tokens. Users can only see and revoke their own tokens; cross-user revoke returns 404 to avoid leaking ownership. --no-auth (ZDDC_NO_AUTH=1) skips ACL enforcement entirely on this instance. On master: anyone reads everything (dev / trusted-LAN / public-read deployments). On a downstream proxy/cache/mirror: trust upstream's filtering, don't re-evaluate ACLs locally. Implemented as a swap to policy.AllowAllDecider; all existing handlers keep calling AllowFromChain unchanged. Distinct from --insecure, which only relaxes the no-root-.zddc startup check. WARN-level startup log when --no-auth is active so accidental enablement is visible. 33 new tests covering token storage, validation/expiry/revocation, the JSON API end-to-end, the HTML page, and the middleware-Bearer integration including the case-insensitive prefix and expired-token paths. Full suite + go vet clean. Doc updates: zddc/README.md "Authentication" rewritten to cover both auth paths and the token UI/API; AGENTS.md gains ZDDC_NO_AUTH and a "Bearer tokens" subsection flagging the dot-prefix-shielding pre- condition; ARCHITECTURE.md adds "Bearer token issuance" and "--no-auth" subsections under "Server security model" with the hash-as-filename rationale and dispatch-shielding regression- sensitivity called out; CLAUDE.md adds a one-line summary of the new auth topology so future agents pick it up by default. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
|||
| 3115e388fc |
feat(server): authenticated CRUD + verb-based RBAC with WORM archive folders
Replaces the binary acl.allow/deny model with five permission verbs
(r/w/c/d/a) and first-class roles, and adds an authenticated file API
(PUT/DELETE/POST move/mkdir) so the HTML tools can edit-in-place over
HTTP. Closes the AC-3(7) and AC-6 federal-readiness gaps.
File API (zddc/internal/handler/fileapi.go)
- PUT <new> → action c
- PUT <existing> → action w
- PUT <.zddc> → action a (CanEditZddc strict-ancestor rule)
- DELETE → action d
- POST mkdir → action c (auto-writes creator-owned .zddc when the
parent is Incoming/Working/Staging)
- POST move → action w on src + c on dst, atomic via os.Rename
- Optional If-Match for optimistic concurrency, --max-write-bytes cap,
audit log emits a structured file_write event per operation.
Permission model (zddc/internal/zddc/{acl,file,roles,cascade_mode}.go)
- acl.permissions: { principal → verb-set } map; principals are email
patterns or role names. Empty verb set is an explicit deny.
- roles: { name → members } definitions, available at the level they
declare and all descendants. Closer-to-leaf shadows ancestor.
- Legacy acl.allow/deny still work; they fold into permissions at
parse time (allow → "rwcd", deny → "").
- Cascade walks leaf→root; first level with any matching entry wins;
the union of matching verb sets at that level decides.
- --cascade-mode=strict adds a root→leaf ancestor-deny pre-pass so an
ancestor explicit-deny is absolute (NIST AC-6). Default delegated
preserves the existing commercial behavior.
Special folders (zddc/internal/zddc/special.go)
- Incoming / Working / Staging: mkdir auto-writes a .zddc into the new
subdir granting created_by + that email rwcda directly. Same form
operators write by hand; creator can edit it later to add others.
- Issued / Received: server-enforced WORM split. Cascade grants
inherited from above the WORM folder are masked to r only; grants
placed at-or-below the WORM folder retain r,c. Operators grant
write-once (cr) to the doc controller via an explicit .zddc at the
Issued/Received folder. Admins exempt — only escape hatch.
Browser polyfill (shared/zddc-source.js)
- HttpDirectoryHandle + HttpFileHandle implement the FS Access API
surface (values, getFileHandle, createWritable, removeEntry,
queryPermission/requestPermission) over zddc-server's listing JSON
and file API. Existing tools written against showDirectoryPicker
work unchanged.
- detectServerRoot() returns { handle, status }: tools auto-load on
HTTP, surface a clear "no permission to list" message on 403, and
fall back to the welcome screen on 0.
- classifier renames take the atomic POST move path on HTTP-backed
handles; mdedit and transmittal route reads/writes through the
polyfill so prior FS-API code paths cover both modes.
Tests
- zddc/internal/zddc/{cascade_mode,roles,special,acl}_test.go cover
delegated vs strict, role membership / shadowing / legacy fallback,
WORM split semantics, verb-set parser round-trip.
- zddc/internal/handler/fileapi_test.go now also covers role-based
vendor scenarios, WORM blocking vendor & doc controller writes,
explicit Issued .zddc unlocking the cr drop-box, admin bypass,
auto-ownership on mkdir, and strict-mode lockouts.
Docs
- ARCHITECTURE.md + zddc/README.md document the verb model, role
syntax, special-folder behaviors, cascade-mode flag, and full file
API surface. Federal-readiness gap analysis strikes AC-3(7) and
AC-6.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
|||
| 5c33c8a821 |
docs: ACL/security overhaul (cascade rules, OPA, caching)
Three docs aligned with the preceding three feature commits. zddc/README.md -------------- Major overhaul of the access-control narrative. The previous "three- tier" example table was misleading: it claimed a project-level allow-list "restricts" access under a parent wildcard, when actually the cascade is additive (a non-team employee falls up to root and matches *@company.com). Operators reading the old docs would build deployments that looked locked-down but leaked across the company. New sections under "Access control: the .zddc cascade": * Step 1: starter .zddc — leads with the public-by-default warning and the --insecure escape hatch * How a request is evaluated — bottom-up walk with code citations * Glob patterns — @-boundary rule * When the cascade helps and when it fights you — the asymmetry between adding strangers (easy) and excluding insiders (hard) * Pick your layout — decision matrix for common shapes * Worked example: paired open/closed projects + third-party archive — full layout with trace table for two representative users * Patterns that look secure but aren't — anti-patterns including same-level allow+deny shadow, leaf-allow-doesn't-restrict, apps:-as-UI-mount * Trust model and invariants — auth boundary, subtree authority, root-only escalation gate * Trust boundary — network isolation requirement, anonymous information disclosure on /, audit-log integrity * Debugging permissions — manual cascade trace * Directory visibility / Reserved hidden segments * How to verify in 5 minutes — recipe with negative anti-pattern test * Federal-readiness gap analysis — bulleted with NIST control refs * External policy decider — OPA wire format, deployment shapes, failure modes * OPA decision cache — TTL semantics, knobs * Reference Rego policy — --print-rego, parity test rationale * Caching and ETags — content-hash story, why not server-side * Future work Plus env-var table updates for ZDDC_INSECURE, ZDDC_OPA_URL, ZDDC_OPA_FAIL_OPEN, ZDDC_OPA_CACHE_TTL; CORS narrative reflects default-empty. ARCHITECTURE.md --------------- New "Server security model" section between Form Renderer and CSS: cooperating layers (auth / policy decider / cascade / tool-rooted view / reserved prefixes / audit log), commercial-vs-federal trust model side-by-side, why the tool-rooted view matters for third-party containment. AGENTS.md --------- Two new env-var rows (ZDDC_OPA_URL, ZDDC_OPA_CACHE_TTL); ACL line sharpened with cascade rules + cross-reference; ZDDC_CORS_ORIGIN description updated for default-empty. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
|||
| a02a26d3c2 |
feat: form-data system v0 (sixth tool + zddc-server endpoints)
All checks were successful
Build + deploy releases / build-and-deploy (push) Successful in 8s
Schema-driven form renderer plus zddc-server endpoints that turn any
<name>.form.yaml into a working data-collection form at <path>/<name>.form.html.
Submissions land in <path>/<name>/<YYYY-MM-DD>-<email-sanitized>.yaml,
ACL-gated by the existing .zddc cascade. The form posts back to its own URL;
the server strips ".html" and routes by what's underneath, so create and
update use the same client-side code path.
Form spec dialect: JSON Schema 2020-12 + RJSF-style ui:* hints, written in
YAML. Chosen for LLM authorability — it's the canonical structured-output
target for OpenAI/Anthropic, and the ui:* convention is the most-trained UI
hint vocabulary. Supported subset for v0: type (string/number/integer/boolean/
array/object), enum, min/max, minLength/maxLength, required, additionalProperties:
false, properties, items, format (date, email). Round-trip mode is form-as-truth:
submission YAML is regenerated each save, comments are not preserved (the v1
file-as-truth mode for hand-edited files like .zddc itself is deferred).
New components:
* form/ — sixth single-file HTML tool, vanilla JS renderer (~760 LoC)
* zddc/internal/jsonschema/ — focused JSON Schema validator covering only
the v0 keyword subset. Match-implementation-cost-to-surface-used: a full
library brings 70%+ surface we don't use; revisit when v1 adds $ref +
oneOf + if/then/else.
* zddc/internal/handler/formhandler.go — RecognizeFormRequest / ServeForm,
capability-URL re-edit, atomic submission writes via the new
zddc.WriteAtomic helper extracted from writer.go.
* dispatch() in zddc-server/main.go now intercepts *.form.html and
*.yaml.html before the static-file path; spec existence is the trigger.
Build pipeline: form joins ZDDC_RELEASE_TOOLS in lockstep, gets its own
embedded copy in handler/form.html (separate from the apps cascade —
the form renderer is fixed, not subject to per-folder version overrides).
Tests: 5 new Playwright specs (form-safety) + 14 new Go tests across the
validator and handler. All 172 Playwright tests + 10 Go packages green.
End-to-end manual verification: GET empty → POST 201 + capability URL →
GET re-edit (pre-filled) → POST update → 200, raw YAML browsable, ACL
deny → 403.
Docs: form/ section added to AGENTS.md and ARCHITECTURE.md. AGENTS.md
also documents the implementation-vs-dependency policy. CLAUDE.md repo-shape
list extended.
Deferred (v1+): .zddc editor migration onto this system, file-as-truth
lossless YAML round-trip, ui:show-when conditional visibility, oneOf/anyOf,
apps-cascade preview hook, cascade-fetched form definitions.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
|||
| 7570fb7494 |
refactor: separate website repo + deploy-host model
Migrates from in-repo orphan `website` branch + LFS to a two-repo +
deploy-host model so source editing is fully decoupled from live state.
- Source code stays here (codeberg.org/VARASYS/ZDDC).
- Hand-edited website content moves to a separate Codeberg repo
(codeberg.org/VARASYS/ZDDC-website, cloned at ~/src/zddc-website/).
- Live site is /srv/zddc/ on the deploy host (Caddy bind-mount),
populated by ./deploy from this repo's dist/release-output/ plus
~/src/zddc-website/.
- Releases are no longer in any git history — reproducible from
<tool>-vX.Y.Z tags via `./build release X.Y.Z`. No LFS, no
Codeberg release assets.
Build/deploy split:
- ./build (no arg) is source-only; nothing in dist/release-output/
or /srv/zddc/ is touched.
- ./build alpha|beta|release seeds dist/release-output/ from
/srv/zddc/releases/ (preserving symlinks), then mutates the
channel(s) being cut on top. The bundle is always a complete
intended-live snapshot, so the verifier sees a complete world
and ./deploy --releases (rsync --delete-after) replaces live
state cleanly.
- New ./deploy wraps the rsync flow with --content / --releases
subcommands.
Docs updated to reflect the new model: CLAUDE.md, AGENTS.md,
ARCHITECTURE.md, zddc/README.md, README.md, .gitignore, shared/
build-lib.sh comments, deprecated zddc/release.sh message.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
|||
| 6167e99f3a |
chore: simplify CLI to ./build / ./build beta / ./build release
Renames build.sh → build and replaces the --release flag form with
subcommands:
./build cut alpha (default; active dev iteration)
./build beta cut beta (cascades alpha → beta)
./build release cut stable (coordinated next version)
./build release X.Y.Z cut stable at explicit version
./build help
The contract shift: there's no longer a "plain dev build that doesn't
touch channels" at the top level. Every full-stack build is a publish
action — running ./build IS active dev iteration, which is what alpha
already meant. To iterate on one tool without writing to the website
worktree, use the per-tool sh tool/build.sh (unchanged).
Output continues to land in ${ZDDC_DEPLOY_RELEASES_DIR:-$HOME/src/zddc-website/releases}
and nothing is pushed automatically. Commit + push the website branch
yourself when you want to publish. Stable cuts still tag locally on
main; tags push separately too.
Behind the scenes: the export of ZDDC_DEPLOY_RELEASES_DIR is moved
above the per-tool build.sh invocations so children inherit it. The
prior "if RELEASE_CHANNEL else write_zddc_server_stubs_all" branch is
collapsed since RELEASE_CHANNEL is always set under the new CLI.
Docs (CLAUDE.md, AGENTS.md, ARCHITECTURE.md, zddc/README.md) updated
to reference ./build everywhere; the per-tool sh tool/build.sh refs
stay (they're a separate, narrower entry point).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
|||
| 76820fa8dd |
chore: split website out into orphan branch + worktree
Moves website source + release artifacts off `main` and into a new
orphan branch named `website` in this same Codeberg repo. A `git worktree`
of that branch — typically at ~/src/zddc-website/ — is what the system
Caddy now bind-mounts and serves at zddc.varasys.io. Decoupling source
from the live site means editing source can no longer accidentally
affect what's published.
Layout going forward:
- ~/src/zddc/ — main worktree (this branch, source only).
- ~/src/zddc-website/ — git worktree of the `website` branch:
hand-edited content + LFS-tracked release
artifacts (server binaries) + regular-git
HTML tool releases + symlinks.
- Caddy bind-mount swapped: ~/src/zddc/website → ~/src/zddc-website
(quadlet at /etc/containers/systemd/caddy.container, restarted).
Build pipeline now writes releases to
${ZDDC_DEPLOY_RELEASES_DIR:-$HOME/src/zddc-website/releases}.
- build.sh: RELEASES_DIR points at the env var
- shared/build-lib.sh: promote_release honors the env var, falls
back to the legacy in-repo path so any
standalone single-tool release on a checkout
that still has website/ keeps working
- freshen-channel: passes ZDDC_DEPLOY_RELEASES_DIR through to
the worktree-based build
Docs (CLAUDE.md, AGENTS.md, ARCHITECTURE.md, .gitignore) updated for
the new layout. The 51 MB of website/ blobs stays in main's history
(no force-push); over time Codeberg's GC will pack them down.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
|||
| 9fce18cd45 |
feat: lockstep release infra + cascade/.archive fixes + profile perf + page redesign
Four entangled change-sets from one session, committed together because
their file-level overlap (build.sh, docs, embedded/, watcher.go, …) makes
post-hoc separation noisy:
* fix(archive): nested-party + folder-type cascade
transmittalIsUnderVisibleParty short-circuited on the first matched
party segment, only checking the immediately-next segment for a
folder-type marker. Paths like BM/sub/Issued/<txn> bypassed the Issued
toggle entirely. Replaced with isUnderHiddenFolderType (full-path) +
any-segment party match. Eight new Playwright cases pin the contract
in tests/archive-cascade.spec.js.
* refactor(zddc-server): scope .archive index by project
archive.Index now buckets by top-level segment
(.ByProject[<project>].ByTracking[<tracking>]). Resolve and AllEntries
take a project parameter; handler extracts it from contextPath's first
segment. /.archive/ at root returns 404 — stable refs must be
project-rooted. Within-project (tracking, rev) collisions emit a WARN
with both paths. Cross-project tracking-number duplicates no longer
collide.
* perf(zddc-server): lazy-load expensive bits of the profile page
serveProfilePage now ships a minimal shell: Email, EmailHeader,
IsSuperAdmin (root .zddc only). Visible projects + admin subtrees +
editable scaffolds populate client-side via /.profile/access. Subtree-
admin scaffolds live in <template id="tmpl-subtree-admin">; pure
non-admins receive no live admin form. ScanZddcFiles now memoized,
invalidated on .zddc events by the watcher and writer helpers.
* feat: lockstep release + redesigned releases page
sh build.sh --release [version|alpha|beta] is the canonical lockstep
cut: every tool (5 HTML + zddc-server) bumps to the same coordinated
version. zddc-server binaries now committed under website/releases/
with the same cascade chain as HTML tools (no more Codeberg release-
asset publication). zddc/release.sh deprecated (kept as a guard);
shared/publish-codeberg-release.sh removed.
Releases page redesigned as an action-first install guide: hero +
version dropdown that rewires every download link, channel chips for
always-visible alpha/beta access (state-aware labels: "tracks stable"
vs "active dev"), Path A (zddc-server with platform auto-detect from
UA), Path B (5 standalone tool HTMLs), version-pinning empowerment
narrative (drop-a-copy vs .zddc apps: cascade), channels explainer.
Channel-link verifier asserts every <tool>_{stable,beta,alpha}.html
resolves at the end of every build. Bootstrap-friendly: zddc-server
artifact checks skip until the first lockstep cut anchors the chain.
Tests: 167 Playwright + all Go packages green.
Docs: CLAUDE.md, AGENTS.md, ARCHITECTURE.md, zddc/README.md updated.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
|||
| adb6904397 |
docs: rewrite for embedded + cascade install model
Updates every repo doc to reflect the simplified install model:
- Local install is just a download from /releases/.
- Server install is just running zddc-server (current-stable HTMLs
embedded at compile time).
- Customize via .zddc apps: cascade entries (channel/version/URL/path,
with default + per-app composition); editor at /.profile/zddc/.
Removes references to the old install scripts, level-1/level-2 stubs,
admin UI at /.profile/apps, SHA-256 verification, TOFU writes, refresh
worker, and ZDDC_APPS_* env vars.
zddc/README.md: replaces "Landing Page and Tool Install" section with
"Apps: virtual tool HTMLs" — covers the folder-name availability rules,
the resolution chain (real-file override / cascade / embedded), spec
syntax cheat sheet, cache layout under <ZDDC_ROOT>/_app/, the ?v=
cache-only override, and the X-ZDDC-Source response header.
ARCHITECTURE.md: install-distribution-model section rewritten to
describe the embed-first / cascade-override model with one canonical
example.
AGENTS.md, CLAUDE.md: short-form summaries pointing at the same model.
README.md: install bullet rewritten.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
|||
| 7365e94cac |
docs: align with simplified release model
Updates to all six top-level docs to describe the new flow:
- Storage: HTML tools live in website/releases/ as committed static
files. Per-version files are real bytes; partial-version pins and
channel mirrors are checked-in symlinks. No manifest.json, no Codeberg
indirection, no Caddy regex-rewrite.
- URL scheme: <tool>_v<X.Y.Z>.html (exact), <tool>_v<X.Y>.html (latest
patch), <tool>_v<X>.html (latest minor), <tool>_<channel>.html
(channel mirror). All resolve via the symlink chain.
- Cascade rule: stable cut → beta + alpha symlinks reset to stable;
beta cut → alpha resets to beta. Channels are never stale.
- No -alpha.N / -beta.N counter tags. Channel URLs are stable URLs by
design; counters defeat that. The on-page <date> · <sha> label is
enough for traceability.
- bootstrap/install.sh is the canonical install path. The four hand-
rolled snippets are gone; one script handles all three deployment
patterns + both target shapes.
- Helm charts under helm/ (zddc-server-{prod,dev}/) build from source
via init container; documented as the recommended k8s deployment
path.
- zddc-server now publishes binaries on stable cuts only — no alpha/
beta channel for binaries. Active dev runs through the dev helm chart
which builds from source on each rollout.
Files updated:
- CLAUDE.md — Repo shape, Most-used commands, Things that bite if you
forget. Drops mentions of manifest.json, the Codeberg-as-canonical
model, and -alpha.N/-beta.N tags.
- AGENTS.md — website/ tree, Releasing — channels and layout, Channel
discipline rules (renumbered to add coordinated minor/major bump
rule), Freshen helper, Bootstrap stubs, zddc-server Release tagging.
- ARCHITECTURE.md — website/ tree, build.sh step 5, Channels section,
level-2 bootstrap description.
- README.md — tool publishing description, link to helm/.
- bootstrap/README.md — install path is install.sh now; pin URL table
uses static symlinks; CORS check uses release-asset URLs (not
manifest.json).
- zddc/README.md — Quick Start uses Codeberg URLs directly (no proxy);
Release tagging is stable-only; Distribution / Versioning sections
rewritten.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
|||
| bdac8dc4fb |
docs: clean up drift left over from the Codeberg release-assets refactor
The
|
|||
| 916e53d873 |
feat(install): replace .zip downloads with copy-paste shell snippets
The "Install on your server" section of the home page now prints four
short shell snippets — copy-paste into a terminal, files land in CWD.
Each uses curl to fetch the relevant bootstrap files; nothing else to
install:
1. Self-contained: fetches the 5 current-stable tool HTMLs into CWD
plus a _template/ directory of level-1 stubs.
~1.8 MB on disk; no runtime dependency on the
site after install.
2. Track stable: fetches 5 tiny level-2 stubs (~10 KB total)
that fetch zddc.varasys.io's stable channel
on every page load.
3. Track beta: same, for beta.
4. Track alpha: same, for alpha.
Each snippet card explains when/why to use that option directly inline.
Implementation:
- build.sh now produces website/bootstrap/level1/<tool>.html and
website/bootstrap/track-{alpha,beta,stable}/<tool>.html as
standalone files (rather than packaging them into zips).
- install.zip and track-{alpha,beta,stable}.zip are removed; the
snippets curl the per-channel stubs directly.
- Docs updated: README, ARCHITECTURE, CLAUDE, AGENTS, bootstrap/README,
zddc/README, landing/build.sh comment.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
|||
| 91d6e61e22 |
feat(web): releases index, alpha+beta channel builds, inline server section
Three things on the public website:
1) Cut alpha and beta channel builds for all five tools, so each tool
now has stable + beta + alpha actually published — previously
beta and alpha were vapor for archive (which had been freshened
earlier) and missing entirely for the others. The intro page's
tool cards now point at real artifacts on every channel.
2) New website/releases/index.html — a generated index of every
version + channel of every tool, with stable/beta/alpha pill
links per tool and a "Pin to version" row of every concrete
v0.0.X build. Regenerated by build.sh's new build_releases_index
function (reads the filesystem so it is always consistent with
what is actually under releases/). Linked from the intro page nav
(Releases), from the bottom of the Try the tools section
("Browse all versions"), and from the Learn more list.
reference.html's nav gets the same Releases link.
3) Folded website/zddc-server.html into website/index.html as a new
inline section ("zddc-server (optional)") below the tool cards.
The earlier separate page is removed; the broken Server nav link
that pointed at it is gone too. The new section leads with the
dual-mode insight (the tools work locally on a folder OR via any
web server, including the optional zddc-server) and frames
zddc-server as a small Go binary that adds things a generic web
server cannot: ACL via .zddc files, virtual .archive URL space,
per-request access logging, mundane glue. The What is it?
paragraph also mentions the dual-mode story up front so users
reading top-to-bottom get the framing before they hit the cards.
Also caught two stale _latest.html refs missed by the earlier
rename sweep: 8 tool links in reference.html and a comment line in
CLAUDE.md. Verified with a full link audit — every relative href in
index.html, reference.html, and releases/index.html now resolves to
an existing file under website/.
ARCHITECTURE.md doc-ownership table updated: zddc-server.html row
removed; new row added for the regenerated releases/index.html.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
|||
| 67f794e6d0 |
refactor: rename channel 'latest' to 'stable' across all artifacts
The 'latest' label for the current-stable channel was inconsistent
with the channel set we use elsewhere (alpha / beta / stable). Rename
to 'stable' so URLs, file names, zip names, and image tags all line
up with the channel terminology used in the bootstrap, AGENTS.md
discipline rules, and chart consumers.
File / artifact renames
- website/releases/<tool>_latest.html → <tool>_stable.html (5 files)
- website/track-latest.zip → track-stable.zip
- shared/build-lib.sh: promote_release writes/refreshes _stable.html
- bootstrap/level{1,2}.html.tmpl: channels map drops 'latest', keeps
'stable' as the canonical name. ?v=stable is now the explicit way
to switch to current-stable for one request (alongside ?v=alpha,
?v=beta, and ?v=X.Y.Z).
- build.sh: install.zip sources from <tool>_stable.html; emits
track-stable.zip instead of track-latest.zip.
Container image (.woodpecker.yml rewritten)
- Tag publishing now cascades:
zddc-server-vX.Y.Z → :X.Y.Z, :stable, :beta, :alpha, :latest
zddc-server-vX.Y.Z-beta.N → :X.Y.Z-beta.N, :beta, :alpha
zddc-server-vX.Y.Z-alpha.N → :X.Y.Z-alpha.N, :alpha
- :stable, :beta, :alpha are now first-class channel pointers; chart
consumers (e.g. tnd-zddc-chart) can FROM :beta for dev and FROM
:stable for prod.
- :latest kept as an alias for :stable per Docker convention.
Documentation sweep
- AGENTS.md, ARCHITECTURE.md, CLAUDE.md, README.md
- bootstrap/README.md, zddc/README.md
- website/index.html, website/zddc-server.html
- transmittal/template.html, transmittal/README.md
all updated to reference _stable.html / track-stable.zip / the
'stable' channel name. ARCHITECTURE.md's manual freshen example
points at ./freshen-channel instead of the old git-checkout snippet.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
|||
| 699069e538 |
docs: add zddc-server.html — local vs online mode, what the server adds
The intro page's "zddc-server" link previously pointed at a Codeberg
blob URL (which uses /src/branch/main/, not GitHub's /blob/main/, so
the link 404'd anyway). Replace with a hand-edited concept page on
the website itself.
The page is structured around two access modes:
- Local directory mode — open a tool, point it at a folder, work
via the File System Access API. No upload, no server.
- Online mode — take that same local directory and put it behind
any web server (nginx, Caddy, Apache, even python -m http.server).
The Archive Browser tool works against the server's directory
listings the same way it works against a local folder.
zddc-server is then introduced as a Go binary that gives you online
mode out of the box, plus four conveniences a generic web server
can't: ACL via .zddc YAML files (gated on email-header trust),
virtual /.archive/ URL space, per-request access logging, and the
mundane glue (TLS, ETags, conditional GET, CORS).
Closing section: the on-disk layout is the same in both modes — the
server doesn't transform the archive, it serves it. Stop the server
and the directory is still a valid ZDDC archive. The "Zero Day"
promise: server is convenience, not lock-in.
Also:
- Add Server nav link to website/index.html and reference.html.
- Fix the bootstrap/README.md link that used GitHub's /blob/main/
pattern (Codeberg uses /src/branch/main/).
- Update ARCHITECTURE.md doc-ownership table: new row for the concept
page, clarify that zddc/README.md is the operations reference.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
|||
| ea385b5366 |
Initial commit
ZDDC — Zero Day Document Control. A file-naming convention plus five single-file HTML tools (archive, transmittal, classifier, mdedit, landing) and an optional Go HTTP server (zddc-server) with ACL and a virtual archive index. Self-contained, offline-capable, dependency-free. See README.md for an overview, AGENTS.md and ARCHITECTURE.md for the build/release/architecture detail, bootstrap/README.md for the two-level deployment install pattern, and zddc/README.md for the HTTP server. |