# 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 ─────────────────────────────────────────────────────────
#
# Two 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 read+write-once-
#     create there (via the worm: lists below) and read/write
#     elsewhere in a project, plus subtree-admin of working/ and
#     staging/ so they can stand up new top-level folders and manage
#     user/staging subtrees. They are NOT subtree-admin of archive/,
#     so the WORM constraint still binds them in received/issued.
#
#   project_team — everyone working on a project. Read-only across
#     the project. Their own working/<email>/ home and anything they
#     create under incoming/ get a creator-owned auto-own .zddc
#     (rwcda) which wins via deepest-match, so "read-only except
#     what I own" falls out of the cascade with no special rule.
roles:
  document_controller:
    members: []
  project_team:
    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 the
# convention is four canonical folders: archive (formal record),
# working (in-progress workspace), staging (outbound prep), reviewing
# (Plan-Review-managed draft workspaces). Under archive/<party>/ the
# convention is four more: mdl (deliverables list), incoming (counterparty
# drop zone), received (immutable submittals), issued (immutable responses).
#
# All of this 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 gets read across the
    # project; document_controller gets read + overwrite-existing
    # (so people can ask them to fix a stuck file). Neither 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
        document_controller: rw
    # Plan Review composite endpoint: the doc controller right-clicks
    # archive/<party>/received/<tracking>/ in the browse app and gets
    # a "Plan Review" item that scaffolds workflow folders under the
    # paths below. Both keys required; omitting the block disables
    # the menu item for this subtree.
    on_plan_review:
      reviewing_root: reviewing/
      staging_root: staging/
    paths:
      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, an
            # auto-own .zddc grants them rwcda there (UNFENCED — so
            # the project-level project_team:r still cascades through
            # to received/issued). That lets them set up the
            # counterparty's own .zddc afterward.
            auto_own: true
            # 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/ 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]
            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
                # MDL records: each .yaml file is an independent
                # deliverable with its own composed tracking number.
                # No locks — the row's body fields drive the
                # filename, type is free-choice from the deployment's
                # field_codes. Operators define field_codes at the
                # project root (or higher) to supply the originator /
                # discipline / type / sequence vocabularies.
                records:
                  "*.yaml":
                    filename_format: "{originator}-{phase?}-{project}-{area?}-{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
                # RSK records: each .yaml file is a row of a parent
                # rsk-type deliverable. The table itself has a
                # tracking number (same shape as an MDL deliverable
                # with type=RSK); rows append a -{row} suffix. The
                # server auto-assigns row within the row-scope group
                # on POST-create.
                records:
                  "*.yaml":
                    filename_format: "{originator}-{phase?}-{project}-{area?}-{discipline}-{type}-{sequence}{suffix?}-{row}"
                    field_defaults:
                      type: RSK
                    locked: [type]
                    row_field: row
                    row_scope_fields: [originator, phase, project, area, 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]
      working:
        default_tool: browse
        available_tools: [browse, classifier]
        # working/ auto-owns the first creator + the per-user homes
        # below.
        auto_own: true
        drop_target: true
        # Doc controller is subtree-admin of working/ — full create
        # + manage, including taking over a fenced per-user home if a
        # user leaves. (Scoped here, not at the project root, so the
        # WORM constraint in archive/<party>/received|issued still
        # binds them.)
        admins: [document_controller]
        paths:
          "*":           # per-user home dir
            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]
        auto_own: true
        drop_target: true
        # Doc controller is subtree-admin of staging/ too — same
        # rationale as working/.
        admins: [document_controller]
      reviewing:
        default_tool: browse
        available_tools: [browse]
        # reviewing/ is the doc-controller's draft-workspace area. The
        # "Plan Review" composite endpoint (see on_plan_review at project
        # level) 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 so the doc
        # controller can author per-folder .zddc files (originator ACL,
        # planned_date).
        auto_own: true
        drop_target: true
        admins: [document_controller]
      # Project-level aggregation tables. All three are virtual: the
      # folder doesn't exist on disk; the server synthesizes listings
      # by walking archive/*/ at request time. ACL on each synthetic
      # row is evaluated against the canonical archive/<party>/ path,
      # so party owners can edit their own rows and non-owners see
      # them read-only.
      ssr:
        default_tool: tables
        available_tools: [tables]
        # SSR aggregates one row per party folder; the row's backing
        # file is archive/<party>/ssr.yaml. + Add row in this view
        # creates a new party folder.
        virtual: true
      mdl:
        default_tool: tables
        available_tools: [tables]
        # Project-rollup of every archive/<party>/mdl/ row. Read +
        # edit; + Add row is disabled because party affiliation is
        # ambiguous here (add at the per-party path instead).
        virtual: true
      rsk:
        default_tool: tables
        available_tools: [tables]
        # Project-rollup of every archive/<party>/rsk/ row. Same
        # semantics as the mdl rollup.
        virtual: true
