From cc7f34e92249db1b61c0bc9b3dc8cfba5553950a Mon Sep 17 00:00:00 2001 From: ZDDC Date: Thu, 21 May 2026 13:23:12 -0500 Subject: [PATCH] fix(listing): synthetic table.yaml/form.yaml verbs reflect actual authority MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The synthetic spec entries injected into rollup virtual surfaces (//{ssr,mdl,rsk}/) had Verbs hardcoded to "r" — so even an elevated root admin saw the spec files as read-only in the YAML editor's verbs check (cap.has(node, 'a') returned false → saveBtn disabled + the red read-only banner). The hardcode was a Part 2 oversight; every other synthetic listing entry already computes verbs via EffectiveVerbsFromChainP against the entry's path. Now table.yaml and form.yaml do the same — elevated admins get "rwcda" and can PUT a custom spec to override the embedded default at the rollup view; everyone else still gets "r" via the project-level project_team:r grant cascading through. Co-Authored-By: Claude Opus 4.7 (1M context) --- zddc/internal/fs/tree.go | 23 +++++++++++++++++++---- zddc/internal/handler/tables.html | 2 +- 2 files changed, 20 insertions(+), 5 deletions(-) diff --git a/zddc/internal/fs/tree.go b/zddc/internal/fs/tree.go index 341f120..a3ee4b0 100644 --- a/zddc/internal/fs/tree.go +++ b/zddc/internal/fs/tree.go @@ -291,11 +291,26 @@ func ListDirectory(ctx context.Context, decider policy.Decider, fsRoot, dirPath, // Row rollups carry synthetic spec entries so the tables tool // can walkServer them. Folder-nav virtuals don't need spec // files — they're just party listings rendered by browse. + // Verbs reflect actual cascade authority at each synthetic + // spec's URL so elevated admins see them as writable (they + // CAN materialise an override .zddc / spec by PUTting to + // the virtual path). Non-admins fall through to the default + // 'r' that the embedded baseline grants on the rollup view. if zddc.IsRowSlot(vv.Slot) { - result = append(result, - listing.FileInfo{Name: "table.yaml", URL: baseURL + "table.yaml", IsDir: false, Virtual: true, Verbs: zddc.VerbR.String()}, - listing.FileInfo{Name: "form.yaml", URL: baseURL + "form.yaml", IsDir: false, Virtual: true, Verbs: zddc.VerbR.String()}, - ) + for _, spec := range []string{"table.yaml", "form.yaml"} { + specURL := baseURL + spec + verbs := policy.EffectiveVerbsFromChainP(ctx, decider, parentChain, principal, specURL) + if !verbs.Has(zddc.VerbR) { + continue + } + result = append(result, listing.FileInfo{ + Name: spec, + URL: specURL, + IsDir: false, + Virtual: true, + Verbs: verbs.String(), + }) + } } } diff --git a/zddc/internal/handler/tables.html b/zddc/internal/handler/tables.html index e5e9e3e..20398a7 100644 --- a/zddc/internal/handler/tables.html +++ b/zddc/internal/handler/tables.html @@ -1534,7 +1534,7 @@ body.is-elevated::after {
ZDDC Table - v0.0.22-beta · 2026-05-21 16:29:59 · 90a3102 + v0.0.22-dev · 2026-05-21 18:21:55 · a6cb847-dirty