M6-08: Manage apps, webhooks and incoming webhooks¶
Status: ready | Depends on: M2-26, M2-27, M6-04 | Milestone: M6
Goal¶
After this ticket owners manage extensions in the desktop administration and in the console (01 5.5 "Apps, webhooks"; 11 4 to 8): installing an app by uploading its manifest file (never a URL), with the consent dialog of 11 4.1 that shows in plain text the scopes, events and target hosts, commands, components, grants and data outflow, lets the owner choose optional scopes and the concrete grant targets with the "Who has access?" preview, and confirms with step-up; the app token and the webhook key ID are shown exactly once; updating an app with a new manifest shows the diff and asks for new consent (app.scopes.update); suspending, resuming and uninstalling with the effects of 11 4.3 explained before confirming; outgoing webhook subscriptions per installation (event types within the granted events, target URL within webhook.allowed_hosts), their state, the deliveries with result and next attempt, and redelivery; incoming webhooks per channel (create, shown once, revoke), with the "incoming app" option of 11 8.1 for owners. Every change requires step-up. Widget tests and patrol web tests in make console-e2e cover the flows, and a test proves that the token of an incoming webhook appears exactly once. Why: apps and webhooks move data out of the instance, so the owner must see and confirm every outflow (11 4.1), and a token shown twice would weaken "shown once" (08 12).
Read first¶
docs/spec/11-extensions.md: 3 (manifest format and validation), 4 (installation, update, suspension and uninstallation with the table of 4.3), 5 (scopes and grants), 6 (limits;webhook.max_pending_per_subscription, automaticdisabled), 7 (outgoing webhooks: subscription rules, signature,webhook-keys), 8 (incoming webhooks: creation, "incoming app", call, token once), 14, 16.docs/spec/05-actions.md(core contract, read only): rowsapp.*,webhook_out.*,webhook_in.*,token.create,token.revoke,grant.create,access.preview; 2.6 (scopes table).docs/spec/01-product.md5.2 (administration rules), 5.5 (navigation).docs/api/openapi.yaml:listApps,validateManifest,installApp,upgradeAppManifest,createGrant,previewGrant,createToken,revokeToken,getApp,setAppScopes,suspendApp,resumeApp,uninstallApp,listWebhookSubscriptions,createWebhookSubscription,updateWebhookSubscription,deleteWebhookSubscription,listWebhookDeliveries,redeliverWebhook,listIncomingWebhooks,createIncomingWebhook,revokeIncomingWebhook,previewGrant,getWebhookKeys; schemasAppManifest,AppScopeand the install request and response.docs/DECISIONS.md: D-W-100 and its extension to M5 and M6 (D-W-120); section 10 (format ofD-M6-08-<n>rows) and the rows of M2-24 to M2-27.- Briefs (merged names win, D-W-100):
M2-24-apps-install.md,M2-26-webhooks-outgoing.md,M2-27-webhooks-incoming.md(server behaviour, errors, the once-only token),M6-04-console-admin.md(console hosting, step-up in the browser,patrolweb suites), the brief of M4-27 (grant wizard and preview for the grant targets). - Code on the base branch:
packages/admin_ui,apps/console,apps/client(administration),tools/console-e2e.
This ticket touches no API operation, no table, no action, no event and no configuration key.
Fixed requirements that apply¶
Owner's fixed build rules (apply verbatim):
- No MVP. New from scratch and directly in the final state; stages are only the order.
- Strictly modular: clear module and service boundaries, dependencies only through defined interfaces, no cross access.
- Documentation in code: every public module, class, function and interface with a docstring/doc comment (purpose, parameters, return value, errors, example where useful); comments explain the why, not the what. API documentation is generated from code (OpenAPI from the routes, dartdoc, Python API docs in MkDocs).
- Documentation in the repo: README per module/service, ADR for every architecture decision, CHANGELOG per release, keep the documentation site current. A change without matching documentation is not done.
- Quality gates (SonarQube, linters, type checking, security scanners, test coverage) block the merge.
Rules for every brief:
- Test first: write the failing test, run it and keep the red output for the report, then make it green, then clean up.
- No en dash and no em dash in any file you write; the
text-rulespre-commit hook rejects them, and the ARB test of M4-06 rejects them in interface texts. Use a hyphen or rephrase. - White label: examples, manifests and test data use
example.comand the people Alex, Sam and Robin; no company, host or personal names. Interface texts come frompackages/l10n. - No configuration key is added; limits shown (
webhook.allowed_hosts,webhook.max_pending_per_subscription) come from server responses. - Specification first: a change to behaviour updates
docs/spec/in the same change. The core contract is not edited by this ticket.
Product requirements implemented here:
- Installation from a manifest file only, with the consent dialog of 11 4.1 in plain text; grants with concrete targets chosen by the owner; step-up; token and key ID shown exactly once.
- Update with a diff and new consent for new scopes, events, hosts or grants (11 4.2).
- Suspend, resume and uninstall with the effects of 11 4.3 stated before confirming.
- Outgoing subscriptions within the granted events and allowed hosts; deliveries with state and redelivery; a
disabledsubscription shows its reason ("gap", failures). - Incoming webhooks per channel with the token shown once and the "incoming app" option for owners (11 8.1).
- Every change requires step-up; the screens show what the server returns and decide nothing about permissions themselves.
- Tokens shown once are never written to browser storage, logs, the local database or crash reports; they leave the screen when the person closes the dialog.
Scope¶
Create:
packages/admin_ui/lib/src/apps/: apps list and detail, install flow (file picker, manifest validation errors from the server, consent dialog, scope and grant target selection with the preview, confirmation), update diff, suspend and uninstall dialogs with the 11 4.3 table, outgoing subscriptions and deliveries, redelivery (Design notes 1 to 3).packages/admin_ui/lib/src/incoming_webhooks/: list per channel, create (with the "incoming app" option for owners as a client-side composition, Design note 5), the once-only token dialog with copy, revoke (Design note 4).packages/admin_ui/lib/src/secrets/once_only_secret.dart: the shared once-only dialog used for app tokens, incoming webhook tokens and, where the screens of M4-28 show agent tokens, refactored to use it (Design note 4).- Tests (Tests).
Change:
- Desktop administration navigation (
apps/client) and console routes (apps): host the screens. tools/console-e2e: thepatrolweb suites of this ticket.infra/compose/test/(theconsole-e2eoverlay of M6-03, which the installation bundle excludes): a webhook receiver service inside the Compose network that serves HTTPS with a certificate for its service name from the per-run test CA of M6-03 and records the deliveries for the test (reachable from the host only on127.0.0.1); in the E2E configuration only,webhook.allowed_hostsandegress.allowed_hosts(<receiver>;allow_private, because the receiver has a private Compose address) list the receiver, and theapicontainer trusts the test CA throughSSL_CERT_FILEpointing to a bundle of the system roots and the test CA (Go reads it on Linux); the release image and the bundle stay unchanged.packages/l10nARB files (consent texts, 11 4.3 effects);packages/admin_ui/README.md; rootCHANGELOG.mdunder[Unreleased];docs/DECISIONS.mdsection 10 rowsD-M6-08-1toD-M6-08-4.
Out of scope¶
- Server behaviour of apps and webhooks (M2-24 to M2-27); the ready-made integrations (M2-49, M2-50).
- Agents' webhook subscriptions (
listAgentWebhookSubscriptions, managed with the agents screen of M4-28). - Changes to the core contract.
Boundaries¶
- Work in a dedicated git worktree on the branch
feat/M6-08-apps-webhooks-admin; one writer per worktree. - Conventional Commits with the package or component as scope (for example
feat(admin-ui): ...,test(console): ...) and a DCO sign-off on every commit (git commit -s); squash into meaningful commits before the merge (docs/quality/push-discipline.md). - Never push to
main, never tag and never create remote repositories; the only remote steps are the branch push and the pull request ofAGENTS.md. - Do not modify files outside the scope above, except the CHANGELOG, README and
docs/DECISIONS.mdentries named in this brief. - Follow the host rules supplied by the launcher (ports, machine rules); the stack publishes its ports only on
127.0.0.1; webhook targets in the E2E are a receiver inside the Compose network listed inwebhook.allowed_hostsof the E2E configuration. - Stop only processes and containers you started, by PID or container ID. Temporary directories only via
mktemp -d, removed at the end. - Never read or print secrets; tokens in tests are compared and discarded, never logged.
- Docker, the pinned Flutter and the pinned browsers must be available for
make console-e2e; the ticket must run it once on the build host or stop withVerdict: BLOCKED <tool> missing. - Never lower a threshold, disable a gate, delete a test, raise
repo.max_file_kbor extendquality/licenses/allowed.txt. - No permission logic in the client (15 1 principle 1).
- If the core contract or
docs/api/lacks something this ticket needs, stop withVerdict: BLOCKED <reason>instead of building a deviation (P-11). - This brief was written before milestone M1 closed (D-W-100, extended to M5 and M6 by D-W-120). Where the merged code of an earlier ticket names a package, type, function, file, provider or make target differently from this brief, follow the code and list each difference in the report; where it behaves differently from the specification, stop with
Verdict: BLOCKED <reason>.
Design notes¶
-
Install flow (11 4.1; M2-24; Ruling for docs/DECISIONS.md).
installAppanswers201 Appwithout a token and without grants (M2-24,AppInstall,App); grants come fromcreateGrantper target and the token fromcreateToken. Step 1: pick a manifest file (.jsonor.yamlas 11 3.1 allows) and validate it withvalidateManifest, which returns the parsed manifest or the rule that failed. Step 2: the consent dialog lists, from the server's answer or the parsed manifest: required and optional scopes with their meaning from 05 2.6 in plain text, events, target hosts of outgoing data, commands, components, requested grants with a target picker per grant,data_egress; each grant target choice runspreviewGrantand shows the plain text. Step 3: one confirmation with step-up runs, in order,installAppwith the chosen scopes,createGrantfor each chosen target (each already previewed), andcreateTokenfor the app; the token appears in the once-only dialog, the key ID for receivers comes fromgetWebhookKeys. If a later call fails, the screen shows which steps succeeded and offers to retry the failed one or to uninstall the half-installed app (uninstallApp); nothing is retried silently. An error of 3.2 validation shows the field and rule. -
Update and lifecycle (11 4.2, 4.3). Update: upload a new manifest with the same
slugthroughupgradeAppManifest; show added and removed scopes, events, hosts and grants; removed items take effect at once (say so); added ones needsetAppScopeswith consent. Suspend, resume, uninstall: the dialog shows the row of 11 4.3 for the chosen action before the step-up. -
Outgoing webhooks (11 7). A subscription form limits event types to the granted events (from
getApp) and checks the URL schemehttps; the server enforceswebhook.allowed_hostsand answers with the reason; deliveries show state, attempts, next attempt and the last status code, never the response body (not stored, 11 7); "Redeliver" callsredeliverWebhook. The key for receivers is shown fromgetWebhookKeyswith a copy action. -
Once-only secrets (11 4.1, 8.1; 08 12; Ruling for docs/DECISIONS.md). One dialog widget shows a secret (app tokens, incoming webhook tokens, and the
hmac_secretthatcreateWebhookSubscriptionreturns once where the subscription uses it) with a copy action and the warning that it will not be shown again; the secret lives only in the widget state, is cleared on close, is excluded from semantics announcements except on explicit focus, and is never passed to a logger, a crash report, the local database or browser storage. A test asserts that after closing the dialog no widget, provider or log capture still holds the canary token, and that reopening the detail screen shows only the token's prefix or nothing. -
"Incoming app" without an API of its own (11 8.1; M2-27).
IncomingWebhookCreaterequires anapp_id, and the server offers no combined call (M2-27 reported the gap). For owners the screen composes it:installAppwith a built-in minimal manifest (messages:writeonly, no events, no hosts),createGrantwithwriteon exactly the chosen channel, thencreateIncomingWebhookwith thatapp_id, under one confirmation; a failure after the first step offers retry oruninstallApp. Managers choose an existing app (11 8.1). Record the composition as a ruling and name the API gap in the report for a later spec change. -
Rulings for
docs/DECISIONS.md(D-M6-08-<n>): the install flow and where validation happens (note 1); update and lifecycle dialogs (note 2); the deliveries view without response bodies (note 3); the shared once-only dialog (note 4); the composed "incoming app" (note 5).
Tests (write them first)¶
- Widget and golden (
packages/admin_ui/test/apps/,test/incoming_webhooks/, fake server): install with consent, optional scope deselected, grant targets with preview, confirmation; update diff; suspend and uninstall dialogs with the 11 4.3 effects; subscriptions and deliveries; incoming webhook create and revoke; light and dark, text scaling 1 and 2, de and en. once_only_secret_test.dart(PLAN row): the incoming webhook token appears exactly once; after close, no trace of the canary in widgets, providers, the log capture or the fake storage; the detail screen never shows it again.patrolweb suites inmake console-e2e: install an app from a manifest file with step-up and see the token once; create an outgoing subscription to the receiver in the Compose network and see a delivery succeed; create an incoming webhook, post to it with the shown token from the test, see the message in the channel through the API, revoke it and see the next post answered404.
Commands that must pass, from the repository root:
cd packages/admin_ui && flutter analyze --fatal-infos && flutter test --coveragecd apps/console && flutter test --coverageandcd apps/client && flutter test --coveragemake console-e2eon the build hostmake arch,make lint,make fmt-check,make coveragemake check
Acceptance criteria¶
- Widget and
patrolweb tests of the flows pass in desktop and console. - The token of an incoming webhook appears exactly once; app tokens likewise.
- Installation shows the consent of 11 4.1 with concrete grant targets; update, suspend, resume and uninstall state their effects; every change requires step-up.
- README, ARB files, CHANGELOG and the rulings are updated.
-
make checkis green.
Definition of done¶
- Code and tests as specified; doc comments on every public Dart symbol; comments explain why.
packages/admin_ui/README.mdupdated in English.- Rulings of Design note 6 recorded in
docs/DECISIONS.mdsection 10. - Root
CHANGELOG.mdentry under 'Unreleased'. - An ADR only if you take an architecture decision beyond this brief (MADR 4 as in
docs/adr/0000-template-madr-4.md, headings and text in English, next free number,make adr-index). make checkgreen.- An independent code review over the diff, then the red team gate (
docs/quality/red-team-gate.md, verdictJAorNEIN); onNEINfix with a test per finding and have the delta checked again. Record both verdicts. - Land the change as
AGENTS.md("How work lands onmain") describes: a pull request from this branch, squash-merged only when review, red team andmake check,ci-okand the review threads are green; thengit worktree remove.
Report¶
Write to {{REPORT_PATH}}:
- First line exactly
Verdict: DONEorVerdict: BLOCKED <reason>. - Commits (SHA and subject), changed files with line counts, test results with counts (the red run before the change and the green run after), the summary of
make checkand ofmake console-e2e. - Review and red team verdicts and the findings fixed.
- What is not proven.
- Follow-ups that could not be done in this scope, each with its reason, every difference to the names of this brief (D-W-100), and spec gaps found.
Then write {{DONE_PATH}} with exit=0 sha=<sha> or exit=1 <reason>.