Skip to content

Commit 21043bd

Browse files
bakeyclaude
andcommitted
docs(feishu): fold in the first-user field report (PR #186 discussion)
Every doc-shaped item from the end-to-end field test, plus the upstream filing for the one deployment blocker: - Zero-trust VPN egress entry: the ~36ms oauth_token_exchange_failed with no resolved address is the guard refusing 198.18.0.0/15 before the request leaves the process; filed upstream as oomol-lab/ open-connector#275 (credit to the reporter) and linked. - The gateway-version floor is now stated as a prerequisite with a copy-paste self-check command (old gateways expose only docs/bitable feishu actions and fail registration with a typo-looking error). - The scope-union caveat now ships the concrete 10-scope narrowed list and the pasteable Feishu-console bulk-import JSON, linked to #267. - The binding example covers all six tables with their required resources annotated and the query-first ordering note (people copy the example before reading the table). - tasks `type: my_tasks` rephrased as the only value Feishu accepts (1470400, field-verified), in the doc and the yaml comment. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1 parent c6ece9a commit 21043bd

2 files changed

Lines changed: 79 additions & 8 deletions

File tree

crates/skardi/src/sources/providers/open_connector/packs/feishu.yaml

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -146,8 +146,9 @@ tables:
146146
page_size_input: pageSize
147147
page_size: 100
148148
has_more_path: "$.hasMore"
149-
# `my_tasks` is the executor's default; pinned explicitly so the
150-
# table's population is declared, not inherited. Omitting the
149+
# `my_tasks` is the ONLY value Feishu accepts (1470400 for
150+
# assigned/created/followed — field-verified), pinned explicitly so
151+
# the table's population is declared, not inherited. Omitting the
151152
# action's `completed` input returns tasks in BOTH states — the
152153
# state=all default. The input is deliberately unmapped as a filter:
153154
# real rows carry NO `completed` boolean (live-verified 2026-08-04 —

docs/open-connector-feishu.md

Lines changed: 76 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -41,14 +41,30 @@ spec:
4141
open_connector:
4242
runtime_token_env: OPEN_CONNECTOR_TOKEN
4343
bindings:
44+
# The three tables that need NO resource — a first ctx can only
45+
# cover these; the other three each require an id you get by
46+
# querying chats / wiki_spaces first, then coming back.
4447
- name: team # schema name in SQL
4548
source_pack: feishu
4649
tables: [chats, tasks, wiki_spaces]
50+
# Each of the remaining tables needs one resource, so each
51+
# needs its own binding — adding them to `team` fails startup
52+
# with `missing required resource input`.
4753
- name: standup # per-chat binding for chat history
4854
source_pack: feishu
4955
resource:
5056
containerId: oc_a1b2c3d4e5f6 # chat_id from the chats table
5157
tables: [messages]
58+
- name: standup_members
59+
source_pack: feishu
60+
resource:
61+
chatId: oc_a1b2c3d4e5f6 # chat_id from the chats table
62+
tables: [chat_members]
63+
- name: handbook
64+
source_pack: feishu
65+
resource:
66+
spaceId: "7034502641455497244" # space_id from wiki_spaces
67+
tables: [wiki_nodes]
5268
```
5369
5470
```sql
@@ -90,8 +106,10 @@ Design notes:
90106
unmapped — it is exclusive, and flooring an upper bound would drop
91107
rows. `body.content` maps to the `content` column as the provider's
92108
own JSON-encoded payload text (its inner schema varies by `msg_type`).
93-
- **`tasks` pins `type: my_tasks`** and always omits the `completed`
94-
input — Feishu's spelling of a state=all listing. Nothing pushes it:
109+
- **`tasks` sends `type: my_tasks`** — the only value Feishu accepts
110+
(`1470400: Invalid Param 'type'. Only 'my_tasks' is supported.` for
111+
`assigned`/`created`/`followed`), not a tunable choice — and always
112+
omits the `completed` input, Feishu's spelling of a state=all listing. Nothing pushes it:
95113
real rows carry no `completed` boolean (completion on the wire is
96114
`status: todo|done` plus `completed_at`), so filter on `status`
97115
locally.
@@ -109,6 +127,22 @@ The gateway's feishu provider uses the OAuth authorization-code flow
109127
creates. Rows are the authorizing user's view — a chat the user left or
110128
a wiki space they cannot read is simply absent, not an error.
111129

130+
**Gateway version is a floor, not a fact**: the six actions this pack
131+
needs were added to Open Connector after older mid-2025 builds (which
132+
expose only docs/bitable feishu actions); a too-old gateway fails
133+
registration with `action 'feishu.list_chats' was not found`, which
134+
reads like a typo but means "upgrade the gateway". Self-check before
135+
going further:
136+
137+
```bash
138+
curl -s -H "Authorization: Bearer $OPEN_CONNECTOR_TOKEN" \
139+
"$GATEWAY/v1/actions?service=feishu&limit=500" \
140+
| python3 -c "import json,sys; d=json.load(sys.stdin); \
141+
ids=[i['id'] for i in d['data']['items']]; print(len(ids)); \
142+
print([n for n in ['feishu.list_chats','feishu.list_messages','feishu.list_chat_members','feishu.list_tasks','feishu.list_wiki_spaces','feishu.list_wiki_nodes'] if n not in ids])"
143+
# expect: a few hundred actions, then an empty list []
144+
```
145+
112146
Operational findings from the live verification, all three of which the
113147
Feishu console gates independently of each other:
114148

@@ -123,10 +157,46 @@ Feishu console gates independently of each other:
123157
(99992402 above it) despite the gateway schema declaring 100 — the
124158
pack requests 50.
125159
- Upstream gateway caveat: its authorization URL requests the union of
126-
ALL feishu actions' scopes with no narrowing surface, which Feishu
127-
rejects (20027) unless the app enables every one — deployments should
128-
expect to narrow the provider's scope list until upstream grows a
129-
config-level override.
160+
ALL feishu actions' scopes with no narrowing surface — measured at
161+
164 scopes on the live authorize URL, including destructive write
162+
scopes, to read six tables — which Feishu rejects (20027) unless the
163+
app enables every one. Until upstream grows a config-level override
164+
([#267](https://github.com/oomol-lab/open-connector/issues/267)),
165+
narrow `feishuOAuthScopes` in the gateway's
166+
`src/providers/feishu/definition.ts` to what these tables need:
167+
168+
```ts
169+
const feishuOAuthScopes = [
170+
"offline_access",
171+
"im:chat:read", // chats
172+
"im:chat.members:read", // chat_members
173+
"im:message:readonly", // messages (next three also messages)
174+
"im:message.group_msg:get_as_user",
175+
"im:message.p2p_msg:get_as_user",
176+
"im:message.reactions:read",
177+
"task:task:read", // tasks
178+
"wiki:space:retrieve", // wiki_spaces
179+
"wiki:node:retrieve", // wiki_nodes
180+
];
181+
```
182+
183+
The Feishu console bulk-imports scopes, so enabling them is one paste:
184+
185+
```json
186+
{"scopes":{"tenant":[],"user":["offline_access","im:chat:read","im:chat.members:read",
187+
"im:message:readonly","im:message.group_msg:get_as_user","im:message.p2p_msg:get_as_user",
188+
"im:message.reactions:read","task:task:read","wiki:space:retrieve","wiki:node:retrieve"]}}
189+
```
190+
191+
- Zero-trust corporate VPNs (aTrust / EasyConnect class) that map
192+
external domains into `198.18.0.0/15` trip the gateway's egress guard
193+
BEFORE any request leaves the process: the OAuth exchange fails in
194+
tens of milliseconds with only `oauth_token_exchange_failed` and no
195+
resolved address — the speed is the tell. The guard checks reserved
196+
ranges unconditionally (`OOMOL_CONNECT_ALLOW_PRIVATE_NETWORK` cannot
197+
open them, and it does not reach provider egress at all); tracked
198+
upstream as
199+
[#275](https://github.com/oomol-lab/open-connector/issues/275).
130200

131201
Filed upstream (oomol-lab/open-connector) from this verification pass:
132202
[#267](https://github.com/oomol-lab/open-connector/issues/267)

0 commit comments

Comments
 (0)