You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
kbagent notification (from #600 / PR #615, #618) is read-only: list and detail over the Notification service's /project-subscriptions. That closed the audit gap, but auditing surfaces things you then want to fix, and there's no way to do that from the CLI.
Concrete motivating case: a fleet-wide Flow-notification audit across 20 projects found 10 /project-subscriptions still naming departed employees / a decommissioned mailbox (michal.hruska@keboola.com, monika.feigler@keboola.com, lenka.hancarova@p3parks.com). All 10 happened to target already-orphaned configs this time (harmless), but that's luck, not policy — the next one could easily be attached to a live production flow, and there'd still be nothing to do about it except open the UI by hand.
What exists in the API (confirmed against the public swagger, https://notification.eu-central-1.keboola.com/docs/swagger.yaml)
DELETE /project-subscriptions/{subscriptionId} — delete by ID. Same auth. 204 on success, 404 if it doesn't exist for the current project.
No PATCH/PUT exists. "Fix a recipient" is necessarily delete-then-recreate, not an in-place edit — worth designing the CLI/service surface around that from the start rather than pretending an update primitive exists.
NewProjectSubscription request body: required event (kebab-case string, e.g. job-failed) and recipient ({channel: "email", address} or {channel: "webhook", url}); optional filters (array of {field, value, operator} — same dotted-path fields list/detail already parse, e.g. job.configuration.id, job.component.id, branch.id) and expiresAt.
Proposed solution
Extend the exact layers #615 already built — no new abstraction:
create_subscription(alias, event, component_id, config_id, channel, address_or_url, branch_id=None, ...) — builds the filters list from component_id/config_id/branch_id the same way list_subscriptions already resolves config names, so the caller passes friendly parameters instead of hand-rolling job.configuration.id-style filter dicts.
delete_subscription(alias, subscription_id) — thin wrapper, no name resolution needed.
replace_subscription_recipient(alias, subscription_id, new_address) — convenience for the actual motivating use case: get_project_subscription → delete → recreate with the same event/filters/expiresAt but the new recipient. This is the one that turns "10 stale addresses found" into "10 fixed" in one command per row instead of manual delete+recreate every time.
commands/notification.py (notification_app): add
kbagent notification create --project ALIAS --event NAME --component-id ID --config-id ID [--branch ID] --channel email|webhook --address ADDR [--expires-at TS]
kbagent notification delete --project ALIAS --subscription-id ID [--yes]
kbagent notification replace-recipient --project ALIAS --subscription-id ID --address NEW_ADDR [--yes]
delete and replace-recipient are destructive/mutating — register them under the same --deny-writes permission gate as flow update/config update, not the read-only bucket list/detail currently sit in.
No in-place update exists in the API, so don't build a CLI update that pretends otherwise — replace-recipient above is explicit about being delete+recreate under the hood (and inherits a new subscription_id, worth surfacing in the output so scripts don't cache the old one).
Why it matters
Closes the loop #600 opened: list/detail let you find problems (stale addresses, orphaned subscriptions targeting deleted configs) across every project in one command; today, fixing what you found still means opening the Keboola UI once per subscription. Same audit-then-fix workflow this org already uses for flow update/config update.
Problem
kbagent notification(from #600 / PR #615, #618) is read-only:listanddetailover the Notification service's/project-subscriptions. That closed the audit gap, but auditing surfaces things you then want to fix, and there's no way to do that from the CLI.Concrete motivating case: a fleet-wide Flow-notification audit across 20 projects found 10
/project-subscriptionsstill naming departed employees / a decommissioned mailbox (michal.hruska@keboola.com,monika.feigler@keboola.com,lenka.hancarova@p3parks.com). All 10 happened to target already-orphaned configs this time (harmless), but that's luck, not policy — the next one could easily be attached to a live production flow, and there'd still be nothing to do about it except open the UI by hand.What exists in the API (confirmed against the public swagger,
https://notification.eu-central-1.keboola.com/docs/swagger.yaml)POST /project-subscriptions— create a subscription.security: [StorageApiTokenAuth, StorageApiBearerToken, ...], identical to the read path already implemented — no elevated scope needed, same as the correction already made in kbagent notification: fleet-wide audit of Flow Notification subscriptions (Notification Service API) #600's discussion.DELETE /project-subscriptions/{subscriptionId}— delete by ID. Same auth.204on success,404if it doesn't exist for the current project.PATCH/PUTexists. "Fix a recipient" is necessarily delete-then-recreate, not an in-place edit — worth designing the CLI/service surface around that from the start rather than pretending an update primitive exists.NewProjectSubscriptionrequest body: requiredevent(kebab-case string, e.g.job-failed) andrecipient({channel: "email", address}or{channel: "webhook", url}); optionalfilters(array of{field, value, operator}— same dotted-path fieldslist/detailalready parse, e.g.job.configuration.id,job.component.id,branch.id) andexpiresAt.Proposed solution
Extend the exact layers #615 already built — no new abstraction:
client/notifications.py(_NotificationsMixin): addMirrors the existing
list_project_subscriptions/get_project_subscriptionin the same file.services/notification_service.py(NotificationService): addcreate_subscription(alias, event, component_id, config_id, channel, address_or_url, branch_id=None, ...)— builds thefilterslist fromcomponent_id/config_id/branch_idthe same waylist_subscriptionsalready resolves config names, so the caller passes friendly parameters instead of hand-rollingjob.configuration.id-style filter dicts.delete_subscription(alias, subscription_id)— thin wrapper, no name resolution needed.replace_subscription_recipient(alias, subscription_id, new_address)— convenience for the actual motivating use case:get_project_subscription→ delete → recreate with the sameevent/filters/expiresAtbut the new recipient. This is the one that turns "10 stale addresses found" into "10 fixed" in one command per row instead of manual delete+recreate every time.commands/notification.py(notification_app): adddeleteandreplace-recipientare destructive/mutating — register them under the same--deny-writespermission gate asflow update/config update, not the read-only bucketlist/detailcurrently sit in.Non-goals
POST /notifications(send a one-off notification right now) is a different endpoint entirely and requires a Manage API application token withnotifications:push-eventscope — already called out as out-of-scope in kbagent notification: fleet-wide audit of Flow Notification subscriptions (Notification Service API) #600's discussion thread, still true here.updatethat pretends otherwise —replace-recipientabove is explicit about being delete+recreate under the hood (and inherits a newsubscription_id, worth surfacing in the output so scripts don't cache the old one).Why it matters
Closes the loop #600 opened:
list/detaillet you find problems (stale addresses, orphaned subscriptions targeting deleted configs) across every project in one command; today, fixing what you found still means opening the Keboola UI once per subscription. Same audit-then-fix workflow this org already uses forflow update/config update.