Skip to content

Put ownership on the console - #170

Merged
icebergai-review-bot[bot] merged 1 commit into
mainfrom
ownership-console-v3
Aug 16, 2026
Merged

Put ownership on the console#170
icebergai-review-bot[bot] merged 1 commit into
mainfrom
ownership-console-v3

Conversation

@richardmhope

Copy link
Copy Markdown
Contributor

Part 3 of #146, on top of the API in #165. The screens. Escalation notifications
are the last PR.

What lands

  • /ownership (admin) — teams, routing rules, and response targets on one page.
  • Findings queue — owner and due columns, an owner dropdown, and a due filter.
  • Triage panel — an owner picker, offered to analysts.
  • Overview — Overdue and Unowned tiles, linking to the queues behind them.
  • Finding detail — who owns it, whether a person pinned it, and when it is due.

The calls worth arguing about

One screen, not three. A team with no rule owns nothing; a rule with no target
produces no deadline. An operator setting this up is answering one question — who
looks after what, and how fast — not visiting three pages.

Rules are shown in the order routing evaluates them, disabled ones included.
Order is the entire meaning of a rule set, and a screen that displayed a friendlier
one would be a screen you could not use to reason about routing. A disabled rule's
position is exactly what somebody is thinking about when they consider turning it
back on.

Disband is the primary action; delete is conditional. A team that owns findings
cannot be deleted at all — the API answers 409, because the FK is SET NULL and
the delete would otherwise orphan a whole team's work. So the screen leads with
disbanding and hides the delete control entirely for a team with open findings,
rather than offering a button whose only outcome is an error.

"Leave unchanged" has to be expressible. A <select> cannot post an omitted
field, and any owner value pins a finding against future routing. Without an
empty option, an analyst could not add a triage comment without also taking the
routing decision away from the rules. So owner reads the same three ways
assignee already does: empty is unchanged, none is null, anything else is an id.

In the opposite direction, the queue collapses ?owner_group_id= and ?unowned=
into a single ?owner= dropdown. The API keeps them separate because an absent
query parameter already means "do not filter" there; a dropdown has no such
ambiguity, and two controls that must not both be set is a state a URL can carry
and an operator can get wrong.

Overdue is computed once, in Python. ownership.overdue is widened to a
Protocol so the console can ask it of a FindingRead — the alternative is the
template re-deriving overdue in Jinja, and a second definition is the copy that
drifts. The overview counts both queues by asking the API rather than filtering the
open-findings page, which is capped: deriving them would under-report exactly when
the backlog is large enough to matter.

Invariants

Every route resolves WebAdmin/WebViewer itself and then calls the API handler as
a function — calling a handler directly does not run its own Depends (docs/web.md).
Nothing under iceberg_api.web touches the ORM, no template gains an inline
<script>/<style> or an onclick=, and the existing invariant tests
(test_web_invariants.py, test_web_shell.py) pass unchanged.

The owner picker is offered to analysts even though the user picker is admin-only:
listing teams is viewer+ at the API, because a team is not a directory of people.

Acceptance criteria, against #146

  • Unowned actionable findings are visible as a first-class queue — a dropdown
    option, an overview tile, and a link from every team's row
  • Reassignment history and filters — the owner event is in the finding's trail,
    and the queue filters on owner, unowned, and overdue
  • Dashboard summaries — Overdue and Unowned tiles, plus per-team open/overdue
    counts on the ownership screen
  • Notifications — the last PR

23 tests in apps/api/tests/test_web_ownership.py; make check green.

Part 3 of #146, on top of the API in #165. The screens: a `/ownership`
administration page for teams, routing rules and response targets, the owner and
due columns on the findings queue, an owner picker on the triage panel, and the
two ownership tiles on the overview. Escalation notifications are the last PR.

**One screen, not three.** A team with no rule owns nothing and a rule with no
target produces no deadline; an operator setting this up is answering one
question, not visiting three pages.

**Rules are shown in the order routing evaluates them.** Order is the entire
meaning of a rule set, and a screen that displayed a friendlier one would be a
screen you could not use to reason about routing. Disabled rules stay in place,
because their position is what somebody is thinking about when they consider
turning one back on.

**Disband is the primary action, delete is conditional.** A team that owns
findings cannot be deleted at all — the API answers 409 — so the screen leads
with disbanding and hides the delete control entirely for a team with open work,
rather than offering a button whose only outcome is an error.

**"Leave unchanged" has to be expressible.** A `<select>` cannot post an omitted
field, and *any* owner value pins a finding against future routing. Without an
empty option an analyst could not add a triage comment without also taking the
routing decision away from the rules, so `owner` reads the same three ways
`assignee` already does: empty is unchanged, `none` is null, anything else is an
id. In the opposite direction, the queue collapses `?owner_group_id=` and
`?unowned=` into one `?owner=` dropdown — the API keeps them separate because an
absent parameter already means "do not filter" there, while two controls that
must not both be set is a state a URL can carry and a person can get wrong.

**Overdue is computed once, in Python.** `ownership.overdue` is widened to a
Protocol so the console can ask it of a `FindingRead`, because the alternative
is the template re-deriving it in Jinja and a second definition is the copy that
drifts. The overview counts both queues by asking the API rather than filtering
the open-findings page, which is capped — deriving them would under-report
exactly when the backlog is large enough to matter.

23 tests in `apps/api/tests/test_web_ownership.py`, plus docs/web.md.
make check green.

@icebergai-review-bot icebergai-review-bot Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Verdict

APPROVE

Completed bounded review across 1 immutable scope(s). One medium-impact navigation defect found in the new ownership screen.

Scope health

Convergence: healthy. Review mode: initial.
Recommended action: CONTINUE_INCREMENTAL.

  • No escalation signals.

Prior findings

Finding Status
No prior finding state

New findings

Root cause: The template exposes the underlying API filter name instead of the web route's normalized query parameter.

  • FOLLOW-UP ISSUE · medium: Team queue links use an ignored query parameterapps/api/src/iceberg_api/web/templates/admin/ownership.html:70
    Status: NEW. Attribution: new_in_scope.
    The ownership screen generates links with owner_group_id, but the web findings route only reads the owner query parameter and translates it to the API's owner_group_id. Consequently, the team-specific Open and Overdue counters navigate to an unfiltered findings queue (apart from state/overdue).
    Invariant: A team-specific ownership link must preserve the selected team when navigating to the findings queue.
    Ownership: Web ownership template and web findings-route query adapter.. Behaviour: The Teams table's Open and Overdue links..
    Evidence: ownership.html links to /findings?owner_group_id={{ group.id }}...; findings.py accepts owner and calls id_or_none(owner), with no owner_group_id request parameter. The overview correctly uses owner=none, and the added tests exercise ?owner={group.id}.

Fix-induced regressions

  • None evidenced.

Uncertainty

  • No material uncertainty recorded.

Validation

  • Static review of the supplied immutable diff; exact-head CI was reported as passed in the review data.

Residual risks

  • None identified.

@icebergai-review-bot
icebergai-review-bot Bot merged commit be54ec7 into main Aug 16, 2026
6 checks passed
@icebergai-review-bot
icebergai-review-bot Bot deleted the ownership-console-v3 branch August 16, 2026 03:16
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant