Filter discovery and compose output by an externally supplied authorization context #15

Open
opened 2026-09-22 14:50:19 +02:00 by michael · 0 comments
Owner

Goal

berhtjan-core must be able to filter contributions and composed
fields using an externally supplied effective authorization context,
at its two entry points:

  • Discovery (scanner/discover.lua, load_contributions()): a whole
    module/contribution the caller has no access to must not enter the
    index at all.
  • Compose (composer.lua, compose()): individual fields within an
    otherwise visible group may be restricted (e.g. an address is
    visible, but its birthday field is not).

berhtjan-core does not define identities, roles, groups, or the
policy used to derive effective permissions. It only receives an
already-resolved authorization context and decides "does this go into
the result, or not" with it. See DAW/daw-contracts#12 for
how that context is represented.

Why now, not later

finding-01 (session insight): confirmations/visibility of data are
bound to a scope and grants, and this must also prevent indirect
leaks through relationships or their mere existence. The same applies
here at the module/field level: filtering after the fact (e.g. only
hiding a field in the UI) is exactly the kind of indirect leak this
principle rules out. Authorization must be applied before
unauthorized metadata enters indexes, composed results, caches, or
downstream processing -- not as a second pass layered on top
afterwards.

Two asymmetric cases

  • Discovery is binary: a module is in the index or it is not.
    Filtering belongs in load_contributions() itself, before the
    scanned contributions are handed to scanner/index.lua
    (build_index(), sorted_subcommands()) -- not as a filter bolted onto
    index.lua afterwards; by the time index.lua runs, unauthorized
    entries must already be gone.
  • Compose is partial: a field missing because of authorization is
    not an error. compose() currently returns nil, err on the first
    unresolvable field (field_resolve_failed) -- that error path is for
    a genuinely broken field type, not for "field intentionally
    omitted". A field that a caller may not see must be left out of the
    composed result while compose() still returns success (true, nil).
    Confusing the two would make an ordinary authorization boundary look
    like a broken form in a consuming application (e.g. Steurjan).

Scope

  • Design only in this issue: where the authorization context enters
    discover.lua and composer.lua, and the exact shape of "module
    filtered out" vs. "field filtered out" (success, just absent) vs.
    "genuine error" (nil, err).
  • No implementation yet -- this depends on
    DAW/daw-contracts#12 being resolved first (at least at
    the "what shape does the context have" level).
  • Do not invent a role or permission model here.

Acceptance criteria

  • Design note: how the authorization context reaches
    load_contributions() and compose()
  • Design note: discovery filtering happens in discover.lua, before
    data reaches index.lua
  • Design note: compose() distinguishes "field authorized away"
    (still success) from "field genuinely broken" (error), and
    documents the distinction
  • No role/permission model defined here (tracked in
    DAW/daw-contracts#12)

DAW/daw-contracts#12 (effective authorization context
shape, cross-repo)

## Goal berhtjan-core must be able to filter contributions and composed fields using an externally supplied effective authorization context, at its two entry points: - **Discovery** (scanner/discover.lua, load_contributions()): a whole module/contribution the caller has no access to must not enter the index at all. - **Compose** (composer.lua, compose()): individual fields within an otherwise visible group may be restricted (e.g. an address is visible, but its birthday field is not). berhtjan-core does not define identities, roles, groups, or the policy used to derive effective permissions. It only receives an already-resolved authorization context and decides "does this go into the result, or not" with it. See DAW/daw-contracts#12 for how that context is represented. ## Why now, not later finding-01 (session insight): confirmations/visibility of data are bound to a scope and grants, and this must also prevent indirect leaks through relationships or their mere existence. The same applies here at the module/field level: filtering after the fact (e.g. only hiding a field in the UI) is exactly the kind of indirect leak this principle rules out. Authorization must be applied before unauthorized metadata enters indexes, composed results, caches, or downstream processing -- not as a second pass layered on top afterwards. ## Two asymmetric cases - **Discovery is binary**: a module is in the index or it is not. Filtering belongs in load_contributions() itself, before the scanned contributions are handed to scanner/index.lua (build_index(), sorted_subcommands()) -- not as a filter bolted onto index.lua afterwards; by the time index.lua runs, unauthorized entries must already be gone. - **Compose is partial**: a field missing because of authorization is not an error. compose() currently returns nil, err on the first unresolvable field (field_resolve_failed) -- that error path is for a genuinely broken field type, not for "field intentionally omitted". A field that a caller may not see must be left out of the composed result while compose() still returns success (true, nil). Confusing the two would make an ordinary authorization boundary look like a broken form in a consuming application (e.g. Steurjan). ## Scope - Design only in this issue: where the authorization context enters discover.lua and composer.lua, and the exact shape of "module filtered out" vs. "field filtered out" (success, just absent) vs. "genuine error" (nil, err). - No implementation yet -- this depends on DAW/daw-contracts#12 being resolved first (at least at the "what shape does the context have" level). - Do not invent a role or permission model here. ## Acceptance criteria - [ ] Design note: how the authorization context reaches load_contributions() and compose() - [ ] Design note: discovery filtering happens in discover.lua, before data reaches index.lua - [ ] Design note: compose() distinguishes "field authorized away" (still success) from "field genuinely broken" (error), and documents the distinction - [ ] No role/permission model defined here (tracked in DAW/daw-contracts#12) ## Related DAW/daw-contracts#12 (effective authorization context shape, cross-repo)
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
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
DAW/daw-modul-berhtjan-core#15
No description provided.