Commit 9f34676
authored
feat(granola): complete API coverage, note triggers, and connector validation (#6880)
* feat(granola): complete API coverage, note triggers, and validation fixes
Granola's public API exposes nine endpoints; Sim implemented three. Adds the
remaining six and wires the new programmatic webhook-endpoint lifecycle into a
managed trigger.
Tools (6 new, 9 total):
- get_transcript, list_audit_events
- create/list/update/delete_webhook_endpoint
Triggers: note.generated, note.edited, note.access_granted, plus an all-events
trigger. The provider handler registers the Granola endpoint on deploy and
deletes it on undeploy, scoped to the trigger's own event names, and verifies
every delivery with the Standard Webhooks HMAC-SHA256 signature Granola returns
on creation. event_id is the idempotency key, which Granola reuses across
retries.
Validation fixes to the shipped tools:
- get_note dropped speaker.attribution ("me"/"them"); now surfaced
- a 413 on get_note now explains that the transcript is too large inline and
points at get_transcript, instead of surfacing a bare status code
- note IDs are URL-encoded rather than interpolated raw
- base URL, auth headers, and status-aware error handling are shared runtime
helpers; params/outputs stay literal per file so the docs generator still
reads them
Tests cover signature verification (including replay and body-tamper
rejection), event matching, subscription create/delete, and the block/tool
contract — plus a guard that ids shared between the tool and trigger surfaces
seed the same default, since block state is keyed by id and last-wins.
The knowledge-base connector was validated against the spec and needed no
changes.
* fix(granola): correct array output schemas, listing-truncation signal, and docs
Findings from validation passes over the tools, trigger, and connector.
Tools — array outputs were declared as `type: 'json'` with `properties`, which
describes an object, not an array. Agents and the output picker therefore saw
`notes.title` instead of `notes[i].title`. All 15 array outputs (including the
pre-existing three tools) now use `type: 'array'` with `items`, matching the
2000+ other tool files. The audit event `data` field stays `json`; it is
genuinely free-form per the spec.
Connector — `hasMore` was ANDed with the cursor, so a `hasMore: true` response
with no cursor was reported as a complete listing. The sync engine treats
exactly that shape as truncated and sets `listingTruncated` to block deletion
reconciliation; masking it meant a partial first page could be taken for the
whole corpus and reconciliation would hard-delete every note past it. Granola
would have to violate its own contract to emit that shape, but the engine
already handles it and the connector was hiding the signal. Also aligns
mimeType with the `.txt`/text-plain bytes the engine actually writes (it was
the only connector of 101 claiming text/markdown).
Trigger — the setup instructions named a Granola settings path that does not
exist; the help center says Settings > Connectors > API keys in the desktop app.
Both list parsers now split commas inside array entries, so an array-wrapped
free-text value cannot be sent as one malformed identifier.
Block — `id`, `events`, and `hasMore` are produced by several operations but
their descriptions named only one, unlike `folders` which already documented
both meanings.
Adds connector tests pinning all four listingCapped quadrants and the
truncation signal, and tool tests for the list parser and the PATCH body's
per-field "omit means unchanged" semantics.
* fix(granola): clean up webhook endpoints created by a failed registration
Raised independently by both reviewers. The registration service only rolls
external state back when createSubscription *returns* — its rollback is guarded
on `preparedProviderConfig`, so a handler that throws is assumed to have left
nothing behind. Granola's handler broke that contract: when Granola accepted the
POST but the success body was missing `id` or `signing_secret` (including a body
that failed to parse and became `{}`), it threw with the endpoint already live.
Nothing then recorded an external id, so undeploy could not remove it, and
Granola kept delivering to a callback whose signature could never be verified —
duplicating on every deploy retry.
The handler now removes what it created before rethrowing, matching the pattern
grain's multi-hook create already uses. It deletes by id when Granola returned
one, and otherwise recovers the endpoint by matching the callback URL, which
also covers a connection that fails after the request reached Granola.
Endpoints whose URL was redacted to its origin are never matched — that
comparison could delete another workflow's endpoint on the same host. Cleanup is
best effort and never masks the original failure. A non-2xx is left alone, since
no endpoint was created.
Also folds the delete call shared with deleteSubscription into one helper.
* fix(granola): never recover an orphaned endpoint by callback URL
The previous commit's URL-based recovery was unsafe. A redeploy reuses the live
registration's `path`, so the candidate and the currently serving endpoint share
a callback URL — listing by that URL and deleting every match would remove the
live deployment's endpoint and silently stop a working trigger, which is worse
than the leak it was trying to prevent.
Cleanup is now keyed solely on the id Granola returned. When the success body
carries no id there is no way to tell the candidate's endpoint from the live
one, so it is left in place: a leaked endpoint produces unverifiable deliveries
that Granola disables on its own, whereas deleting the wrong one takes down live
traffic with no signal.
The 2xx-missing-signing-secret case this originally fixed still cleans up, since
that response does carry an id.
Adds a test asserting no lookup or delete is attempted when the response has no
id, so URL matching cannot be reintroduced unnoticed.1 parent 02ae2b4 commit 9f34676
33 files changed
Lines changed: 3265 additions & 99 deletions
File tree
- apps
- docs/content/docs/en/integrations
- sim
- blocks/blocks
- connectors/granola
- lib/webhooks/providers
- tools
- generated
- granola
- triggers
- granola
- packages/deployment-config/src
Large diffs are not rendered by default.
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
| 65 | + | |
| 66 | + | |
| 67 | + | |
| 68 | + | |
| 69 | + | |
| 70 | + | |
| 71 | + | |
| 72 | + | |
| 73 | + | |
| 74 | + | |
| 75 | + | |
| 76 | + | |
| 77 | + | |
| 78 | + | |
| 79 | + | |
| 80 | + | |
| 81 | + | |
| 82 | + | |
| 83 | + | |
| 84 | + | |
| 85 | + | |
| 86 | + | |
| 87 | + | |
| 88 | + | |
| 89 | + | |
| 90 | + | |
| 91 | + | |
| 92 | + | |
| 93 | + | |
| 94 | + | |
| 95 | + | |
| 96 | + | |
| 97 | + | |
| 98 | + | |
| 99 | + | |
| 100 | + | |
| 101 | + | |
| 102 | + | |
| 103 | + | |
| 104 | + | |
| 105 | + | |
| 106 | + | |
| 107 | + | |
| 108 | + | |
| 109 | + | |
| 110 | + | |
| 111 | + | |
| 112 | + | |
| 113 | + | |
| 114 | + | |
| 115 | + | |
| 116 | + | |
| 117 | + | |
| 118 | + | |
| 119 | + | |
| 120 | + | |
| 121 | + | |
| 122 | + | |
| 123 | + | |
| 124 | + | |
| 125 | + | |
| 126 | + | |
| 127 | + | |
| 128 | + | |
| 129 | + | |
| 130 | + | |
| 131 | + | |
| 132 | + | |
| 133 | + | |
| 134 | + | |
| 135 | + | |
| 136 | + | |
| 137 | + | |
| 138 | + | |
| 139 | + | |
| 140 | + | |
| 141 | + | |
| 142 | + | |
| 143 | + | |
| 144 | + | |
| 145 | + | |
| 146 | + | |
| 147 | + | |
| 148 | + | |
| 149 | + | |
| 150 | + | |
| 151 | + | |
| 152 | + | |
| 153 | + | |
| 154 | + | |
| 155 | + | |
| 156 | + | |
| 157 | + | |
| 158 | + | |
| 159 | + | |
| 160 | + | |
| 161 | + | |
0 commit comments