Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 9 additions & 5 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -773,15 +773,19 @@ kbagent notification detail --project NAME --subscription-id ID
# flow's configuration JSON, so `flow detail` / `config detail` never showed them. The in-flow
# `type: "notification"` TASK is a different mechanism and stays visible there.
# Event names are kebab-case (job-failed, job-succeeded, job-succeeded-with-warning,
# job-processing-long + phase-job-* variants); --event is forwarded verbatim and NOT validated
# (the API declares EventName as an open string). Filter fields are dotted paths into the event
# job-processing-long + phase-job-* variants); --event is NOT validated (the API declares
# EventName as an open string). The service ACCEPTS ?event= and then IGNORES it (200 + the
# full list, verified live), so kbagent sends it AND narrows client-side -- a direct API
# caller must do the same or get a superset. Filter fields are dotted paths into the event
# payload (job.component.id, job.configuration.id, branch.id, phase.id) -- --component-id /
# --config-id match those CLIENT-SIDE, since the API supports only ?event=. A subscription with
# --config-id likewise match CLIENT-SIDE. A subscription with
# NO filters is project-wide and fires for EVERY job; those are dropped by --component-id/
# --config-id and counted in `project_wide_excluded` (plus a warning) so "who gets paged for
# this flow" is never silently under-reported. Webhook recipients carry `url`, email carries
# `address` -- both render in the single `address` column. Subscriptions are project-level, not
# branch-scoped; a branch-specific one carries a branch.id filter shown in the Branch column.
# `address` -- both render in the single `address` column. The Branch column is populated on
# EVERY row, production included: the UI always writes a branch.id filter, and for production
# that value is the DEFAULT branch's numeric id -- compare against `branch list` to tell a
# production alert from a dev-branch one; presence alone means nothing.

kbagent context
kbagent init [--from-global] [--project ALIAS ...]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -308,8 +308,10 @@ Requires the project to be added with its **master ('owner') Storage API token**
## Notification Subscriptions (Flow Notifications Tab) (since v0.86.0)
- `notification list [--project NAME ...] [--event NAME] [--component-id ID] [--config-id ID]` -- fleet-wide list of Notification Service subscriptions: the recipients behind the Flow Builder's **Notifications tab** (bell icon -- Success / Error / Processing-delay / Warning cards). These live in a separate platform service (`notification.{stack}`, plain project Storage token, no elevated scope), **not** in the flow's `configuration` JSON, which is why `flow detail` / `config detail` never showed them. The in-flow `type: "notification"` **task** is a different mechanism and remains visible via `flow detail`. Row keys: `project_alias`, `subscription_id`, `event`, `component_id`, `config_id`, `config_name` (resolved), `branch_id`, `phase_id`, `channel`, `address`, `expires_at`, `scope`, `filters`
- `notification detail --project NAME --subscription-id ID` -- one subscription with every filter printed verbatim, including threshold filters (`durationOvertimePercentage`) that have no dedicated column
- Event names are **kebab-case**: `job-failed`, `job-succeeded`, `job-succeeded-with-warning`, `job-processing-long`, plus the `phase-job-*` variants. `--event` is forwarded verbatim as `?event=` and deliberately **not** validated -- the API declares `EventName` as an open string, so a newer platform event works without a kbagent release
- `--component-id` / `--config-id` filter **client-side**: the API's only server-side filter is `?event=`. They match the subscription's own `job.component.id` / `job.configuration.id` filter values (dotted paths into the event payload -- not flat `componentId`/`configurationId` keys)
- Event names are **kebab-case**: `job-failed`, `job-succeeded`, `job-succeeded-with-warning`, `job-processing-long`, plus the `phase-job-*` variants. `--event` is deliberately **not** validated -- the API declares `EventName` as an open string, so a newer platform event works without a kbagent release
- **The service ignores `?event=`** -- verified live: a filtered request answers `200` with the project's FULL subscription list. kbagent sends the parameter anyway (a server-side fix would then cost nothing) and narrows client-side, so `--event` is correct from the CLI; a direct API caller gets a superset. See [gotchas.md](gotchas.md)
- `--component-id` / `--config-id` filter **client-side** too. They match the subscription's own `job.component.id` / `job.configuration.id` filter values (dotted paths into the event payload -- not flat `componentId`/`configurationId` keys)
- `branch_id` is populated on **every** row, production included -- the Flow Builder always writes a `branch.id` filter and uses the default branch's numeric id for production. A filled Branch column does NOT mean "dev-branch only"; cross-check `kbagent branch list`
- A subscription with **no filters at all** is project-wide and fires for every job. Those are excluded by `--component-id`/`--config-id` and reported as `project_wide_excluded` (plus a warning in human mode), so "who gets paged when this flow breaks" is never silently under-reported -- see [gotchas.md](gotchas.md)
- Exposed over `kbagent serve` as `GET /notifications` and `GET /notifications/{project}/{subscription_id}`
- Read-only in this release; creating/deleting subscriptions is not exposed
Expand Down
42 changes: 29 additions & 13 deletions plugins/kbagent/skills/kbagent/references/gotchas.md
Original file line number Diff line number Diff line change
Expand Up @@ -1218,20 +1218,31 @@ events and emits a final `done` SSE frame mirroring the same record.
- **Event names are kebab-case, and the field is an open string.**
`job-failed`, `job-succeeded`, `job-succeeded-with-warning`,
`job-processing-long`, plus the `phase-job-*` variants -- *not* the
camelCase `jobFailed` spelling. `--event` is forwarded verbatim as
`?event=` and deliberately not validated against a fixed list, because the
API declares `EventName` as `type: string` with no enum. A misspelled event
therefore returns an empty list, not an error -- if a query comes back
empty, re-check the spelling before concluding nobody is subscribed.
camelCase `jobFailed` spelling. `--event` is deliberately not validated
against a fixed list, because the API declares `EventName` as
`type: string` with no enum. A misspelled event therefore returns an empty
list, not an error -- if a query comes back empty, re-check the spelling
before concluding nobody is subscribed.
- **The service IGNORES `?event=`; kbagent narrows client-side**
(verified live, not from the swagger). A filtered request answers `200` with
the project's FULL subscription list -- the documented query parameter has
no effect. kbagent still sends it (so a server-side fix would cost nothing)
and then filters the rows itself, which is why `--event` behaves correctly
from the CLI. **Anything talking to the service directly must do the same
narrowing**, or "who gets paged on failure" silently answers with a superset
that includes success and processing-delay recipients. Same lesson as
Storage's `list_files`: a documented query parameter is a statement of
intent, not of behavior -- check what comes back.
Comment thread
padak marked this conversation as resolved.
- **Filter fields are dotted paths into the event payload.**
`job.component.id`, `job.configuration.id`, `branch.id`, `phase.id`,
`durationOvertimePercentage` -- *not* flat keys like `configurationId` or
`component`. Only the first four have dedicated output columns; everything
else rides along in the raw `filters` list (`notification detail` prints it
verbatim).
- **`--component-id` / `--config-id` filter client-side.** The API's only
server-side filter is `?event=`; everything else is applied by kbagent after
fetching the project's full subscription list.
- **`--component-id` / `--config-id` filter client-side** -- as does
`--event` (see above). The endpoint has no server-side filter that works at
all: kbagent always fetches the project's full subscription list and narrows
it locally.
- **A subscription with NO filters is project-wide and fires for every job.**
`filters` is optional in the schema (only `event` and `recipient` are
required), so a catch-all "page me on any failure" subscription is legal and
Expand All @@ -1250,11 +1261,16 @@ events and emits a final `done` SSE frame mirroring the same record.
- **Webhook recipients carry `url`, email recipients carry `address`.** Both
render in the single `address` column; a `--json` consumer reading the raw
API would need to handle both keys.
- **Subscriptions are project-level, not branch-scoped.** There is no branch
query parameter. A branch-specific subscription carries a `branch.id`
filter, surfaced in the `branch_id` column. Configuration *names* are
resolved against the project's active branch, so a flow that exists only in
production shows a blank `config_name` while a dev branch is active.
- **`branch_id` is populated on EVERY subscription, production included** --
a filled Branch column does NOT mean "dev-branch only" (verified live).
There is no branch query parameter, but the Flow Builder always writes a
`branch.id` filter, and for a production subscription that value is the
**default branch's numeric id** (e.g. `390737` for a project whose `Main`
branch has that id). To tell a production alert from a dev-branch one you
must compare against `kbagent branch list` for that project -- presence
alone tells you nothing. Configuration *names* are resolved against the
project's active branch, so a flow that exists only in production shows a
blank `config_name` while a dev branch is active.
- **`config_name` is best-effort.** It is resolved by an exact
`(component_id, config_id)` match, falling back to a config-ID lookup when
the subscription has no component filter -- but only when unambiguous. Two
Expand Down
9 changes: 8 additions & 1 deletion src/keboola_agent_cli/changelog.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,9 @@
"registered alias already holds -- no elevated scope, no manage token.",
"`notification list` counts the project-wide subscriptions its scope filters hide, "
'so "who gets paged" is never under-reported. `--component-id` / `--config-id` '
"filter client-side (the API's only server-side filter is `?event=`) and match the "
"filter client-side -- as does `--event`: the service accepts its documented "
"`?event=` parameter and then ignores it, answering 200 with the project's full "
"list (verified live), so kbagent sends it and narrows the rows itself. They match the "
"subscription's own `job.component.id` / `job.configuration.id` filter values. A "
"subscription with NO filters is project-wide and fires for every job in the "
"project; those rows are excluded by those two flags but counted in "
Expand All @@ -48,6 +50,11 @@
"declares `EventName` as an open string.",
"`kbagent serve` mirrors the new group 1:1: `GET /notifications` and "
"`GET /notifications/{project}/{subscription_id}`.",
'A filled Branch column on `notification list` does not mean "dev-branch only". '
"The Flow Builder writes a `branch.id` filter on EVERY subscription, and for a "
"production one that value is the default branch's own numeric id -- so `branch_id` "
"is populated on every row, production included. Cross-check `kbagent branch list` "
"for the project to tell a production alert from a dev-branch one.",
],
"0.85.1": [
"Fix: `kbagent config new --push` no longer creates a broken configuration from a "
Expand Down
15 changes: 12 additions & 3 deletions src/keboola_agent_cli/client/notifications.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,10 +38,19 @@ class _NotificationsMixin(_CoreClient):
def list_project_subscriptions(self, event: str | None = None) -> list[dict[str, Any]]:
"""List every notification subscription for the token's project.

.. warning::
**The service IGNORES ``event``** -- verified against a live stack,
where a filtered request answers 200 with every subscription in the
project. The parameter is still sent because the swagger documents
it and a server-side fix would then cost nothing, but THIS METHOD
DOES NOT NARROW. Callers that need narrowing must filter the
returned list themselves; ``NotificationService`` does exactly that.

Args:
event: Optional event-name filter (e.g. ``job-failed``). Passed
through to the API as ``?event=``; a falsy value is omitted
entirely rather than sent as an empty parameter.
event: Optional event-name filter (e.g. ``job-failed``). Sent as
``?event=``; a falsy value is omitted entirely rather than sent
as an empty parameter. See the warning above -- passing it does
not reduce the result.

Returns:
List of subscription dicts verbatim from the API.
Expand Down
25 changes: 16 additions & 9 deletions src/keboola_agent_cli/commands/context.py
Original file line number Diff line number Diff line change
Expand Up @@ -1009,15 +1009,22 @@
Event names are kebab-case: job-failed, job-succeeded,
job-succeeded-with-warning, job-processing-long, and the phase-job-*
variants. --event is NOT validated against that list (the API declares
EventName as an open string), it is forwarded verbatim as ?event=.
--component-id / --config-id filter CLIENT-SIDE on the subscription's
own job.component.id / job.configuration.id filter values; the API
supports only ?event=. A subscription with NO filters is project-wide
(scope="project-wide") and fires for every job -- those are excluded by
--component-id/--config-id and reported as project_wide_excluded so the
"who gets paged for this flow" answer is never silently incomplete.
Subscriptions are project-level, not branch-scoped; a branch-specific one
carries a branch.id filter, surfaced in the branch_id column.
EventName as an open string).
ALL filtering is CLIENT-SIDE, --event included: the service accepts
?event= and then IGNORES it, answering 200 with the project's full
subscription list (verified live). kbagent still sends the parameter and
narrows the rows itself, so --event is correct from the CLI -- anything
calling the service directly must narrow too, or it gets a superset.
--component-id / --config-id match the subscription's own
job.component.id / job.configuration.id filter values.
A subscription with NO filters is project-wide (scope="project-wide") and
fires for every job -- those are excluded by --component-id/--config-id
and reported as project_wide_excluded so the "who gets paged for this
flow" answer is never silently incomplete.
branch_id is populated on EVERY row, production included: the Flow
Builder always writes a branch.id filter and uses the DEFAULT branch's
numeric id for production. A filled Branch column does NOT mean
"dev-branch only" -- cross-check `branch list` to tell them apart.

kbagent notification detail --project NAME --subscription-id ID
One subscription with every filter printed verbatim, including threshold
Expand Down
14 changes: 13 additions & 1 deletion src/keboola_agent_cli/services/notification_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -217,7 +217,9 @@ def list_subscriptions(
Args:
aliases: Project aliases to query. ``None`` / empty means every
registered project.
event: Event-name filter, applied server-side via ``?event=``.
event: Event-name filter. Sent as ``?event=`` AND applied
client-side, because the service ignores the parameter -- see
the comment in :meth:`_fetch_project_subscriptions`.
component_id: Client-side filter on the subscription's
``job.component.id`` filter value (the API has no such filter).
config_id: Client-side filter on ``job.configuration.id``.
Expand Down Expand Up @@ -326,6 +328,16 @@ def _fetch_project_subscriptions(
raw = client.list_project_subscriptions(event=event)
rows = [_extract_subscription_fields(sub) for sub in raw]

# The live service IGNORES ``?event=`` -- verified against a real
# stack, where a filtered request answers 200 with every
# subscription in the project. The parameter is still sent (the
# swagger documents it, and a server-side fix would then cost
# nothing), but the narrowing has to happen here or --event
# answers "who gets paged on failure" with a superset that
# includes success recipients.
if event:
rows = [row for row in rows if row["event"] == event]

# Count only rows the filter actually DROPPED. A subscription
# filtering on job.component.id alone is labelled project-wide
# (scope keys off the config filter), but --component-id KEEPS
Expand Down
Loading