mirror of
https://github.com/multica-ai/multica.git
synced 2026-07-17 15:19:00 +02:00
agent/lambda/768b92e0
3 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
e07b5403ab |
MUL-4502: make autopilot webhook admission durable (#5386)
* fix(autopilots): make webhook admission durable Co-authored-by: multica-agent <github@multica.ai> * fix(autopilots): address webhook delivery review Co-authored-by: multica-agent <github@multica.ai> --------- Co-authored-by: Eve <eve@multica-ai.local> Co-authored-by: multica-agent <github@multica.ai> |
||
|
|
d970b68ce7 |
feat(autopilot): View/Write permission layer + member access delegation (MUL-3807) (#4695)
* feat(autopilot): add View/Write permission layer Autopilot write and execute operations were gated only by workspace membership, so any member could edit, delete, trigger, or rotate the webhook of any autopilot, and GetAutopilot returned webhook tokens to every member (a token alone can trigger the autopilot). - Add canWriteAutopilot / requireAutopilotWrite: update, delete, trigger, replay-delivery, and all trigger/secret management now require the autopilot creator or a workspace owner/admin. - Redact webhook_token/path/url in GetAutopilot for callers without write access; trigger metadata otherwise stays visible (View default = all members). Creating an autopilot stays open to any member. - ANDs with the existing private-assignee-agent dispatch gate. MUL-3807 Co-authored-by: multica-agent <github@multica.ai> * feat(autopilot): delegate write access via collaborators + manage-access UI Adds an explicit grant primitive so an autopilot's creator/admin can authorize specific workspace members to manage it, with a frontend entry point — beyond the implicit creator/owner-admin set from the prior commit. Backend: - New autopilot_collaborator table (migration 128, members-only, app-layer cleanup, no FK) + sqlc queries. - memberCanWriteAutopilot now also honors explicit collaborators; the write gate, webhook-secret redaction, and a new per-caller can_write flag (on list + detail) all flow through it. - POST/DELETE /api/autopilots/{id}/collaborators (writer-gated); GetAutopilot embeds the collaborators list. Delete cleans up grants in its transaction. - Tests: grant->write->revoke flow, non-writer can't grant, non-member rejected. Frontend (web + desktop via packages/views): - ManageAccessDialog: member picker to grant/revoke, current list with remove. - 'Manage access' entry in the autopilot detail header; edit/run/add-trigger/ delete and the list-row kebab + per-trigger rotate/delete now gate on can_write (absent => allowed, server stays the gate). - can_write wired through types/schema/api client/mutations; en + zh-Hans copy. MUL-3807 Co-authored-by: multica-agent <github@multica.ai> * fix(autopilot): add manage-access i18n keys to ja/ko locales The locale parity test requires every non-EN bundle to cover every EN key. The prior commit added detail.manage_access + the access.* block to en and zh-Hans only, failing parity for ja and ko. Add the translated keys to both. Co-authored-by: multica-agent <github@multica.ai> * fix(autopilot): restrict access-list management to creator/admin only Final-review fix: AddAutopilotCollaborator/RemoveAutopilotCollaborator used requireAutopilotWrite, which counts granted collaborators as writers — so a collaborator could in turn grant/revoke others, a privilege escalation contradicting the 'collaborators cannot re-grant' design. - New requireAutopilotAccessManagement guard uses the narrower autopilotWriteByOwnership predicate (creator or workspace owner/admin only); swapped into both collaborator endpoints. Collaborators keep their edit/trigger/secret write-execute rights. - GetAutopilot now also stamps can_manage_access (narrower than can_write); the detail page gates the 'Manage access' button on it so collaborators no longer see an entry that would 403. - Tests: collaborator grant-others -> 403, revoke-peer -> 403, while retaining edit; can_manage_access true for owner, false for collaborator. MUL-3807 Co-authored-by: multica-agent <github@multica.ai> --------- Co-authored-by: J <j@multica.ai> Co-authored-by: multica-agent <github@multica.ai> |
||
|
|
2323b72710 |
feat(autopilots): webhook delivery layer + idempotency/signature/replay (MUL-2334) [PR1] (#2774)
* feat(autopilots): webhook delivery layer + idempotency / signature / replay (MUL-2334)
Splits "inbound webhook receipt" from "autopilot run creation" so we can
record duplicate attempts, signature outcomes, and ignored/skipped
deliveries — and replay a delivery on demand. v1 ingress wrote straight
into autopilot_run.trigger_payload, which collapsed the two concerns and
left run_only autopilots vulnerable to provider retry storms.
Backend only (PR1). UI Deliveries tab follows in PR2.
Schema (migration 093):
- autopilot_trigger.provider: 'generic' | 'github' (default 'generic').
- autopilot_trigger.signing_secret: nullable plaintext (HMAC needs it
cleartext; mirrors how webhook_token is stored).
- webhook_delivery: one row per inbound POST. Carries raw_body,
selected_headers, dedupe_key/source, signature_status,
autopilot_run_id, replayed_from_delivery_id, response_status / body.
- Partial unique index on (trigger_id, dedupe_key) excludes NULL and
'rejected' rows, so a wrong-secret 401 does NOT permanently block a
future retry with the same X-GitHub-Delivery once the operator fixes
the secret.
Ingress flow (autopilot_webhook.go), persist-first + sync dispatch:
1. IP rate limit -> 2. token lookup -> 3. token rate limit ->
4. read raw body -> 5. autopilot/workspace cross-check ->
6. normalize JSON (400 without persistence on parse failure) ->
7. compute dedupe key + signature status ->
8. INSERT delivery (status=queued). On (trigger_id, dedupe_key)
unique-violation: bump attempt_count on existing row and return
the original delivery_id + autopilot_run_id with 200 ->
9. invalid/missing signature: UPDATE -> rejected, return 401 with
delivery_id (no dispatch, not replayable) ->
10. trigger disabled / autopilot paused/archived: UPDATE -> ignored,
return 200 ->
11. DispatchAutopilot synchronously, UPDATE -> dispatched/skipped/failed
with autopilot_run_id and the response body we returned ->
12. TouchAutopilotTriggerFiredAt and return 200.
No new long-running worker. A stale 'queued' row only happens if the
process dies between INSERT and UPDATE; that's a follow-up sweeper, not
this PR.
Authenticated API:
- GET /api/autopilots/{id}/deliveries (slim list)
- GET /api/autopilots/{id}/deliveries/{deliveryId} (with raw_body)
- POST /api/autopilots/{id}/deliveries/{deliveryId}/replay -> creates
a new delivery row (replayed_from_delivery_id set), dispatches a
new run, never collapses onto the original via dedupe.
- PUT /api/autopilots/{id}/triggers/{triggerId}/signing-secret
Write-only; trigger response surfaces has_signing_secret +
signing_secret_hint (last 4 chars), never the secret itself.
Signature verification reuses the GitHub-compatible
X-Hub-Signature-256: sha256=<hex(hmac(body, secret))> scheme; the
HMAC helper is constant-time. Invalid/missing signatures still count
against per-IP and per-token rate limits.
autopilot_run.trigger_payload is intentionally preserved — delivery
records the HTTP receipt; run records the normalized envelope handed
to the agent. They are two different views.
Tests (Postgres-backed):
- delivery persistence on accept
- dedupe via Idempotency-Key and X-GitHub-Delivery; run_only retry
storm pin (3 retries -> 1 run)
- invalid signature: 401 + rejected row + no run linkage
- missing signature when secret configured: 401 + 'missing' state
- valid signature dispatches
- signing secret never echoed in trigger responses; hint shows last 4
- min-length and clear-by-empty for signing secret PUT
- replay creates a NEW delivery + new run; rejected deliveries cannot
be replayed
- list omits raw_body; detail includes it; cross-autopilot ID returns
404 (workspace isolation defense in depth)
- provider validation: unknown -> 400, github -> 201 round-trips
- bad-signature stream still counts against per-token rate limit
Co-authored-by: multica-agent <github@multica.ai>
* fix(autopilots): address PR review on webhook delivery layer (MUL-2334)
- Exclude `failed` from the (trigger_id, dedupe_key) partial unique index
alongside `rejected`, so a transient ingress failure does not strand the
provider's stable X-GitHub-Delivery / Idempotency-Key retry. Update the
dedupe lookup to prefer non-terminal rows under the same predicate.
- Tighten delivery status enum: drop `skipped` from the CHECK constraint
and from the handler. A run that was admission-skipped (e.g. runtime
offline) is now recorded as delivery=`dispatched` linked to the
skipped run, with the response payload carrying status=`skipped`.
Source of truth for skipped-ness is autopilot_run.status, not the
delivery row — keeps the Deliveries UI enum unambiguous.
- On dispatch error, link the (possibly non-nil) autopilot_run returned
by DispatchAutopilot to the failed delivery so Deliveries UI can
navigate to the run row for debugging.
- Slim list projection: ListWebhookDeliveriesByAutopilot no longer pulls
raw_body / selected_headers / response_body — a 100-row page × 256 KiB
would otherwise round-trip ~25 MiB from Postgres per Deliveries reload.
Detail endpoint continues to return the full row.
- Fix backend CI: TestGetDelivery_ReturnsFullPayload now decodes the
response and asserts on the parsed raw_body instead of substring-
matching against an escaped JSON string; raise the test-suite default
webhook rate limits in TestMain so the shared 192.0.2.1 IP bucket
doesn't fill across the suite and leak 429s into unrelated tests.
- Add regression coverage for the dedupe-after-failure path.
cd server && go test ./... is green locally.
Co-authored-by: multica-agent <github@multica.ai>
---------
Co-authored-by: multica-agent <github@multica.ai>
|