Skip to content

Commit 3967597

Browse files
SK-3113-parity-commands
1 parent 425da52 commit 3967597

3 files changed

Lines changed: 402 additions & 0 deletions

File tree

‎.claude/commands/api-vs-sdk.md‎

Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
---
2+
name: api-vs-sdk-parity
3+
description: Generate or update an API-vs-SDK parity doc for a Skyflow SDK — the wire-level API contract (protobuf or OpenAPI/JSON Schema) vs the SDK's public request/response/option models. Defaults to this repo (Node/TypeScript); pass a git URL or local path in $ARGUMENTS to target a different Skyflow SDK (Java, Python, Go, ...) instead. Argument: $ARGUMENTS
4+
constraints:
5+
- "Never edit, create, or delete any file under src/_generated_/, or anywhere inside a cloned/checked-out SDK repo — those are read-only sources, not output targets."
6+
- "Do not modify any source code in this pass — this skill only produces the doc."
7+
- "Never invent, guess, or recall a contract's field shape, or a target SDK's model shape, from memory — read the given path/URL or use exactly what was pasted."
8+
---
9+
10+
You are producing a field-by-field parity doc between Skyflow's wire-level API contract and a Skyflow SDK's public models. Neither this repo nor a freshly cloned SDK repo is assumed to have a `.proto` or OpenAPI/Swagger file checked in — the contract must come from `$ARGUMENTS` (a path, if the caller has one locally, or pasted content) on essentially every run. Do not fall back to a contract file from a previous run sitting anywhere in the repo, and do not fabricate field shapes.
11+
12+
## Getting the contract
13+
14+
`$ARGUMENTS` may contain either:
15+
- a path to a contract file (protobuf `.proto`, or OpenAPI/Swagger `.yaml`/`.yml`/`.json`) — relative to the repo root, or absolute, or
16+
- the raw contract content itself, pasted in a fenced code block (` ```proto ... ``` `, ` ```yaml ... ``` `, or ` ```json ... ``` `).
17+
18+
If `$ARGUMENTS` has no contract path or pasted content, or names a path that doesn't exist, **stop and ask the user to share the contract** before doing anything else.
19+
20+
Once you have a real path, read it with the Read tool. If pasted inline, use that content directly. Note which contract style it is (protobuf `message`/`service` definitions, OpenAPI 3 `components.schemas`, or Swagger 2 `definitions`) — the vocabulary differs and step 1 below needs to know which to grep for.
21+
22+
## Getting the SDK to compare
23+
24+
`$ARGUMENTS` may also specify which SDK to compare the contract against. Distinguish this from a contract path/model-scope path by what it points at: a contract file has a `.proto`/`.yaml`/`.yml`/`.json` extension; a *model scope* path (see below) lives inside the current repo's own model tree; anything else that looks like a git remote (`https://github.com/...`, `git@...`, optionally with a `@<ref>` or "branch: <name>"/"tag: <name>" suffix) or a local filesystem path to a *different* repository root is the **target SDK location**.
25+
26+
- **No SDK location given** → default to this repo, checked out at the current working directory (Node/TypeScript SDK).
27+
- **A local path to a different repo** → use it directly, read-only. Don't assume it's already up to date — note whatever `git log -1`/`git status` shows in the doc's header so staleness is visible, but don't `git pull` a checkout you don't own.
28+
- **A git URL** → clone it yourself rather than asking the user to. Use a stable cache location outside both this repo and the SDK's own tree, e.g. `/tmp/skyflow-sdk-parity-clones/<repo-name>`, so repeat runs against the same SDK don't re-clone from scratch:
29+
- If that directory doesn't exist yet: `git clone --depth 1 [--branch <ref>] <url> <dir>`.
30+
- If it already exists as a git checkout: `git fetch --depth 1 origin <ref-or-default-branch> && git checkout FETCH_HEAD` (or a plain `git pull` on the default branch) to refresh it before reading — never diff against a stale clone from a previous run.
31+
- Treat this clone as read-only. Never write into it, and never run install/build steps in it — you only need to read source files.
32+
33+
Once you know the SDK's root, **identify its language and its model layer from what's actually there** — don't assume it mirrors this repo's `src/vault/model/request|response|options` layout, since that's a Node/TypeScript-specific convention:
34+
- Look for the language first (file extensions present at the repo root / `src`-equivalent: `.ts`, `.java`, `.py`, `.go`, ...) — this decides what "an exported type" means (TypeScript `export interface`/`type`/`class`; Java a `public class`/`record` in a `model`/`dto`/`types`-ish package; Python a `class` in a `models`/`types` module, often a dataclass or Pydantic model; Go an exported `struct` in a `models`/`types` package).
35+
- Look for directory names that suggest request/response/options (or the SDK's local terms for the same three concepts — e.g. Java/Go SDKs often fold "options" into request builders) — check any top-level `README`, and the package/module structure, rather than guessing from this repo's naming.
36+
- If you can't confidently find a model layer after a real search, say so and ask the user where it lives in that SDK, instead of guessing.
37+
38+
Alternatively, `$ARGUMENTS` can specify the model location directly:
39+
- a directory path containing the target language's model source files, or
40+
- one or more labeled fenced code blocks containing the actual type definitions (any language), for comparing against a model source not checked out anywhere.
41+
42+
If `$ARGUMENTS` gives neither an SDK location nor a model path, default to this repo's own model source: `src/vault/model/`, which has three subdirectories —
43+
- `src/vault/model/request/` — request bodies
44+
- `src/vault/model/response/` — response bodies
45+
- `src/vault/model/options/` — caller-facing option objects
46+
47+
Read all three (of whatever the target SDK's equivalent structure turns out to be) unless `$ARGUMENTS` scopes the run to one (e.g. "just responses", "just options for insert"). State whichever default or scope was used explicitly in the doc's header — including which SDK repo/ref was compared — so a future run against a narrower scope or a different SDK isn't silently assumed to mean the same thing.
48+
49+
To find out what the target SDK actually implements today (rather than assuming it matches a previous run or another language's SDK), find its equivalent of a controller/service layer (in this repo: `src/vault/controller/`, currently `vault`, `audit`, `binlookup`, `connections`, and `detect`) and cross-reference its operations against the model layer (e.g. `insert`, `update`, `get`, `delete`, `detokenize`, `tokenize`, `query`, `deidentify-text`, `reidentify-text`, `file-upload`, `deidentify-file`, `invoke`). Re-derive this list from the source on every run; the exact set of implemented operations changes over time and per SDK.
50+
51+
---
52+
53+
## Type-equivalence reference
54+
55+
Use this (extend it if a contract construct isn't listed, or the target SDK is in a language not listed) when deciding whether a field "matches" across the contract and the target language:
56+
57+
| Protobuf | OpenAPI / JSON Schema | TypeScript | Java | Python | Go |
58+
|---|---|---|---|---|---|
59+
| `string` / `StringValue` | `type: string` | `string` | `String` | `str` | `string` |
60+
| `int32` / `Int32Value`, `int64` | `type: integer` | `number` | `Integer`/`int`, `Long`/`long` | `int` | `int32`/`int64` |
61+
| `bool` / `BoolValue` | `type: boolean` | `boolean` | `Boolean`/`boolean` | `bool` | `bool` |
62+
| `double` / `DoubleValue`, `float` | `type: number` | `number` | `Double`/`double` | `float` | `float64` |
63+
| `repeated T` | `type: array`, `items: T` | `T[]` | `List<T>` | `List[T]` | `[]T` |
64+
| `Struct` (used as a map) | `type: object`, `additionalProperties: T` | `Record<string, T>` | `Map<String, T>` | `Dict[str, T]` | `map[string]T` |
65+
| optional/nullable marker | field absent from `required: [...]`, or `nullable: true` | `T \| undefined`, `T?`, an optional property (`field?:`) | `Optional<T>`, a `@Nullable` field, or absent from a builder's required setters | `Optional[T]`, `T \| None`, a field with a default | a pointer `*T`, or a zero-value default (harder to distinguish "absent" from "zero" — note this explicitly when it matters) |
66+
| "default when absent" idiom | schema `default: <value>` | `?? <default>`, `\|\|= <default>`, a default parameter | a default in the builder/constructor, or a null-check with a fallback | a default parameter, or `.get(key, default)` | a zero-value default, or an explicit fallback after an `ok` check |
67+
68+
---
69+
70+
## Steps
71+
72+
1. From the contract, list every message/schema definition (`grep -n "^message "` for protobuf; look under `components.schemas` for OpenAPI 3, `definitions` for Swagger 2 — use offset/limit or grep rather than reading a large file in one shot). Identify which ones correspond to what the target SDK actually implements today, per the controller/model cross-reference above — a different run, a different scope, or a different SDK entirely may cover a different subset. Re-derive this mapping from what's actually present in the target source; never assume it matches what a prior run against another SDK found.
73+
2. For each relevant contract message/schema, record its exact field names, its contract-native type, and which fields are required (protobuf's `required` list, or OpenAPI's `required: [...]` array — explicitly note "not required" if neither exists).
74+
3. Read every file in the target model source director(ies) (or parse each pasted block). For each publicly-reachable type in the target language (TypeScript `export interface`/`type`/`class`; Java a `public class`/`record`; Python a `class` reachable from the package's public module; Go an exported `struct` — in every case, anything reachable from the SDK's public entry point, not an unexported/package-private helper type) that corresponds to one of the messages/schemas from step 1, record its exact field names, the target language's types, and optionality — plus any default value substituted when a wire key is absent (e.g. a field defaulting to `0`, `''`, `None`, or a zero value during parsing).
75+
4. Build one field-by-field table per type: `Field | Contract | <Language> | Status`, with the SDK's actual language name in the header (e.g. `Java`, `Python`, `Go`) instead of a placeholder. A field "matches" when the name agrees after accounting for a documented wire-key rename (e.g. a `snake_case` wire key vs a `camelCase`/`PascalCase` property, per that language's naming convention), the types are equivalent per the reference table above, and optionality is consistent or the flip is clearly intentional.
76+
5. Flag every mismatch and note WHY it might be intentional before assuming it's a bug:
77+
- A contract-required field that's optional/defaulted in the SDK is often deliberate defensive parsing (drop/default instead of throw on a malformed response) — say so.
78+
- A field present in the contract but never exposed by the SDK (e.g. a raw plaintext value the SDK deliberately withholds) may be a deliberate security choice — note it as intentional, don't flag it as missing, but say what confirms the intent (a comment, a design doc, a test) if you can find one.
79+
- A field on the SDK's model with no contract peer (e.g. a field added to carry client-side routing info) is a legitimate SDK-side addition, not a mismatch — say so explicitly.
80+
- Cross-check the contract's own inline examples against its own field descriptions — authors sometimes document a field one way and give an example using a different key. Flag any such internal inconsistency you find, since the SDK's parsing code can only pick one of the two.
81+
- When comparing a non-Node SDK, don't assume it shares a Node-SDK quirk (a rename, a dropped field, an enum gap) found in an earlier run against a different language — re-verify against that SDK's own source every time.
82+
6. List client-side-only types with no contract peer separately (e.g. connection/vault configuration, not part of any wire response) — these are not parity gaps.
83+
7. List contract messages/schemas/operations with no implementation at all in the target SDK as a scope note, not a parity gap.
84+
8. Do not modify any source code in this pass, in this repo or in a cloned SDK repo — this skill only produces the doc.
85+
9. Do not mention test coverage or missing tests anywhere in the doc — this skill compares the contract to SDK models only, not test coverage.
86+
87+
---
88+
89+
## Output
90+
91+
Default filename: `docs/api-vs-sdk-parity.md`, for a run against this repo with no scoping. Use a suffixed filename instead — `docs/api-vs-sdk-parity-<scope>.md` — whenever the run doesn't match that default, so different scopes/SDKs get their own doc rather than overwriting each other or the default Node doc:
92+
- scoped to a subset of models in this repo (e.g. "just options") → `docs/api-vs-sdk-parity-options.md`-style scope tag.
93+
- run against a different SDK entirely → tag with that SDK's language/name, e.g. `docs/api-vs-sdk-parity-java.md`, `docs/api-vs-sdk-parity-python.md`, `docs/api-vs-sdk-parity-go.md`. Derive the tag from the SDK's repo name/language, not from guesswork.
94+
95+
- If the target file doesn't exist, create it with a top-level heading, a one-line note on what contract source, contract style, and model path(s) were compared, which SDK repo and ref (branch/tag/commit) it came from when it isn't this repo, and the date, then one section per type.
96+
- If it exists, replace only the sections for types covered by this run — leave unrelated sections untouched. Add new sections for types not previously covered.
97+
- Every section is a markdown table — never a prose bullet list — even when a whole area is one side's fields vs the other's absence. A cell with nothing to report is literally "Absent" or "N/A", not an omitted row.
98+
- End the doc with an "Open items worth a decision" list — anything a human needs to resolve (an ambiguous wire key, an unconfirmed intentional omission). If nothing is open, say so explicitly rather than omitting the section.
99+
100+
```
101+
# API vs SDK Parity — Skyflow <Language> SDK
102+
103+
Compares <contract source, e.g. "pasted protobuf" / "openapi.yaml"> against <language> models in `<source path(s)>` (<SDK repo/ref, or "this repo" if local>). Generated <date>.
104+
105+
### <TypeName> ↔ `<ContractMessage/Schema>`
106+
| Field | Contract | <Language> | Status |
107+
|---|---|---|---|
108+
| ... | ... | ... | ✅ match / ⚠️ <mismatch, with reasoning> / ℹ️ <intentional/SDK-only, with reasoning> |
109+
110+
### Client-side-only types (no contract peer)
111+
| Type | Purpose |
112+
|---|---|
113+
| ... | ... |
114+
115+
### Unimplemented contract features (scope note, not a parity bug)
116+
| Contract feature | Note |
117+
|---|---|
118+
| ... | ... |
119+
120+
---
121+
## Open items worth a decision
122+
<list, or "none identified">
123+
```

0 commit comments

Comments
 (0)