|
| 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