[VS-NfD] Inherit classification in the page tree; require a dedicated right to downgrade #205

Closed
opened 2026-07-30 01:43:39 +02:00 by fable-5 · 1 comment
Collaborator

Plan reference: 20-massnahmenplan.md -> P1-2
ADR: ADR 0022
Effort: M (3 AT)
Depends on: #204

Context

A subpage of a classified page must not silently be unclassified — that is
how classified content escapes marking in practice. Downgrading is the
sensitive direction and needs its own right plus an audit record.

Current state

  • Pages nest via parentId with a max depth of 6 enforced in the service,
    cycles rejected at write time (apps/api/prisma/schema.prisma:255–266);
    trashed pages keep parentId and purge promotes children explicitly.
  • Permission decisions run centrally through apps/api/src/permissions/
    (deny-wins, default-closed) — the place to add a capability rather than
    an ad-hoc check.

Acceptance criteria

  • A new page inherits the effective classification of its parent; a
    page moved under a higher-classified parent is raised.
  • Raising is allowed to any writer; lowering requires a dedicated
    capability expressed in the central permission model, never an ad-hoc
    check.
  • Every raise and lower is audited with old value, new value, actor and
    page.
  • Move operations cannot lower a page's classification as a side effect
    (proven by test), including the purge-promotes-children path.
  • Tests cover: inherit on create, raise on move, lower denied without
    the capability, lower audited with the capability.
  • docs/architecture/permissions.md documents the capability.

Out of scope

Output marking, and a UI for bulk re-classification.

**Plan reference:** `20-massnahmenplan.md` -> P1-2 **ADR:** ADR 0022 **Effort:** M (3 AT) **Depends on:** #204 ## Context A subpage of a classified page must not silently be unclassified — that is how classified content escapes marking in practice. Downgrading is the sensitive direction and needs its own right plus an audit record. ## Current state - Pages nest via `parentId` with a max depth of 6 enforced in the service, cycles rejected at write time (`apps/api/prisma/schema.prisma:255–266`); trashed pages keep `parentId` and purge promotes children explicitly. - Permission decisions run centrally through `apps/api/src/permissions/` (deny-wins, default-closed) — the place to add a capability rather than an ad-hoc check. ## Acceptance criteria - [ ] A new page inherits the effective classification of its parent; a page moved under a higher-classified parent is raised. - [ ] Raising is allowed to any writer; **lowering** requires a dedicated capability expressed in the central permission model, never an ad-hoc check. - [ ] Every raise and lower is audited with old value, new value, actor and page. - [ ] Move operations cannot lower a page's classification as a side effect (proven by test), including the purge-promotes-children path. - [ ] Tests cover: inherit on create, raise on move, lower denied without the capability, lower audited with the capability. - [ ] `docs/architecture/permissions.md` documents the capability. ## Out of scope Output marking, and a UI for bulk re-classification.
fable-5 added this to the M26 — VS-NfD: classification metadata milestone 2026-07-30 01:43:39 +02:00
fable-5 added the
area:auth
vs-nfd:blocker
effort:M
vs-nfd
labels 2026-07-30 01:43:39 +02:00
Author
Collaborator

Done in PR #264 (commit 488d0d0, CI run 539 green, fast-forward merged on Stefan's standing merge instruction).

Evidence:

  • Inherit on create: new page takes max(instance default, parent level) - test classification-inheritance.e2e.db.test.ts.
  • Raise on move: repositioning under a higher-classified parent raises the WHOLE moved subtree; each raised page audited (page.classification_raised, trigger move, from/to/actor/page).
  • Lowering: dedicated capability canLowerClassification (pond-wide Pond Admin) in the central shared permission model - editor with write gets 403 classification_lower_forbidden; Pond Admin lowers with audit page.classification_lowered (warning severity).
  • No move-like path lowers as a side effect: reposition under lower parent, trash-promote and purge-promote all pinned by test.
  • Audit catalogue bumped to v1.1 (docs/architecture/audit-events.md); permissions.md documents the capability.
Done in PR #264 (commit 488d0d0, CI run 539 green, fast-forward merged on Stefan's standing merge instruction). Evidence: - Inherit on create: new page takes max(instance default, parent level) - test classification-inheritance.e2e.db.test.ts. - Raise on move: repositioning under a higher-classified parent raises the WHOLE moved subtree; each raised page audited (page.classification_raised, trigger move, from/to/actor/page). - Lowering: dedicated capability canLowerClassification (pond-wide Pond Admin) in the central shared permission model - editor with write gets 403 classification_lower_forbidden; Pond Admin lowers with audit page.classification_lowered (warning severity). - No move-like path lowers as a side effect: reposition under lower parent, trash-promote and purge-promote all pinned by test. - Audit catalogue bumped to v1.1 (docs/architecture/audit-events.md); permissions.md documents the capability.
Sign in to join this conversation.
No project
No Assignees
1 Participants
Notifications
Due Date
The due date is invalid or out of range. Please use the format 'yyyy-mm-dd'.

No due date set.

Dependencies

No dependencies set.

Reference: stwaidele/dorfteich#205
No description provided.