Commit graph

9 commits

Author SHA1 Message Date
14f8780dc5 fix(tables,profile): define spacing tokens (gutters + cell padding); profile rows open the .zddc form
The tables tool referenced --spacing-sm/md/lg (14×) but they were never
defined and used no fallbacks, so every padding/margin/gap collapsed to 0 —
table cells had no vertical padding and the table sat flush to the viewport
edges. Define --spacing-sm/md/lg (+ alias the --color-*/--radius-sm names the
tool uses) in shared/base.css, and give .table-main a clear left/right gutter
(padding: md lg). Fixes every tables view (profile/tokens/diagnostics/mdl).

Profile: clicking a project (or admin-subtree) row now opens that scope's
.zddc INFO FORM in the browse editor (via the ?file=.zddc deep link →
selects + previews the .zddc → schema-driven Title/Roles/Admins form),
instead of navigating into the project's files. Diagnostic rows still link
to their endpoints.

Validated in a containerized browser: 24px side gutters + padded rows;
clicking Proj → /Proj/?file=.zddc → the .zddc form editor. Full suite green.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-07 08:13:54 -05:00
d183de434d feat(profile): render the admin diagnostics (config/logs/whoami) as chrome'd tables
The profile page links to /.profile/{config,logs,whoami}, which returned raw
JSON — so a browser click landed on raw JSON. Render them through the tables
engine instead (header chrome + sortable/filterable columns), content-
negotiated: browsers (Accept: text/html) get the table; scripts (Accept:
application/json) still get the unchanged JSON. New serveDiagTable helper +
kvRow/kvColumns: logs → time/level/message/detail rows (newest first);
config + whoami → Field/Value rows. Dropped the deep effective-policy row
from the profile table (kept JSON-only, not linked).

Extends api-actions.js with a `readOnly` context flag so a server-injected
read-only table (no apiActions) still hides the file-model toolbar buttons
(+ Add row / Save). Export CSV stays.

Completes the bespoke-server-page → tables-engine consolidation: tokens,
profile, and the three admin diagnostics now all render declaratively with
shared chrome; per-role gating stays server-side (diagnostics are elevated-
super-admin only). Full Go suite green; verified in a containerized browser.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-06 18:23:23 -05:00
d9256050d2 feat(profile): render /.profile via the tables engine (access + create + diagnostics)
Retire the bespoke profile page. /.profile now renders through the shared
tables engine (header chrome incl. the profile menu) from a server-injected
context: the caller's "Effective access" — projects + admin subtrees — as
clickable rows (rowNav opens each), identity in the description, and an
apiActions "+ New project" (name → POST /.profile/projects, gated on
can_create_project; roles are added afterward by editing the project's
.zddc, which is now standing-editable). Super-admin diagnostics
(config/logs/whoami/effective-policy) stay discoverable as rows linking to
their unchanged endpoints — gated on IsSuperAdmin so a non-admin's context
never even names them.

Dropped as redundant/niche: the in-page theme picker (the header has the
theme button), the localStorage inspector, and the "editable .zddc" links
(those files are now standing-editable in browse).

Extends the generic apiActions layer (tables/js/api-actions.js) with
`fixed` constant fields (e.g. parent="/"), `required` field validation, and
`rowNav` clickable rows (capture-phase, so it beats the editor's per-cell
handlers). Rewrote TestServeProfileHTMLLayered to the new model (per-role
context correctness: no admin leak; super-admin diagnostics present) and
dropped the now-dead stripTemplates helper. Validated in a containerized
browser; full Go suite green.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-06 16:07:56 -05:00
fbe9d11f22 feat(profile): project-create — drop parent picker, add role groups, record creator
Projects are always created at the deployment root, so the "Parent" dropdown
(and populateParentChoices) is gone — the client always POSTs parent:"/".

The Create-new-project dialog now collects members for the four project roles
— admins, document controllers, project team, guests — as simple email lists.
Server-side, each non-empty list becomes a roles:<name> entry plus a base
acl.permissions grant (document_controller→rwcd, project_team→rwc, guest→r);
an explicit advanced acl.permissions entry for the same key still wins.

The new project's .zddc now always records the creator: zf.CreatedBy = creator
email, and the creator is always included in admins: (deduped, first) so they
administer their own project from birth.

Tests: creator recorded + roles/permissions seeded; explicit permission
overrides the role default. Existing create tests still pass (creator-in-admins
is compatible with the explicit-admins-list case).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-05 08:55:55 -05:00
f196205622 refactor(audit): pre-release cleanup pass
Single audit pass that removes pre-release back-compat, consolidates the
admin-policy decider, and fixes the .zddc write path.

Field removal — acl.allow / acl.deny:
- Drop ACLRules.Allow / Deny struct fields and mergeLegacyACL().
- Remove walker / lookups / validate / decider branches that read them.
- Migrate every test fixture (YAML strings and ACLRules struct literals)
  to acl.permissions: { principal → verb-set }.
- Rewrite both bundled Rego policies (access.rego, access_federal.rego)
  to traverse level.acl.permissions; rewrite parity-test helpers.
- Update create-project form (profile page) to collect permissions
  instead of allow/deny lists.

Admin decider consolidation:
- Delete zddc.CanEditZddc — strict-ancestor rule retired. Subtree admins
  own their own .zddc; the policy decider's IsActiveAdmin short-circuit
  is the single bypass site.
- Migrate tablehandler.ServeTable to AllowActionFromChainP — closes the
  same Forbidden bug already fixed for /browse.html.
- Drop AccessView.EditableParentChoices and treeEntry.CanEdit (always
  true after the retirement). Profile page renders AdminSubtrees
  directly for both lists.
- Drop the excludeLeaf parameter from AdminLevelInChain /
  IsAdminForChain — no production caller passed true.

Dead code removed:
- policy.AllowWriteFromChain (zero production callers, zero tests).
- zddc.AllowedWithChain (zero production callers; tests deleted).

ModeStrict retirement — federal posture is OPA-only:
- Delete cascade_mode.go / cascade_mode_test.go and the ModeStrict
  branches in cascade.go and acl.go.
- Drop --cascade-mode flag, CascadeMode config field, and the
  InternalDecider.Mode field.
- Drop the mode parameter from every cascade helper:
  GrantedVerbsAtLevel, AllowedAction, EffectiveVerbs,
  EffectiveVerbsRange, RoleMembers, MatchesPrincipal,
  MatchingPrincipals, WormZoneGrant, PolicyChain.VisibleStart.
- Strip cascade_mode from /.profile/config and
  /.profile/effective-policy responses.
- Refresh README / ARCHITECTURE.md to describe federal posture as
  "deploy OPA with access_federal.rego" (NIST AC-6); the bundled Rego
  is the parent-deny-is-absolute variant. The in-process Go evaluator
  implements only the commercial cascade.

Legacy redirects + .admin.css fallback:
- Drop /<dir>/.zddc.html → ?file=.zddc redirect and its test.
- Drop ?zip=1 retired comment + legacy test (handled by the
  .zip virtual-URL path; covered by TestServeSubtreeZip).
- Drop .admin.css fallback in profile_assets.go — only .profile.css now.
- Refresh stale "retired" / "back-compat" / "legacy" comment markers.

.zddc write path fix:
- Dispatcher: route only GET/HEAD on .zddc URLs to ServeZddcFile; carve
  .zddc out of the dot-prefix guard so PUT/DELETE/POST reach
  ServeFileAPI. Before this, .zddc writes 405'd at ServeZddcFile and
  the YAML editor's save flow had no live path.
- ServeFileAPI.resolveTargetPath: same .zddc-leaf carve-out so the file
  API accepts the path; intermediate dot dirs (.zddc.d/) stay reserved.
- Listing: compute Writable per-file with ActionAdmin for .zddc
  (matches the file API's gate) instead of ActionWrite for everything.
- Virtual .zddc placeholder: compute Writable via the same
  parentActiveAdmin || ActionAdmin path. Was always false before.
- browse YAML editor canSave: exempt virtual .zddc — the synthetic
  body is designed to materialize on PUT.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-18 16:28:07 -05:00
b80b11c99f feat: project creation gated by cascade ActionCreate, not hardcoded admin
The /.profile/projects endpoint previously refused anyone without
hasAnyAdminScope. Now it runs the standard decider with ActionCreate
on the parent directory — super-admins still pass via the
IsActiveAdmin bypass branch, and anyone the root .zddc grants `c`
to (e.g. `*@example.com: c`) can self-service a project without
needing an existing admin grant.

Other changes in this commit:

- The new project's .zddc is seeded with the creator's email in
  admins: when the request body doesn't supply one — they become
  subtree admin of their own project at birth. .zddc edits in
  deeper subfolders flow through their authority; strict-ancestor
  rule still prevents them from editing /<project>/.zddc itself.

- AccessView gains can_create_project, computed by the same decider
  call the endpoint uses — UI and server agree on visibility with
  no daylight.

- Profile page splits the subtree-admin template from the create-
  project template so the latter mounts on can_create_project,
  independent of has_any_admin_scope. Non-admin grantees see the
  form; admins keep seeing both.

- Lock-in tests cover the five interesting cases: cascade-granted
  user succeeds and becomes subtree admin; stranger gets 404;
  elevated super-admin auto-defaults admins; explicit admins list
  wins over the default; duplicate-name 409.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-18 10:25:19 -05:00
2d114fcb96 refactor: unified listing protocol + form-editor retirement + admin elevation
Three coordinated changes that share the same files. Common theme:
convention beats exception. Where the codebase had a bespoke wire shape
or a special-case route, replace it with the generic shape every other
client already speaks.

== Listing protocol ==

GET / Accept: application/json used to dispatch to a bespoke
ServeProjectList handler returning {name, url, title} per project — a
shape that diverged from every other directory's listing.FileInfo
response. Now:

- listing.FileInfo gains an optional `title` field (read from each
  directory's own .zddc title:). Generic clients (landing, browse)
  read the same shape from every URL.
- appfs.ListDirectory emits a virtual `.zddc` entry (is_dir:false,
  virtual:true) when no on-disk file exists at that path and the
  caller asked for ?hidden=1. Opens an editable view of the cascade
  defaults; PUT-saving its bytes materialises a real file.
- The bespoke GET / JSON branch in cmd/zddc-server/main.go is gone.
  The bare-root landing serve is Accept-gated: HTML requests get the
  landing tool (project picker), JSON requests fall through to
  ServeDirectory and get the generic listing.
- landing's fetchProjects filters the new generic shape (is_dir,
  strip trailing slash) — same pattern fetchParties already used at
  /<project>/archive/.

== Form editor retirement ==

`<dir>/.zddc.html` was a server-rendered form for editing per-directory
.zddc files (~900 LOC across zddceditor.go, zddchandler.go, zddc_assets.go).
Browse's YAML/CodeMirror editor (with .zddc-schema lint) already edits
the same files via the generic file-API. Two ways to edit the same data
is exception, not convention.

- Delete zddceditor.go, zddchandler.go, zddc_assets.go and tests.
- `/<dir>/.zddc.html` → 302 redirect to `/<dir>/?file=.zddc` (browse
  opens the .zddc in its editor pane).
- /.profile/zddc/* namespace deleted (REST API + assets sub-route).
- Profile page's "Editable .zddc files" list links to browse.
- ServeZddcFile's 405 message + virtual-body comment point at the
  browse URL instead of the dead form.

== Admin elevation (Principal model) ==

Sudo-style: admins are treated as normal users by default; opting into
admin powers is per-request and gated by a `zddc-elevate=1` cookie.

- zddc.Principal{Email, Elevated} replaces bare-email arguments on
  IsAdmin / IsSubtreeAdmin / CanEditZddc. The signature change makes
  the elevation gate compiler-enforced at every admin call site —
  audit-fragility is gone. The empty-email short-circuit is no longer
  load-bearing for elevation; Principal.gate() is the explicit check.
- handler.ACLMiddleware derives Elevated per request: bearer tokens
  are implicitly elevated (CLI clients can't toggle a cookie); browser
  sessions elevate only when zddc-elevate=1 is set. PrincipalFromContext(r)
  is the one-call-per-site bundling helper.
- Every admin-check call site updated to pass a Principal.
- /.auth/admin (forward_auth target for the dev-shell IDE) explicitly
  bypasses elevation with a synthetic-elevated Principal — different
  cookie scope than zddc-server origin, documented inline.
- AccessView gains CanElevate (elevation-independent "does this email
  have admin authority anywhere?") so the header toggle can render
  itself for an un-elevated admin who hasn't opted in yet.
- ServeProjectList is removed; ProjectInfo + EnumerateProjects stay
  for the profile page's server-rendered project list.
- MatchAppHTML stays — still used by main.go to route <dir>/<tool>.html
  URLs to the apps subsystem when no real file exists.
- Test helpers carry Elevated=true by default (matches the
  pre-elevation default; tests for the un-elevated gate use the
  explicit form).

Go tests pass across all 14 internal packages. Browse + every other
tool rebuilds clean.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-14 12:15:07 -05:00
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>
2026-05-01 20:11:38 -05:00
cb46c2ef8c feat(zddc-server): user profile page replaces /.admin/
Replaces the super-admin-only /.admin/ surface with a public-by-default
/.profile/ page that layers admin tools server-side based on the
caller's effective access:

- Universal (everyone, anonymous included): identity card, effective
  access summary, theme picker, localStorage utilities (export / import
  / clear, landing-presets viewer).
- Subtree admins additionally see: editable .zddc files list (linking
  to the existing form-based editor) and a "Create new project folder"
  form.
- Super-admins additionally see: server config, log viewer, whoami
  headers (the old /.admin/ JSON endpoints, repointed under /.profile/).

Project creation is gated on CanEditZddc(newDir) — the same strict-
ancestor rule that already governs .zddc writes — so no new authority
concept is introduced. ValidateProjectName mirrors the existing
reserved-prefix policy (no leading '.' or '_', no path separators).

/.admin/* is hard-cut: no redirect shim. Old URLs fall through to the
existing dot-prefix guard and 404. Custom CSS file rename: prefer
<root>/.profile.css, fall back to legacy <root>/.admin.css.

Per-resource 404 leakage gates preserved on whoami / config / logs /
zddc / projects so non-admin callers cannot detect the existence of
admin-only sub-resources.

Tree-wide gofmt -w applied as a side-effect.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-29 16:32:02 -05:00