Redesign the markdown edit-history store from content-hashed blobs + log.jsonl to one self-describing file per save: .history/<stem>/<ts>-<email>.<ext> The filename IS the audit (colon-free UTC timestamp valid on SMB/Azure Files + the authoring email); listing the directory is the history. No sidecar log, no hashing. A byte-identical save is a no-op; a pre-existing file lazy-seeds its current bytes (author "unknown", stamped at mtime). Reverting copies an old snapshot back (records as a fresh save). Snapshots are kept forever. Fixes the 404 reading history: reads no longer require history to be *currently* enabled — ServeTextHistory serves whatever .history/<stem>/ exists (empty list when none); the dispatch drops the EffectiveHistory gate for reads. WRITES stay gated by the history: flag. (The 404 came from the aggregator refactor turning history off on project-level working/, which made already-recorded snapshots unreadable.) Renames: an in-place rename carries .history/<stem>/ to the new name (serveFileMove); a cross-dir move leaves it behind. Defaults: history: true now ships on the three live-editing slots — working, mdl, rsk — at both the project-level nodes and the per-party folders. It's a .zddc cascade key, so operators override per project. Records (.yaml in mdl/rsk) keep their separate record-history path. Browse history viewer updated to the filename-based version id (id ← sha). Tests rewritten for the per-file scheme + rename behavior + SMB-safe names; HistoryAt defaults test updated. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
579 lines
29 KiB
YAML
579 lines
29 KiB
YAML
# defaults.zddc — embedded baseline configuration for every ZDDC
|
|
# deployment. Baked into the binary via //go:embed in defaults.go,
|
|
# loaded as the bottom-most level of the cascade. Operators override
|
|
# at the on-disk root /.zddc (or any deeper level); to ignore this
|
|
# file entirely, set `inherit: false` on an on-disk .zddc.
|
|
#
|
|
# To export an editable copy for an operator:
|
|
#
|
|
# zddc-server show-defaults > /var/lib/zddc/root/.zddc
|
|
#
|
|
# That places this file at the on-disk root, where the operator can
|
|
# edit it freely. The new file then takes the place of the embedded
|
|
# one (both contribute to the cascade, on-disk wins per-field).
|
|
|
|
title: "ZDDC"
|
|
|
|
# Empty acl at this layer — rules come from on-disk .zddc files above.
|
|
# A deployment with no on-disk root .zddc grants no access (consistent
|
|
# with prior behaviour); operators bootstrap by editing the root file.
|
|
acl:
|
|
permissions: {}
|
|
|
|
# ── Standard roles ─────────────────────────────────────────────────────────
|
|
#
|
|
# Three roles ship empty (no members) — a fresh deployment grants
|
|
# nothing until an operator populates them. They're referenced by the
|
|
# project-scoped grants in paths: below.
|
|
#
|
|
# Role membership UNIONS across the cascade: an on-disk .zddc that
|
|
# defines `project_team` again with one extra member ADDS that member
|
|
# to the inherited role. To start fresh at a subtree (e.g. a project
|
|
# wanting its own team independent of a deployment-wide default), use
|
|
# `reset: true` on the role at that level — ancestor definitions above
|
|
# the reset are then excluded.
|
|
#
|
|
# document_controller — the people who file into
|
|
# archive/<party>/received/ and issued/ (WORM zones). They get:
|
|
# - rwcda at every archive/<party>/ via the role grant written
|
|
# into each party's auto-own .zddc (auto_own_roles below).
|
|
# Cascade carries rwcda down to descendants by default.
|
|
# - read+write-once-create at received/issued via the worm:
|
|
# lists (the WORM mask strips w/d/a even though the role
|
|
# grant supplies rwcda at the party level above).
|
|
# - rwcd explicit at incoming/ and staging/ (the QC and
|
|
# transmittal-out workflows need `d` to move files between
|
|
# slots; the explicit grants shadow the inherited rwcda
|
|
# to make the intent visible).
|
|
# - rwc at archive/ so they can create party subfolders.
|
|
#
|
|
# NOT a subtree-admin anywhere. There is no `admins:` entry for
|
|
# the role — DCs cannot bypass WORM (only worm-create via the
|
|
# list) and cannot reach inside fenced working homes. Admin
|
|
# elevation is reserved for the root admins: list (the human
|
|
# escape hatch for mis-filed documents or recovery).
|
|
#
|
|
# Plan-Review approval is part of this role by design — there is
|
|
# no separate `approver` role; two-person sign-off, when needed,
|
|
# is expressed via per-folder `.zddc` overrides rather than
|
|
# baked-in roles.
|
|
#
|
|
# project_team — everyone working on a project. Read-only across
|
|
# the project by default, with a one-way ratchet through the
|
|
# in-flight slots:
|
|
#
|
|
# working/ cr — create + read; the auto_own_fenced child
|
|
# gives the creator rwcda in their own home,
|
|
# fenced from siblings
|
|
# staging/ cr — drop + read, no modify (after drop, the
|
|
# doc-controller is the only one who can
|
|
# change it)
|
|
# reviewing/ cr — create + read; auto_own (unfenced) gives
|
|
# creator rwcda in their iteration folder,
|
|
# siblings see it via project-level :r
|
|
# received/ r — WORM zone; only document_controller can
|
|
# file (and even they need elevation to edit)
|
|
# issued/ r — WORM zone; published, immutable
|
|
# incoming/ r — counterparty's drop zone (project_team
|
|
# observers it, doc_controller QCs it)
|
|
#
|
|
# "Each handoff drops the role's modify rights for the previous
|
|
# slot." That's the model — project_team works freely in
|
|
# working/, commits to staging/, and from there the doc-
|
|
# controller takes over.
|
|
#
|
|
# observer — pure read-only across the project. Like project_team
|
|
# but with no auto-own home: an observer who somehow created a
|
|
# working/<email>/ would still own it via auto-own (the mechanism
|
|
# is path-keyed, not role-keyed), but since observer lacks `c`
|
|
# anywhere, the situation doesn't arise in practice. Intended for
|
|
# auditors, regulators, and external read-only viewers who must
|
|
# not contribute content.
|
|
roles:
|
|
document_controller:
|
|
members: []
|
|
project_team:
|
|
members: []
|
|
observer:
|
|
members: []
|
|
|
|
# Universal tool baseline. archive (record browser), browse (file
|
|
# tree, hosts the in-place markdown editor), and landing (project
|
|
# picker) work everywhere. Each canonical folder below adds its own
|
|
# context-specific tools (transmittal in staging/, etc.). The cascade
|
|
# unions available_tools across all levels — leaf restrictions don't
|
|
# drop ancestor entries — so this baseline propagates to every
|
|
# descendant.
|
|
available_tools: [archive, browse, landing]
|
|
|
|
# ── The slash / no-slash routing convention ────────────────────────────────
|
|
#
|
|
# Every directory URL has two forms, each served by a configurable
|
|
# tool:
|
|
#
|
|
# <dir>/ (trailing slash) → `dir_tool` — the directory view.
|
|
# Defaults to `browse` (file-tree
|
|
# navigator). This is the site-wide
|
|
# default; you rarely set it.
|
|
# <dir> (no slash) → `default_tool` — the "specialized
|
|
# app" for this folder (e.g. archive,
|
|
# transmittal, tables). If a folder
|
|
# declares no default_tool, the no-
|
|
# slash form just 302s to the slash
|
|
# form, so you land on `dir_tool`.
|
|
#
|
|
# JSON listing requests are unaffected by either key — they always get
|
|
# the raw directory listing, so the browse SPA (and any other client)
|
|
# can enumerate entries no matter what dir_tool/default_tool are.
|
|
#
|
|
# Both keys cascade leaf→root: a parent's default_tool applies to
|
|
# descendants unless a deeper level overrides it (browse set on
|
|
# working/ reaches working/alice/notes/ for free). The keys below set
|
|
# default_tool on the canonical folders; dir_tool is left unset
|
|
# everywhere, so the slash form is always `browse`.
|
|
#
|
|
# ── Canonical project structure ────────────────────────────────────────────
|
|
#
|
|
# Every ZDDC project lives at a top-level directory. Under it
|
|
# `archive/` is the ONLY real top-level folder; it contains a folder
|
|
# per party. Everything party-scoped (the SSR row, MDL/RSK rollups,
|
|
# WORM received/issued, the incoming drop zone, and the in-flight
|
|
# lifecycle slots working/staging/reviewing) lives uniformly under
|
|
# archive/<party>/.
|
|
#
|
|
# Six top-level virtuals sit beside archive/ as resolver views:
|
|
#
|
|
# ssr mdl rsk tables rollups across parties
|
|
# (with a synthesized $party column)
|
|
# working staging browse folder-nav listings of
|
|
# reviewing parties with non-empty content in
|
|
# the slot (in-flight filter). The
|
|
# virtual 302-redirects to the
|
|
# canonical archive/<party>/<slot>/.
|
|
#
|
|
# Mkdir at the project root is restricted to `archive` plus system
|
|
# (_/.-prefixed) names; the six virtual aggregator names are rejected
|
|
# because the virtual would shadow any physical folder created at
|
|
# those URLs (see handler/fileapi.go).
|
|
#
|
|
# Everything below is expressed via the recursive paths: schema. None
|
|
# of the directories need to exist on disk — the cascade walker
|
|
# resolves behaviour from this declaration, so a fresh project lands
|
|
# on usable empty views at every well-known URL.
|
|
#
|
|
# Operators override any of this by mirroring the structure in an
|
|
# on-disk .zddc and changing what they need; on-disk values win.
|
|
|
|
paths:
|
|
# First segment under root is the project name; "*" matches any.
|
|
"*":
|
|
# Project-scoped baseline ACL. project_team and observer get read
|
|
# across the project; document_controller gets read + overwrite-
|
|
# existing (so people can ask them to fix a stuck file). None of
|
|
# the three gets `c` (create) at this level — that's granted only
|
|
# at the specific spots below (archive/, working/, staging/), so
|
|
# the doc controller can't make arbitrary folders. Grants here cap
|
|
# at deeper levels per deepest-match-wins, except where a deeper
|
|
# .zddc restates a fuller grant for the same principal.
|
|
acl:
|
|
permissions:
|
|
project_team: r
|
|
observer: r
|
|
document_controller: rw
|
|
paths:
|
|
# ── Top-level virtual aggregators ───────────────────────────
|
|
#
|
|
# Six resolver views, sibling to archive/. None of these
|
|
# materialise on disk; the server synthesises listings by
|
|
# walking archive/*/<slot>/ at request time and (for the
|
|
# tables rollups) rewrites file reads/writes back to canonical
|
|
# paths inside the per-party folders. ACL on each synthetic
|
|
# row is evaluated against the canonical archive/<party>/
|
|
# chain, so party owners can edit their own rows and non-
|
|
# owners see them read-only.
|
|
ssr:
|
|
default_tool: tables
|
|
available_tools: [tables]
|
|
virtual: true
|
|
mdl:
|
|
default_tool: tables
|
|
available_tools: [tables]
|
|
virtual: true
|
|
# Edit-history default-on for the deliverables list (subtree-
|
|
# inheriting; see working/ note). Operators override per .zddc.
|
|
history: true
|
|
rsk:
|
|
default_tool: tables
|
|
available_tools: [tables]
|
|
virtual: true
|
|
# Edit-history default-on for the risk register.
|
|
history: true
|
|
working:
|
|
default_tool: browse
|
|
available_tools: [browse]
|
|
# Pure folder-nav aggregator over the per-party
|
|
# archive/<party>/working/ slots — same shape as staging/ and
|
|
# reviewing/ below. Nothing lives directly at <project>/working/:
|
|
# creating a folder here prompts for a party (browse's "New
|
|
# folder" picker) and lands it at archive/<party>/working/<name>,
|
|
# which carries its own history: true + auto-own convention.
|
|
virtual: true
|
|
# Edit-history default-on across the working subtree (markdown
|
|
# saves are snapshotted to .history/<stem>/). Subtree-inheriting,
|
|
# so it also covers any pre-reshape <project>/working/<…> homes
|
|
# that still hold content. Reads of recorded history never require
|
|
# this flag — turning it off only stops new snapshots.
|
|
history: true
|
|
staging:
|
|
default_tool: browse
|
|
available_tools: [browse]
|
|
virtual: true
|
|
reviewing:
|
|
default_tool: browse
|
|
available_tools: [browse]
|
|
virtual: true
|
|
|
|
# ── Physical party root ─────────────────────────────────────
|
|
archive:
|
|
default_tool: archive
|
|
# The doc controller can create party subfolders here
|
|
# (archive/<party>/). Restate the full grant — deepest-match
|
|
# is per-principal replacement, so we re-list rw + add c.
|
|
acl:
|
|
permissions:
|
|
document_controller: rwc
|
|
paths:
|
|
# Second segment under archive/ is the party name.
|
|
"*":
|
|
# When the doc controller creates a party folder, the
|
|
# auto-own .zddc grants:
|
|
# - the creator's email rwcda (the standard auto_own
|
|
# mechanism)
|
|
# - the document_controller role rwcda (auto_own_roles
|
|
# below) so any DC in the role has full authority at
|
|
# every party, not just the parties they personally
|
|
# mkdir'd
|
|
#
|
|
# UNFENCED — so the project-level project_team:r still
|
|
# cascades through to received/issued/incoming. That
|
|
# lets the DC who created the party set up the counter-
|
|
# party's own .zddc afterward (e.g. granting them cr at
|
|
# incoming/).
|
|
#
|
|
# No `admins:` here by design. The DC role gets full
|
|
# authority via the role grant in the auto-own .zddc, not
|
|
# via subtree-admin status — so WORM masks at
|
|
# received/issued still bind them (they file write-once
|
|
# via the worm: list), and per-user fenced homes under
|
|
# working/ stay private to their creators. Admin
|
|
# elevation is reserved for the root admins list (the
|
|
# actual sudo-style escape hatch).
|
|
auto_own: true
|
|
auto_own_roles: [document_controller]
|
|
# SSR record: the party folder's ssr.yaml carries this
|
|
# party's vendor / contract / status data. Scoped by
|
|
# filename pattern so the lock on `kind` only applies to
|
|
# ssr.yaml — the mdl/, rsk/, received/, working/,
|
|
# staging/, reviewing/ subfolders are untouched. No
|
|
# filename_format because identity is the party folder
|
|
# name, not a composed tracking number.
|
|
records:
|
|
"ssr.yaml":
|
|
field_defaults:
|
|
kind: SSR
|
|
locked: [kind]
|
|
# ── Field-code vocabularies (field_codes:) ──────────────
|
|
# Each tracking-number component can be constrained by a
|
|
# field_codes entry at this (per-party) level — or higher
|
|
# if every party shares the same vocabulary. Three kinds:
|
|
#
|
|
# enum — closed code list; the label surfaces in form
|
|
# dropdowns and is enforced on POST/PUT.
|
|
# pattern — anchored regex (server wraps it with ^…$).
|
|
# free — no constraint; `description:` is help-text in
|
|
# the form UI.
|
|
#
|
|
# Map-merge across the cascade: a deeper .zddc can narrow
|
|
# or replace a single code without re-listing the others.
|
|
# `originator` is normally NOT listed here — it's bound to
|
|
# the party-folder name via folder_fields on the mdl/ + rsk/
|
|
# records below, so the folder is its sole source of truth.
|
|
#
|
|
# field_codes:
|
|
# discipline:
|
|
# kind: enum
|
|
# codes:
|
|
# EL: "Electrical"
|
|
# ME: "Mechanical"
|
|
# CV: "Civil"
|
|
# sequence:
|
|
# kind: pattern
|
|
# pattern: "[0-9]{4}" # zero-padded 4-digit
|
|
# type:
|
|
# kind: free
|
|
# description: "Document category code within the discipline"
|
|
paths:
|
|
mdl:
|
|
default_tool: tables
|
|
available_tools: [tables]
|
|
# The mdl folder is virtual by convention — the
|
|
# tables tool serves it from the embedded default
|
|
# spec even when the on-disk folder doesn't exist.
|
|
virtual: true
|
|
# Edit-history default-on (markdown notes/specs saved here
|
|
# are snapshotted; .yaml records keep their own record-
|
|
# history path regardless).
|
|
history: true
|
|
# MDL records: each .yaml file is an independent
|
|
# deliverable with its own composed tracking number.
|
|
# No type lock — the row's body fields drive the
|
|
# filename; type is free-choice from the deployment's
|
|
# field_codes (see the field_codes block above).
|
|
#
|
|
# Default template — five required components plus an
|
|
# optional per-deliverable suffix that marks parts of
|
|
# the SAME deliverable (A = Appendix A, 01 = Sheet 1):
|
|
#
|
|
# originator-project-discipline-type-sequence[-suffix]
|
|
#
|
|
# `originator` is folder-bound: the server sets it from
|
|
# the party-folder name (folder_fields below) and the
|
|
# form renders it read-only — the party folder is the
|
|
# single source of truth for the originator code.
|
|
#
|
|
# To add PROJECT-WIDE components (phase, area, ...),
|
|
# override filename_format here AND add matching
|
|
# properties to mdl/form.yaml + columns to mdl/table.yaml.
|
|
# Pick once per project and apply to EVERY deliverable;
|
|
# mixing schemas within one project breaks lexical sort
|
|
# and filtering. Example:
|
|
# records:
|
|
# "*.yaml":
|
|
# folder_fields: { originator: 1 }
|
|
# filename_format: "{originator}-{phase}-{project}-{area}-{discipline}-{type}-{sequence}-{suffix?}"
|
|
records:
|
|
"*.yaml":
|
|
folder_fields:
|
|
originator: 1
|
|
filename_format: "{originator}-{project}-{discipline}-{type}-{sequence}-{suffix?}"
|
|
rsk:
|
|
default_tool: tables
|
|
available_tools: [tables]
|
|
# Risk register — same virtual-by-convention pattern
|
|
# as mdl/. Embedded default-rsk spec backs it when no
|
|
# operator override is on disk.
|
|
virtual: true
|
|
# Edit-history default-on (same as mdl/).
|
|
history: true
|
|
# RSK records: each .yaml file is a row of a parent
|
|
# rsk-type deliverable. The table itself has a tracking
|
|
# number (same default components as an MDL deliverable
|
|
# with type=RSK); rows append a -{row} suffix the server
|
|
# auto-assigns within the row-scope group on POST-create.
|
|
# `originator` is folder-bound to the party folder, same
|
|
# as MDL.
|
|
#
|
|
# To add project-wide phase / area components, override
|
|
# BOTH filename_format AND row_scope_fields here — the
|
|
# scope fields decide which rows share a row-number
|
|
# sequence, so they must list the same components the
|
|
# filename does.
|
|
records:
|
|
"*.yaml":
|
|
folder_fields:
|
|
originator: 1
|
|
filename_format: "{originator}-{project}-{discipline}-{type}-{sequence}-{suffix?}-{row}"
|
|
field_defaults:
|
|
type: RSK
|
|
locked: [type]
|
|
row_field: row
|
|
row_scope_fields: [originator, project, discipline, type, sequence, suffix]
|
|
incoming:
|
|
# incoming/ is the COUNTERPARTY's drop zone. The flow:
|
|
# 1. the other party's document controller uploads
|
|
# files here (a deployment grants them cr at this
|
|
# path, e.g. acl: { permissions: { "*@acme.com": cr } }
|
|
# at archive/Acme/incoming/.zddc — or they mkdir a
|
|
# dated subfolder under incoming/ and own it via
|
|
# auto_own)
|
|
# 2. OUR document controller QCs them via classifier
|
|
# (rename in place) and moves them to received/
|
|
# (which needs delete here + worm-create there),
|
|
# ideally returning a signed transmittal in issued/
|
|
#
|
|
# The normal project_team has only read here (inherited
|
|
# from the project level — they have no c/w) so they can
|
|
# see what's been dropped but not touch it. The
|
|
# document_controller grant restates rwcd so the QC +
|
|
# transfer-out workflow has the delete bit it needs.
|
|
default_tool: classifier
|
|
available_tools: [classifier]
|
|
auto_own: true
|
|
drop_target: true
|
|
acl:
|
|
permissions:
|
|
document_controller: rwcd
|
|
# received/ and issued/ are WORM (write-once-read-many).
|
|
# The `worm:` list marks the zone:
|
|
#
|
|
# - write (w) and delete (d) are stripped for EVERYONE
|
|
# - create (c) is stripped for everyone EXCEPT the
|
|
# principals listed — they get read + write-once-
|
|
# create ("cr")
|
|
# - read for non-listed principals is whatever the
|
|
# normal cascade ACL granted; the WORM list does not
|
|
# itself confer read to outsiders
|
|
# - admins (root / subtree) bypass entirely — the
|
|
# human escape hatch for mis-filed documents
|
|
#
|
|
# The baseline is an empty list: WORM zone, no
|
|
# create-capable principals — filing is locked until a
|
|
# deployment names a document controller, e.g.
|
|
#
|
|
# worm: ["doc-control@example.com"]
|
|
#
|
|
# at received/ (or issued/, or archive/<party>/, or
|
|
# wherever scopes it right). worm: lists UNION across the
|
|
# cascade, so a deeper .zddc adds more controllers.
|
|
received:
|
|
default_tool: archive
|
|
# document_controller may file write-once into the
|
|
# WORM zone. Their project-level rw is masked here
|
|
# to r; worm: restores write-once-create.
|
|
worm: [document_controller]
|
|
issued:
|
|
default_tool: archive
|
|
worm: [document_controller]
|
|
# ── In-flight lifecycle slots (NEW — nested per-party) ────
|
|
#
|
|
# working/staging/reviewing now live inside each party
|
|
# folder instead of at the project root. The project-
|
|
# level <project>/{working,staging,reviewing} virtuals
|
|
# (declared above) are folder-nav views over these
|
|
# canonical per-party slots.
|
|
# ── In-flight ratchet ───────────────────────────────
|
|
#
|
|
# The lifecycle slots form a one-way handoff:
|
|
#
|
|
# working/ → staging/ → issued/ (WORM)
|
|
# (full) (cr) (worm cr)
|
|
#
|
|
# At each step the previous role's modify rights drop:
|
|
# project_team iterates freely in working/; when they
|
|
# promote to staging/ they can't change it without doc-
|
|
# controller help; when DC publishes to issued/ even
|
|
# they can't change it without elevation. Each ACL
|
|
# grant below is the verb-set the ROLE keeps at that
|
|
# step; auto_own + auto_own_fenced sub-folder grants
|
|
# layer per-creator ownership on top of these.
|
|
working:
|
|
default_tool: browse
|
|
available_tools: [browse, classifier]
|
|
# Project_team gets read + create here so they can
|
|
# mkdir their own home folder (and any shared sub-
|
|
# folders). The auto_own_fenced declaration at the
|
|
# `*` child below makes the new folder a private home
|
|
# with rwcda for the creator (fenced from ancestors,
|
|
# so collaborators only join after the owner edits
|
|
# the home's .zddc to grant them access).
|
|
#
|
|
# `cr` instead of just `c` so an existing file at
|
|
# working/ root stays readable to all team members
|
|
# (cascade is per-level deepest-match — a single `c`
|
|
# would shadow the project-level `r`).
|
|
#
|
|
# `document_controller: rwcda` is restated here so a
|
|
# DC whose email is ALSO matched by project_team
|
|
# (typical when project_team is `*@example.com`) gets
|
|
# the higher grant via within-level union. Without
|
|
# the restatement, the cascade's deepest-level-wins
|
|
# would pick project_team's cr and shadow the DC's
|
|
# rwcda inherited from the party's auto-own .zddc.
|
|
# Same pattern applied at staging/ and reviewing/.
|
|
acl:
|
|
permissions:
|
|
project_team: cr
|
|
document_controller: rwcda
|
|
# working/ auto-owns the first creator + the per-user
|
|
# homes below.
|
|
auto_own: true
|
|
drop_target: true
|
|
# Edit-history: every markdown save under working/ (incl.
|
|
# the fenced per-user homes — history inherits through
|
|
# fences) is versioned into a sibling .history/ store with
|
|
# a server-stamped audit line (who + when). The live file
|
|
# stays the source of truth; GET <file>?history lists prior
|
|
# versions. See ZddcFile.History / handler.WriteTextWithHistory.
|
|
history: true
|
|
paths:
|
|
"*": # per-user home dir, fenced
|
|
default_tool: browse
|
|
available_tools: [browse, classifier]
|
|
auto_own: true
|
|
# Per-user home is private by default: the generated
|
|
# auto-own .zddc carries inherit:false so ancestor ACL
|
|
# grants don't reach inside. The user can edit the file
|
|
# to grant collaborators access.
|
|
auto_own_fenced: true
|
|
drop_target: true
|
|
staging:
|
|
default_tool: transmittal
|
|
available_tools: [transmittal, classifier]
|
|
# The ratchet step from working/. project_team gets
|
|
# `cr` — they can drop files (PUT new files at
|
|
# staging/) and read what's there, but cannot edit or
|
|
# delete after the drop. Once a file is in staging it
|
|
# belongs to the doc-controller workflow; the team
|
|
# member needs to ask DC to change it.
|
|
#
|
|
# Convention: project_team drops FILES at staging/,
|
|
# not sub-folders. A sub-folder mkdir'd by project_
|
|
# team would trigger auto_own and grant them rwcda
|
|
# inside their own sub-folder (auto_own is path-keyed,
|
|
# not role-keyed — it fires for any creator). The
|
|
# auto_own here is preserved for DC's per-transmittal
|
|
# mkdir flow; project_team can keep to file drops to
|
|
# honour the "can't alter after" intent.
|
|
#
|
|
# DC gets rwcda explicitly — `d` for the cut to issued/,
|
|
# `a` so Plan Review can write the staging/<tracking>/.zddc
|
|
# the composite endpoint scaffolds. Restated here (not
|
|
# inherited from the party-level role grant) so the
|
|
# within-level union dominates project_team's cr for
|
|
# any DC matched by the team wildcard.
|
|
acl:
|
|
permissions:
|
|
project_team: cr
|
|
document_controller: rwcda
|
|
auto_own: true
|
|
drop_target: true
|
|
reviewing:
|
|
default_tool: browse
|
|
available_tools: [browse]
|
|
# reviewing/ is the doc-controller's draft-workspace
|
|
# area inside this party folder. The "Plan Review"
|
|
# composite endpoint scaffolds a physical folder here
|
|
# for each submittal under review, with a .zddc
|
|
# carrying received_path back to the canonical
|
|
# submittal in received/. Subtree-admin (inherited
|
|
# from the party-level admins:) so the doc
|
|
# controller can author per-folder .zddc files
|
|
# (originator ACL, planned_date).
|
|
#
|
|
# project_team gets `cr` so the originating team can
|
|
# create review-iteration folders alongside the
|
|
# Plan-Review-scaffolded ones. auto_own (unfenced
|
|
# here, unlike working/) gives the creator rwcda
|
|
# inside; siblings see the iteration via the project-
|
|
# level project_team:r cascade.
|
|
#
|
|
# document_controller: rwcda restated for the same
|
|
# reason as working/ + staging/ — keeps a DC matched
|
|
# by the project_team wildcard at full authority via
|
|
# within-level union.
|
|
acl:
|
|
permissions:
|
|
project_team: cr
|
|
document_controller: rwcda
|
|
auto_own: true
|
|
drop_target: true
|