Skip to content

Commit 30a63b2

Browse files
bakeyclaude
andcommitted
feat(skills): add the source-pack skill for AI-driven pack development
A repo skill that lets an AI session take a provider request (Notion, Jira, …) to a review-ready PR by encoding what the GitHub and Slack packs taught us: - SKILL.md: the five-phase workflow — live contract reconciliation FIRST (the wire contract is Open Connector's, not the provider's raw API), table design under the admission gate, implementation, self-review, then PR submission in the house style. - references/contract-reconciliation.md: running the local gateway, the verified /v1 surface (uniform envelope, no /execute suffix, camelCase strict schemas, alias header, no read/write classification), probing actions, reading executor source as the row-shape authority (passthrough vs normalized), the 400-vs-credential-wall input validation trick, and contract capture for fingerprint pinning. - references/implementation.md: pagination/filter/field design rules (total_pages_path, Inexact string-enums, boundary-row protections, ValueFormat), the six fixture categories, the fingerprint pinning recipe (capture -> pin -> sync test -> contract-serving mocks -> drift e2e), the per-declaration e2e test floor, and the three doc targets. - references/review-checklist.md: the distilled review standards from every round both packs went through — silent-truncation checks, contract honesty, structural assertions and row identity, both sides of every gate, information discipline, docs/spec sync, and the final self-review pass required before any PR. .gitignore narrows .claude/ to .claude/* with a !.claude/skills/ exception so shared skills are tracked while local settings stay ignored. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1 parent 549f928 commit 30a63b2

5 files changed

Lines changed: 569 additions & 1 deletion

File tree

Lines changed: 160 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,160 @@
1+
---
2+
name: source-pack
3+
description: >-
4+
Develop a new Open Connector source pack for Skardi end-to-end: research the
5+
provider's live gateway contract, implement the pack (tables, fixtures,
6+
fingerprints, tests, docs), self-review against this repo's accumulated
7+
review standards, and submit a PR. Use this skill whenever the user asks to
8+
add, support, integrate, or onboard a data source or SaaS provider (Notion,
9+
Jira, Gmail, HubSpot, Discord, Feishu, …) as SQL tables, mentions "source
10+
pack", a "milestone 5.x" task, or wants any provider reachable through Open
11+
Connector — even if they never say the words "source pack".
12+
---
13+
14+
# Developing an Open Connector source pack
15+
16+
You are implementing one milestone of the Open Connector integration: a
17+
**source pack** — static, Skardi-reviewed relational contracts over a
18+
provider's read actions, exposed as SQL tables. The GitHub pack (raw
19+
passthrough rows) and Slack pack (normalized rows) are the two reference
20+
implementations; read them before writing anything.
21+
22+
The single most important lesson baked into this repo, learned the hard
23+
way: **the wire contract is Open Connector's, not the provider's raw
24+
API.** The GitHub pack originally shipped with `per_page`, `issue_number`,
25+
a nonexistent action ID, the wrong execute endpoint, and the wrong
26+
response envelope — all plausible from GitHub's own docs, all wrong
27+
against the real gateway, and all invisible to CI because the mocks
28+
encoded the same wrong assumptions. Every phase below exists to prevent
29+
that class of failure.
30+
31+
## Required reading (before phase 1)
32+
33+
1. `docs/superpowers/specs/2026-07-11-open-connector-integration-design.md`
34+
— especially the **source-pack admission gate**: complete terminating
35+
pagination, deterministic schema, read-only allowlist, documented
36+
authz/rate limits, bounded safety defaults, null/empty/nested/
37+
schema-mismatch fixtures, docs. The gate is the definition of done.
38+
2. `docs/superpowers/specs/2026-07-11-open-connector-integration-tasks.md`
39+
— the milestone map; entries 5.1 (GitHub) and 5.2 (Slack) are the
40+
template for what your milestone entry must eventually say.
41+
3. `crates/skardi/src/sources/providers/open_connector/packs/github.rs`
42+
and `packs/slack.rs` — read the module docs top to bottom; every design
43+
decision a pack makes is recorded there with its rationale, and yours
44+
must be too.
45+
4. `docs/open-connector.md`, `docs/open-connector-github.md`,
46+
`docs/open-connector-slack.md` — the documentation shape you will add
47+
to.
48+
49+
## Phase 1 — Reconcile the contract against a live gateway
50+
51+
Do this FIRST, before designing tables. Read
52+
[references/contract-reconciliation.md](references/contract-reconciliation.md)
53+
for the concrete steps: starting the local gateway, probing the real API,
54+
reading the provider's executor source in the Open Connector repo, and
55+
validating generated inputs without provider credentials.
56+
57+
Non-negotiable outputs of this phase:
58+
59+
- The exact action IDs that exist (never assume a name; `github.
60+
list_repositories` did not exist).
61+
- Every input key, verbatim from `inputSchema` (camelCase, and
62+
`additionalProperties: false` means a wrong key is a hard 400).
63+
- The row shape: does the executor pass provider rows through raw
64+
(GitHub-style) or rebuild them normalized (Slack-style)? Only the
65+
executor source answers this — declared output schemas can be lax
66+
(`additionalProperties: true`) while the executor passes through fields
67+
the schema never mentions.
68+
- How pagination is emitted (top-level `nextCursor`? sibling
69+
`total_count`? authoritative `paging.pages`?) and how in-band provider
70+
errors are handled (most OC executors consume them and return a failure
71+
envelope; `error_path` is only for gateways that forward them).
72+
- Captured output schemas for fingerprint pinning (phase 3).
73+
74+
## Phase 2 — Design the tables
75+
76+
Read [references/implementation.md](references/implementation.md) §Design
77+
before deciding anything. Summary of the rules that have survived review:
78+
79+
- Choose read-only list actions with **complete terminating pagination**
80+
only. An action whose pagination cannot be completed (Slack message
81+
history, at the time of 5.2) is deferred and documented as absent, not
82+
shipped incomplete.
83+
- Every design decision (a pinned input, an unmapped filter, an excluded
84+
table, a nullability choice) is written into the module doc with its
85+
why. Reviewers here read module docs as claims to be verified.
86+
- If the user named specific resources ("I want Notion pages and
87+
databases"), scope to those; otherwise propose the natural first wave
88+
(list-shaped, high-value, gate-passing) the way 5.1 chose 8 tables and
89+
5.2 chose 3.
90+
91+
## Phase 3 — Implement
92+
93+
Follow [references/implementation.md](references/implementation.md)
94+
§Implementation for the full checklist: pack file, registry entry, the
95+
six fixture categories (including schema-mismatch), fingerprint pinning
96+
(capture → pin → sync test → contract-serving mocks → drift-refusal
97+
e2e), per-declaration end-to-end tests through `MockGateway`, and the
98+
three documentation targets (pack doc, spec entry with counted
99+
verification, `docs/open-connector.md` status).
100+
101+
Engine extensions are allowed when the pack genuinely needs them (5.1
102+
added `Fidelity` and list plucking; 5.2 added `total_pages_path`,
103+
`ValueFormat`, `TimestampSecondsUtc`) — keep them backward-compatible
104+
(optional fields, `None` defaults) and test them at both the engine and
105+
the pack level.
106+
107+
## Phase 4 — Self-review before any PR
108+
109+
This phase is why the submitted code is good. Work through
110+
[references/review-checklist.md](references/review-checklist.md) — it is
111+
the distillation of every review round the existing packs went through.
112+
Treat it the way you would treat a human reviewer's findings: verify each
113+
item against the actual code, fix what fails, and be honest about
114+
severity. Then:
115+
116+
1. `cargo fmt` and `cargo clippy` clean.
117+
2. `cargo test -p skardi --lib` — the FULL library suite, not just the
118+
pack filter (engine changes ripple).
119+
3. Count tests with the documented methodology
120+
(`cargo test -p skardi --lib sources::providers::open_connector` and
121+
the pack-scoped filter) and make every count in docs/spec match.
122+
4. Run the repo's code review on your own diff (the `review` /
123+
`/code-review` skill if available) and fix or consciously rebut every
124+
finding. A finding you disagree with gets a verified technical
125+
rebuttal, not silence — reviews here have been wrong before (cited
126+
line numbers stale, counts miscounted), and verifying against the
127+
code before acting is part of the standard.
128+
129+
## Phase 5 — Submit the PR
130+
131+
- Branch `feature/open-connector-<provider>-pack` off latest `main`
132+
(fetch first). If the work must stack on an unmerged PR's branch,
133+
stack — and recommend Draft until the base merges.
134+
- Commits: conventional style (`feat(sources): …`), detailed bodies that
135+
explain *why* (look at `git log` for the house voice), ending with the
136+
repo's standard co-author trailer.
137+
- Tick the milestone entry in the tasks spec with a verification blurb
138+
matching 5.1/5.2's density (decisions, live-reconciliation status,
139+
counted tests with the counting command).
140+
- PR body modeled on the merged pack PRs (#168-level per-module detail):
141+
what shipped per module, design decisions with rationale, engine
142+
extensions, verification section with test counts, live-reconciliation
143+
status, and any deliberate deferrals (e.g. fingerprint pins pending,
144+
tables gated on upstream support).
145+
- `gh pr create` with that body; use Draft when stacked or when the user
146+
asked for in-progress visibility.
147+
148+
## Working style
149+
150+
- Evaluate before you fix: when review feedback arrives (from the user or
151+
your own phase-4 pass), first verify the claim against the code —
152+
some findings are already fixed, stale, or wrong, and saying so with
153+
evidence is as valuable as a fix.
154+
- No credentials in Skardi, ever. Provider credentials live in the
155+
gateway; tests use `EnvVarGuard` with per-test-unique variable names;
156+
tokens never appear in YAML, logs, `Debug`, or errors.
157+
- Errors carry identity (action, table, page, row, column) and JSON
158+
*kinds*, never values; snippets stay bounded.
159+
- When you and the user have live-gateway access, prefer one real probe
160+
over an hour of speculation.
Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
# Phase 1 — Live contract reconciliation
2+
3+
The provider's public API docs describe the provider. Skardi talks to
4+
**Open Connector**, whose action contracts rename keys (camelCase),
5+
reject unknown inputs (`additionalProperties: false`), sometimes rebuild
6+
rows entirely, and consume provider error envelopes. Everything below is
7+
about observing the real gateway instead of assuming.
8+
9+
## 1. Start a local gateway
10+
11+
Look for a local checkout of
12+
[oomol-lab/open-connector](https://github.com/oomol-lab/open-connector)
13+
(historically `~/Workspace/open-connector`); clone it if absent. Then:
14+
15+
```bash
16+
cd <open-connector-checkout>
17+
npm install # once
18+
OOMOL_CONNECT_DATA_DIR=/tmp/oc-data \
19+
OOMOL_CONNECT_RUNTIME_TOKEN=skardi-live-test-token \
20+
PORT=3000 npm run start
21+
```
22+
23+
Health check (the runtime token is required once configured):
24+
25+
```bash
26+
curl -s -H 'Authorization: Bearer skardi-live-test-token' \
27+
http://localhost:3000/v1/health
28+
```
29+
30+
Run the server in the background and remember to stop it when done.
31+
32+
## 2. Know the real HTTP surface (verified v1.3.1)
33+
34+
These facts cost a full client rewrite to learn; do not regress them:
35+
36+
- Every `/v1` response uses the uniform envelope
37+
`{"success": bool, "message": str, "data": …, "meta": {…}}` (+
38+
`errorCode` on failures; `meta.executionId` once execution started).
39+
- Execute is `POST /v1/actions/:actionId` — there is **no** `/execute`
40+
suffix. Success payload is under `data`.
41+
- Discovery is `GET /v1/actions/:actionId``data.inputSchema`,
42+
`data.outputSchema`, `data.execution.locallyExecutable`,
43+
`data.execution.noAuthRunnable` (all camelCase, `execution` nested).
44+
- The connection-alias header is `x-oo-connector-alias` (or
45+
`x-oomol-connector-alias`; `?alias=` also works).
46+
- There is **no read/write classification** in action metadata — the
47+
raw-scan gate (`open_connector_scan`) refuses by default-deny against
48+
today's gateway. Pack tables are unaffected (read-only by Skardi's
49+
review).
50+
- Input validation runs BEFORE credential lookup, which enables the
51+
no-credential trick below.
52+
53+
Skardi's client (`open_connector/client.rs`) and `testutil.rs` mocks
54+
already speak this protocol; if the live gateway ever disagrees with
55+
them, that is a finding to fix in the client, not to absorb in the pack.
56+
57+
## 3. Probe the provider's actions
58+
59+
```bash
60+
TOK='Authorization: Bearer skardi-live-test-token'
61+
# What exists — never assume an action ID:
62+
curl -s -H "$TOK" "http://localhost:3000/v1/actions?service=<provider>"
63+
# Per-action contract:
64+
curl -s -H "$TOK" "http://localhost:3000/v1/actions/<provider>.<action>"
65+
# Human-readable guide (input examples, scopes, connection identity):
66+
curl -s http://localhost:3000/api/actions/<provider>.<action>/agent.md
67+
```
68+
69+
For each candidate action record: exact ID, `inputSchema` properties +
70+
`required` + `additionalProperties`, `outputSchema` top-level keys (row
71+
array key = your row path), pagination inputs (`perPage`/`page`?
72+
`cursor`/`limit`?), and `execution.requiredAuthTypes`.
73+
74+
## 4. Read the executor source — the row-shape authority
75+
76+
`<open-connector-checkout>/src/providers/<service>/` contains the
77+
executors. This is the only reliable answer to:
78+
79+
- **Passthrough vs normalized rows.** GitHub's executors return the
80+
provider's raw objects (so fields the declared schema omits — e.g.
81+
`updated_at` — still arrive, because `additionalProperties: true`).
82+
Slack's executors REBUILD rows (camelCase keys, flattened profiles,
83+
renamed row arrays, cursor moved to top-level `nextCursor`). Mapping
84+
Slack's raw field names would have produced all-NULL columns that no
85+
provider doc would explain.
86+
- **In-band provider errors.** If the executor raises on the provider's
87+
in-band error (Slack's `assertSlackPayload` throws on `ok: false`),
88+
the gateway returns a failure envelope and the pack declares
89+
`error_path: None`. Only a gateway that FORWARDS in-band errors needs
90+
`error_path` (the engine checks it before row extraction; the mock
91+
pack models it).
92+
- **Which inputs actually reach the provider** and under what names —
93+
the executor's `compactObject({...})` mapping is the ground truth for
94+
filter pushdown fidelity.
95+
- **Emitted output construction** — where the row array, totals, and
96+
cursors really live.
97+
98+
## 5. Validate generated inputs without provider credentials
99+
100+
The gateway validates input against the action schema BEFORE touching
101+
credentials, so a wrong key is distinguishable from a missing login:
102+
103+
```bash
104+
# Wrong key → HTTP 400, errorCode invalid_input, names the bad property:
105+
curl -s -X POST -H "$TOK" -H 'content-type: application/json' \
106+
-d '{"input":{"owner":"a","repo":"b","per_page":5}}' \
107+
http://localhost:3000/v1/actions/github.list_repository_issues
108+
# Correct keys, no connection → HTTP 403, "Configure … credentials first.":
109+
curl -s -X POST -H "$TOK" -H 'content-type: application/json' \
110+
-d '{"input":{"owner":"a","repo":"b","perPage":5}}' \
111+
http://localhost:3000/v1/actions/github.list_repository_issues
112+
```
113+
114+
Reaching the credential wall proves the pack's generated inputs
115+
(pagination params, fixed inputs, filter keys, resource keys) pass the
116+
strict schema. Do this for every table's input set. Row-data validation
117+
additionally needs a configured connection
118+
(`PUT /api/connections/<service>` with `{authType, values}` — ask the
119+
user; never handle their credentials yourself) and provider egress
120+
(note: fake-IP DNS environments block egress at the gateway's SSRF guard;
121+
contract-level validation above needs neither).
122+
123+
## 6. Capture the contracts for fingerprint pinning
124+
125+
For each chosen action, save `data.outputSchema` verbatim as a fixture:
126+
127+
```
128+
crates/skardi/src/sources/providers/open_connector/packs/fixtures/<provider>/contracts/<action_name>.json
129+
```
130+
131+
Phase 3 pins each table's `expected_fingerprint` to the BLAKE3 hash of
132+
the canonicalized schema (computed by
133+
`action_registry::fingerprint_schema` — never re-derive the
134+
canonicalization elsewhere) and locks pin ↔ fixture with a sync test.
135+
Record the gateway version you captured against.

0 commit comments

Comments
 (0)