From 3967597d5fd6fb50954d8d398df819c2fcd5ee05 Mon Sep 17 00:00:00 2001 From: skyflow-bharti Date: Tue, 1 Sep 2026 09:44:58 +0530 Subject: [PATCH 1/2] SK-3113-parity-commands --- .claude/commands/api-vs-sdk.md | 123 ++++++++++++ .claude/commands/compare-sdks.md | 186 ++++++++++++++++++ .../commands/sdk-behavioral-differences.md | 93 +++++++++ 3 files changed, 402 insertions(+) create mode 100644 .claude/commands/api-vs-sdk.md create mode 100644 .claude/commands/compare-sdks.md create mode 100644 .claude/commands/sdk-behavioral-differences.md diff --git a/.claude/commands/api-vs-sdk.md b/.claude/commands/api-vs-sdk.md new file mode 100644 index 00000000..85b7b071 --- /dev/null +++ b/.claude/commands/api-vs-sdk.md @@ -0,0 +1,123 @@ +--- +name: api-vs-sdk-parity +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 +constraints: + - "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." + - "Do not modify any source code in this pass — this skill only produces the doc." + - "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." +--- + +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. + +## Getting the contract + +`$ARGUMENTS` may contain either: +- a path to a contract file (protobuf `.proto`, or OpenAPI/Swagger `.yaml`/`.yml`/`.json`) — relative to the repo root, or absolute, or +- the raw contract content itself, pasted in a fenced code block (` ```proto ... ``` `, ` ```yaml ... ``` `, or ` ```json ... ``` `). + +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. + +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. + +## Getting the SDK to compare + +`$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 `@` or "branch: "/"tag: " suffix) or a local filesystem path to a *different* repository root is the **target SDK location**. + +- **No SDK location given** → default to this repo, checked out at the current working directory (Node/TypeScript SDK). +- **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. +- **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/`, so repeat runs against the same SDK don't re-clone from scratch: + - If that directory doesn't exist yet: `git clone --depth 1 [--branch ] `. + - If it already exists as a git checkout: `git fetch --depth 1 origin && 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. + - 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. + +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: +- 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). +- 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. +- 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. + +Alternatively, `$ARGUMENTS` can specify the model location directly: +- a directory path containing the target language's model source files, or +- one or more labeled fenced code blocks containing the actual type definitions (any language), for comparing against a model source not checked out anywhere. + +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 — +- `src/vault/model/request/` — request bodies +- `src/vault/model/response/` — response bodies +- `src/vault/model/options/` — caller-facing option objects + +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. + +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. + +--- + +## Type-equivalence reference + +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: + +| Protobuf | OpenAPI / JSON Schema | TypeScript | Java | Python | Go | +|---|---|---|---|---|---| +| `string` / `StringValue` | `type: string` | `string` | `String` | `str` | `string` | +| `int32` / `Int32Value`, `int64` | `type: integer` | `number` | `Integer`/`int`, `Long`/`long` | `int` | `int32`/`int64` | +| `bool` / `BoolValue` | `type: boolean` | `boolean` | `Boolean`/`boolean` | `bool` | `bool` | +| `double` / `DoubleValue`, `float` | `type: number` | `number` | `Double`/`double` | `float` | `float64` | +| `repeated T` | `type: array`, `items: T` | `T[]` | `List` | `List[T]` | `[]T` | +| `Struct` (used as a map) | `type: object`, `additionalProperties: T` | `Record` | `Map` | `Dict[str, T]` | `map[string]T` | +| optional/nullable marker | field absent from `required: [...]`, or `nullable: true` | `T \| undefined`, `T?`, an optional property (`field?:`) | `Optional`, 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) | +| "default when absent" idiom | schema `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 | + +--- + +## Steps + +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. +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). +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). +4. Build one field-by-field table per type: `Field | Contract | | 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. +5. Flag every mismatch and note WHY it might be intentional before assuming it's a bug: + - 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. + - 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. + - 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. + - 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. + - 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. +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. +7. List contract messages/schemas/operations with no implementation at all in the target SDK as a scope note, not a parity gap. +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. +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. + +--- + +## Output + +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-.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: +- scoped to a subset of models in this repo (e.g. "just options") → `docs/api-vs-sdk-parity-options.md`-style scope tag. +- 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. + +- 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. +- 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. +- 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. +- 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. + +``` +# API vs SDK Parity — Skyflow SDK + +Compares against models in `` (). Generated . + +### ↔ `` +| Field | Contract | | Status | +|---|---|---|---| +| ... | ... | ... | ✅ match / ⚠️ / ℹ️ | + +### Client-side-only types (no contract peer) +| Type | Purpose | +|---|---| +| ... | ... | + +### Unimplemented contract features (scope note, not a parity bug) +| Contract feature | Note | +|---|---| +| ... | ... | + +--- +## Open items worth a decision + +``` diff --git a/.claude/commands/compare-sdks.md b/.claude/commands/compare-sdks.md new file mode 100644 index 00000000..1fd0cdc8 --- /dev/null +++ b/.claude/commands/compare-sdks.md @@ -0,0 +1,186 @@ +--- +name: compare-sdks +description: Compare feature coverage and request/response/option type structure across two or more Skyflow SDKs (Node, Java, Python, Go, ...). Unlike api-vs-sdk-parity (one SDK vs the wire contract), this compares SDKs against each other. Prompts the user for each SDK's git URL/path if not given in $ARGUMENTS. Argument: $ARGUMENTS +constraints: + - "Never edit, create, or delete any file inside a cloned/checked-out SDK repo — those are read-only sources, not output targets." + - "Do not modify any source code in this pass — this skill only produces the comparison doc." + - "Never invent, guess, or recall an SDK's operations or model shape from memory — read the actual cloned/local source on every run." +--- + +You are producing a cross-SDK comparison: which operations each SDK exposes, and how each SDK's request/response/options types line up field-by-field against the others. This is a **SDK-vs-SDK** comparison, not SDK-vs-contract — if the user actually wants the wire contract (protobuf/OpenAPI) compared to a single SDK, use the `api-vs-sdk-parity` skill instead. + +## Getting the SDKs to compare + +`$ARGUMENTS` may list one or more SDK locations, each either: +- a git URL (`https://github.com/...`, `git@...`), optionally with `@` or "branch: "/"tag: ", or +- a local filesystem path to an SDK repo checkout. + +This repo (the Node/TypeScript SDK, at the current working directory) is **always included** as one participant by default — no need for the user to pass its own path. Everything in `$ARGUMENTS` is additional SDKs to compare it against. + +**If `$ARGUMENTS` supplies fewer than one other SDK location** (i.e. nothing to compare this repo against), stop and use AskUserQuestion (or a direct question if AskUserQuestion isn't available) to ask the user for the git URL or local path of each additional SDK they want compared — you need at least two participants total (this repo + at least one more) to produce a comparison. Don't guess a URL or proceed with only this repo. + +For each SDK location given: +- **Local path** → use it directly, read-only. Note whatever `git log -1`/`git status` shows in the doc header, but don't `git pull` a checkout you don't own. +- **Git URL** → clone it yourself into a stable cache location outside this repo, e.g. `/tmp/skyflow-sdk-compare-clones/`, so repeat runs don't re-clone from scratch: + - New: `git clone --depth 1 [--branch ] `. + - Already cloned: `git fetch --depth 1 origin && git checkout FETCH_HEAD` before reading, so the comparison isn't run against a stale checkout. + - Treat every clone as read-only — never write into it, never run install/build steps, you only need to read source. + +For each participant (including this repo), identify its language and layers from what's actually there — don't assume every SDK mirrors this repo's `src/vault/controller` / `src/vault/model/{request,response,options}` layout: +- Language from file extensions at the repo root / `src`-equivalent (`.ts`, `.java`, `.py`, `.go`, ...). +- Its controller/service layer (where operations like insert/update/get/delete/detokenize/tokenize/query/deidentify-text/reidentify-text/file-upload/deidentify-file/invoke are implemented) — check `README`, package/module layout; don't assume it matches another SDK's naming. +- Its model layer (request/response/options types, or that language's fold of the same three concepts — e.g. request builders that also carry what Node calls "options"). +- If a layer can't be confidently found after a real search, say so in the doc rather than guessing, and ask the user if it matters for the run. + +--- + +## Type-equivalence reference + +Use this (extend it if a target SDK is in a language not listed) to decide whether a field "matches" across languages — the point is structural equivalence, not identical spelling: + +| Concept | TypeScript | Java | Python | Go | +|---|---|---|---|---| +| string | `string` | `String` | `str` | `string` | +| integer | `number` | `Integer`/`int`, `Long`/`long` | `int` | `int32`/`int64` | +| boolean | `boolean` | `Boolean`/`boolean` | `bool` | `bool` | +| float/double | `number` | `Double`/`double` | `float` | `float64` | +| array of T | `T[]` | `List` | `List[T]` | `[]T` | +| map/dict of T | `Record` | `Map` | `Dict[str, T]` | `map[string]T` | +| optional/nullable | `T \| undefined`, `T?`, `field?:` | `Optional`, `@Nullable`, absent from required setters | `Optional[T]`, `T \| None`, a default | a pointer `*T`, or a zero-value default (note explicitly when "absent" vs "zero" is ambiguous) | + +A field name differing only by each language's own naming convention (`camelCase` in TS, `PascalCase`/`camelCase` in Java/Go, `snake_case` in Python) is a **match**, not a mismatch — flag naming only when it diverges from that SDK's own convention or clearly drops semantic meaning. + +--- + +## Steps + +1. **Build the feature matrix.** From each participant's controller/service layer, list every operation it implements (insert, update, get, delete, detokenize, tokenize, query, deidentify-text, reidentify-text, file-upload, deidentify-file, invoke, and any others actually present — re-derive this list from source, don't assume it matches a prior run). Normalize by the underlying wire operation, not by each SDK's exact method spelling (e.g. Python's `snake_case` method name and Node's `camelCase` one are the same operation). Produce one matrix: rows = operations, columns = each SDK (participant's language/name), cells = ✅ implemented / ❌ not implemented, with a note if an SDK exposes it under a notably different name or shape (e.g. folded into another method). + +2. **Compare request/response/options structures per shared operation.** For every operation implemented by two or more participants, read each participant's actual request/response/options type(s) for that operation (or a language's equivalent fold of those concepts) and record exact field names, that language's type, and optionality/defaults. + +3. **Build one field-by-field table per operation**, columns = `Field | | | ... | Status`. A field "matches" when it's present across participants with structurally-equivalent types (per the reference table) and consistent optionality, accounting for each language's own naming convention. + +4. **Flag every mismatch and note why it might be intentional before assuming it's a bug:** + - A field one SDK exposes as optional/defaulted where another treats it as required may be deliberate defensive parsing — say so. + - A field present in one SDK's model with no peer in another may be a legitimate SDK-side addition (e.g. client-side routing info) rather than a gap — say so explicitly. + - A newer SDK may simply not have caught up to a feature/field another SDK already has — note it as a currency gap, not a design divergence, if that's what the evidence suggests (e.g. changelog, version, recent commit history). + - Don't assume a quirk found in one SDK pairing (a rename, a dropped field) generalizes to a third SDK — re-verify against that SDK's own source. + - Every flagged mismatch feeds the differences summary table in Output (step 4a below) — capture operation, field, which SDK(s) diverge, and the one-line reasoning as you go, rather than re-deriving it from the per-operation tables afterward. + +5. **Compare the service-account / credential-utility surface, every run, not just the vault/detect/connection controller operations.** Every SDK ships free functions or an equivalent object (e.g. `generateBearerToken`/`generate_bearer_token`/`GenerateBearerToken`/a `BearerToken` builder) for: generating a bearer token from a credentials file path, generating one from a credentials string, generating signed data tokens (same file-path/string split), and checking whether a token is expired (`isExpired`/`is_expired`/`IsExpired`/`Token.isExpired`). These live outside the controller layer (often a `service-account`/`serviceaccount`/`service_account` module or a shared/common package used by more than one SDK) — find them by grepping for "bearer" and "signed data token" rather than assuming a fixed path. Apply steps 2–4 to this surface exactly as to any operation: field-by-field tables for the options/parameters each function accepts (ctx, roles/roleIds, logLevel, a token-URI override, time-to-live, etc.) and for what each returns — pay particular attention to the **return shape**, since SDKs diverge here more than elsewhere (a named object/struct vs. a bare tuple vs. a single bare value with a field silently dropped is a real, well-precedented divergence to watch for, not a hypothetical one). + +6. **Compare client construction and configuration, every run, not just as a client-side-only-types list.** This is the entry point every operation above depends on, and it is directly comparable across SDKs even though it isn't itself a wire operation. Cover, field-by-field, per participant: + - The construction pattern itself (single config-object constructor vs. a chainable builder vs. functional options), and whether configs are typed classes/structs or untyped dicts/maps. + - `VaultConfig`-equivalent: id, cluster id, environment (and its default when omitted — check whether it's a compile-time/library default or something that only surfaces as a runtime error on first call), credentials, and any SDK-only extras (e.g. a direct base-URL override that bypasses cluster id/env). + - `ConnectionConfig`-equivalent: id, URL, credentials — and specifically whether credentials are required or optional at this level, since SDKs disagree here. + - `Credentials`-equivalent: is it a discriminated union (type-system-enforced exactly-one-of) or a flat struct/dict relying on runtime validation for the same "exactly one of token/path/credentialsString/apiKey" rule; whether a token-URI override field exists; whether `roles`/`context` are present on every credential shape or only some. + - Client accessors (`.vault()`/`.detect()`/`.connection()` or equivalents) and their **default-selection behavior when no id is passed and multiple configs are registered** — check whether the underlying data structure preserves insertion order (deterministic first-added) or not (e.g. an unordered map/dict in a language where iteration order isn't guaranteed, which is a real non-determinism bug to flag, not a style nit). + - Config mutation methods (add/update/remove/get vault or connection config, update credentials, set/update log level) — note deprecated aliases and whether these methods are chainable (return the client) or not, per SDK. + +7. List remaining client-side-only types with no peer in any other SDK separately (e.g. internal client registries, dead/unused placeholder code, stub types for unimplemented operations) — not a parity gap. Do not put `VaultConfig`/`ConnectionConfig`/`Credentials`/the construction pattern here now that step 6 covers them field-by-field. + +8. Do not modify source code in this pass, in this repo or in any cloned SDK repo — this skill only produces the doc. + +9. Do not mention test coverage anywhere in the doc — this skill compares operations and model shape only. + +--- + +## Output + +Default filename: `docs/sdk-comparison.md`. If the run is scoped (e.g. "just insert", "just responses") or repeated against a different set of SDKs than a prior run, use `docs/sdk-comparison-.md` instead, so different comparisons don't silently overwrite each other. + +- If the target file doesn't exist, create it with a top-level heading, a one-line note listing every participant (repo/ref or "this repo", plus language), and the date, then the feature matrix, then the differences summary, then one section per compared operation, then the service-account section (step 5) and the client construction & configuration section (step 6), then remaining client-side-only types (step 7), then open items. +- If it exists, replace only the sections covered by this run — leave unrelated sections untouched; add new sections for operations/participants not previously covered. +- The service-account section (step 5) and the client construction & configuration section (step 6) are **not optional add-ons** — include them on every run against this skill, the same as any vault/detect/connection operation, even if the user's request only names specific vault operations; only skip them if the run is explicitly scoped to exclude them (e.g. "just compare insert"). +- **Every section — with no exception — is a markdown table, never prose or a bullet list.** This applies to the feature matrix, the differences summary, every per-operation request/response/options section, the service-account and client-construction sections, the client-side-only-types list, and the closing open-items list (as a `| # | Item |` table). If you catch yourself about to write a sentence describing a difference, stop and put it in a table row instead. A cell with nothing to report is literally "Absent" or "N/A", not an omitted row. +- The **differences summary** table is the primary deliverable readers scan first: one row per divergence found anywhere in the run (a feature gap from step 1, a mismatch flagged in step 4, or a divergence found in the service-account or client-construction sections) — never restate rows where every participant simply matches. If a run finds zero divergences, keep the table with a single row stating that. + +``` +# SDK Comparison + +Participants: , , ... Generated . + +## Feature matrix + +| Operation | | | ... | Notes | +|---|---|---|---|---| +| insert | ✅ | ✅ | ... | | +| ... | | | | | + +## Differences summary + +| # | Operation | Field | | | ... | Difference | Likely intentional? | +|---|---|---|---|---|---|---|---| +| 1 | insert | ... | ... | ... | ... | | Yes/No/Unclear — | +| ... | | | | | | | | + +### `` — Request +| Field | | | ... | Status | +|---|---|---|---|---| +| ... | ... | ... | ... | ✅ match / ⚠️ / ℹ️ | + +### `` — Response +| Field | | | ... | Status | +|---|---|---|---|---| +| ... | + +### `` — Options +| Field | | | ... | Status | +|---|---|---|---|---| +| ... | + +## Generate Bearer Token (service-account credential utility) +| Field | | | ... | Status | +|---|---|---|---|---| +| ... | ... | ... | ... | ✅ match / ⚠️ / ℹ️ | + +## Generate Signed Data Tokens (service-account credential utility) +| Field | | | ... | Status | +|---|---|---|---|---| +| ... | + +## Token Expiry Check (service-account credential utility) +| Field | | | ... | Status | +|---|---|---|---|---| +| ... | + +## Client Construction & Configuration + +### Construction pattern +| Aspect | | | ... | +|---|---|---|---| +| Entry point | ... | ... | ... | +| Config shape (typed vs. dict/map) | ... | ... | ... | +| Construction failure mode | ... | ... | ... | + +### `VaultConfig` +| Field | | | ... | Status | +|---|---|---|---|---| +| ... | + +### `ConnectionConfig` +| Field | | | ... | Status | +|---|---|---|---|---| +| ... | + +### `Credentials` +| Field | | | ... | Status | +|---|---|---|---|---| +| ... | + +### Client accessors & config mutation +| Aspect | | | ... | +|---|---|---|---| +| get a vault/detect/connection controller | ... | ... | ... | +| default selection with no id (multiple configs registered) | ... | ... | ... | +| add / update / remove / get config | ... | ... | ... | + +### Client-side-only types (no peer in other SDKs) +| SDK | Type | Purpose | +|---|---|---| +| ... | ... | ... | + +--- +## Open items worth a decision + +``` diff --git a/.claude/commands/sdk-behavioral-differences.md b/.claude/commands/sdk-behavioral-differences.md new file mode 100644 index 00000000..9b6d140d --- /dev/null +++ b/.claude/commands/sdk-behavioral-differences.md @@ -0,0 +1,93 @@ +Generate or update a doc describing user-exposed functionality and feature differences between two SDKs (e.g. an older and newer generation of the same SDK), in any language. Argument: $ARGUMENTS + +## Getting the two SDKs to compare + +$ARGUMENTS may specify, for each SDK being compared: +- a source directory path (any language), or +- one or more labeled fenced code blocks with that SDK's actual source (for an SDK not checked out in this repo — e.g. ` ```kotlin ... ``` `), plus +- an optional short label for each SDK to use in headings (e.g. "legacy" / "v2", or version numbers). + +Never invent or recall another SDK's shape or behavior from memory — read the directory or use exactly what was pasted. + +### Auto-detecting the two SDKs when $ARGUMENTS doesn't name them + +Don't default to any hardcoded folder name — detect the pair from the repo itself: + +1. Check the package manifest(s) for a multi-target/multi-product setup: SwiftPM `Package.swift`'s `products` array, `package.json` workspaces, `settings.gradle`/`settings.gradle.kts` `include(...)`, `Cargo.toml` `[workspace] members`, `pyproject.toml`/`setup.cfg` multiple packages, one `*.podspec`/`*.gemspec` per library, `pom.xml` ``. Each declared library/module is a candidate SDK. +2. If that doesn't cleanly yield candidates, fall back to scanning top-level directories for ones that each have their own source root (`Sources/`, `src/`, `lib/`) and their own build/manifest file — treat each as a candidate SDK. +3. Narrow to exactly two candidates that look like "the same SDK, two generations/flavors" rather than two unrelated libraries — e.g. they share a common internal module/dependency, or their names/descriptions clearly pair up (legacy/next, v1/v2, GA/beta). +4. Determine which of the two is older vs newer using whatever signal actually exists — don't guess if it's unclear: explicit version/generation naming (`v1`/`v2`, `legacy`/`next`), README/CHANGELOG/manifest language ("deprecated", "GA" vs "Beta", "legacy"), or git history (`git log --diff-filter=A --follow --format=%ad -- ` — the directory with the earlier oldest commit is the older SDK). +5. If you can't confidently narrow to exactly two SDK directories, or can't tell which is older, **stop and ask the user** rather than guessing — state what candidates you found and why it's ambiguous. +6. State the auto-detected paths and labels explicitly in the doc's header, exactly as if they'd been passed as an argument, so a wrong detection is visible at a glance and easy to correct on the next run. + +Determine each SDK's language from whatever is most explicit: what the user stated, the dominant file extension in its directory, or the fenced code block's language tag. + +If the two SDKs share a common underlying module (as this repo's two Swift SDKs share `SkyflowCore/Sources/` via Swift's `package` access level), identify it: logic both SDKs delegate to unmodified is identical by construction, and stating that explicitly lets the investigation focus on where behavior can actually diverge — each SDK's own top-level source. If the two SDKs don't share any such module (e.g. two independently-shipped SDKs, or different languages entirely), skip this step and compare full behavior directly. + +## Scope: public-surface functionality only + +This doc is about what an app developer integrating either SDK can actually **do, configure, or observe** through the public API — not internal implementation detail. For every candidate finding, ask: "would an app developer notice this without reading the SDK's source?" If the answer is no, it doesn't belong in this doc. Concretely: + +**In scope:** +- A method/capability that exists in one SDK's public API and not the other. +- A parameter, option, or config value that one SDK accepts and the other doesn't, or that changes what you can express (e.g. a single-value option becoming a richer typed option). +- A difference in what data the app receives back through the public API (e.g. whether a sensitive value is ever exposed, whether a partial-batch failure surfaces per-item). +- A difference in whether an equivalent public call succeeds or fails for the same input. + +**Out of scope — do not include:** +- Raw wire/payload key naming or casing, unless the app's public callback literally hands that raw payload to the app and the key difference changes what the app can read from it. +- Internal implementation quality (crash risk, code robustness, algorithmic detail) that isn't a deliberate functional/feature difference. +- Cosmetic differences with no functional effect: log tags, string prefixes in messages, comments, internal type names. +- Threading/dispatch-queue or concurrency-model behavior. +- Test coverage or missing tests. + +## Format: tables only + +Every area section in this doc — **Feature availability included, with no exception** — is a markdown table (`Capability | | | Classification`), never a prose bullet list, even when a whole area is one SDK's capabilities vs the other's. A cell that has nothing to report is literally `Absent`, not an omitted row. This applies equally when updating an existing doc: if a section you're touching this run was previously written as bullets (an earlier generation may have drifted from this format), convert it to a table as part of the update even if the underlying content isn't changing. + +## Areas to compare + +1. **Feature availability** — enumerate capabilities that exist in only one SDK's public API at all. Diff the file/type lists of the two source directories (e.g. `find -name "*."` vs `find -name "*."`, using each SDK's actual extension) to catch this systematically, then confirm each candidate is actually public/exported (or otherwise app-reachable — `public` in Swift/Kotlin/Java/C#, `export` in TypeScript, no leading underscore in Python, an exported/capitalized identifier in Go) before listing it — an internal-only type with no public entry point isn't a user-facing feature difference. Present this as a `Capability | | | Classification` table per the Format rule above (each side's cell is either a one-line description with its citation, or "Absent"). **A file-list diff only catches capabilities unique to one side — it will silently miss an entire capability area that both SDKs implement identically via shared/inherited code, because identical files don't show up in a diff.** That's what step 2 is for. +2. **Full public-surface inventory (do this before finalizing the area list)** — for each SDK, start from its top-level public entry point(s) (main class/module and everything it re-exports) and walk outward: every public method, property, static/enum member, config option, event/callback, and (for a UI-rendering SDK) component/element lifecycle hook that an app can call, set, or observe. Do this for the full surface, not just the parts that looked interesting during the operation-level read in step 3 — a capability area that's entirely inherited unmodified from a shared module by both SDKs is exactly the kind of thing an operation-focused read skips, and it still belongs in the doc as an explicit ✅-classified section (per the classification rule below) so a reader isn't left wondering whether it was checked at all. Concretely, beyond the request/response operations in step 3, expect areas like: component/element lifecycle (create/mount/unmount/update/destroy, state introspection), an event or callback/listener system (what events exist, what payload each carries, whether unsubscribe exists), client-side validation rules an app can attach (including any cross-field/cross-element rules), styling/theming/UI configuration, session/init config beyond bare authentication (log level, environment/mode switches, custom endpoints, feature flags), and any telemetry/analytics opt-in. Treat this as a checklist to consider for any SDK shape, not a ceiling — derive the actual area list from what each SDK actually implements, and drop items that don't apply (e.g. no event system exists) rather than forcing a section. +3. **Request/response operations** — derive the operation-level areas from what the two SDKs actually implement (don't assume a fixed list). For a Skyflow SDK specifically, expect: collect/insert-update (request configuration, response shape, partial-failure visibility), reveal/detokenize (can the app ever receive a sensitive plaintext value, redaction options and their validation), error handling/callback shape (typed vs untyped), any mocking/masking opt-in features, and validation (what input succeeds or fails for an equivalent call, not how the validator is implemented). Group findings under one heading per operation area. + +## Steps + +1. For each area, read both SDKs' source. Where both delegate to shared underlying code identified above, behavior is identical by construction — state that explicitly rather than re-deriving it, and spend the investigation on each SDK's own layer, where behavior can actually diverge. +2. For each area, describe what an app developer can do differently: what's callable, what's configurable, what's returned, and whether an equivalent call succeeds or fails. Before writing a finding down, apply the in-scope/out-of-scope test above — drop it if it's implementation-only. +3. Classify every difference found: + - ⚠️ **Surprising/breaking** — an app porting between the two SDKs could silently get different observable behavior for what looks like the same call. + - ℹ️ **Expected/versioned** — a deliberate difference because the newer SDK targets a different backend/contract with genuinely different semantics — not something to fix, just something to know. + - ✅ **Identical** — the public-facing behavior really is the same — state this explicitly so the area isn't left as an open question. +4. **An area doesn't need a difference to earn a section.** If the full public-surface inventory (step 2 above) turns up a whole capability area that both SDKs implement identically via shared code, write it up as its own section with every row classified ✅ and a one-line note on why (e.g. "inherited unmodified from ``, no per-SDK override exists"). Skipping it because "nothing diverged" is exactly the gap this step exists to prevent — the doc's job is coverage of the full user-facing surface, not just a diff of where the two SDKs disagree. +5. Cite the exact file:line backing each difference, for both SDKs. +6. Do not modify any source code in this pass — this command only produces the doc. +7. Do not mention test coverage or missing tests anywhere in the doc. + +## Output + +Write to `docs/sdk-behavioral-differences.md` when the pair being compared was auto-detected (no SDKs named in $ARGUMENTS) or matches whatever pair a prior run in this repo already used. If $ARGUMENTS explicitly names a different pair (e.g. comparing against a pasted external SDK, or a third SDK in a repo with more than two), write to `docs/sdk-behavioral-differences--vs-.md` instead, so different comparisons get their own doc rather than overwriting each other. + +- If the target file doesn't exist, create it with a top-level heading, a one-line note on which two SDKs (source paths/labels/languages) were compared and the date, then one section per area. +- If it exists, replace only the sections for areas covered by this run — leave unrelated sections untouched. +- End with a "Migration watch-outs" list: a concrete, ranked list of what a team porting an app from SDK A to SDK B (or maintaining both side by side) needs to know, most-likely-to-bite-first at the top — only functional/feature items, per the scope above. If nothing rises to that level, say so explicitly. + +``` +# SDK Behavioral Differences — vs + +Compares `` against `` (shared behavior via `` noted where relevant, if any). Scope: user-exposed functionality/feature differences only — not internal implementation detail. Generated . + +### Feature availability +| Capability | | | Classification | +|---|---|---|---| +| ... | Present — / Absent | Present — / Absent | ⚠️ / ℹ️ / ✅ | + +### +| Capability | | | Classification | +|---|---|---|---| +| ... | ... | ... | ⚠️ / ℹ️ / ✅ | + +--- +## Migration watch-outs + +``` From 80887ccfdaacffaa05f19ae86eef64544b49856b Mon Sep 17 00:00:00 2001 From: skyflow-bharti Date: Tue, 1 Sep 2026 14:28:48 +0530 Subject: [PATCH 2/2] SK-3113 parity commands --- .claude/commands/compare-client-sdks.md | 107 ++++++++++++++++++++++++ .claude/commands/compare-sdks.md | 71 ++++++++++------ 2 files changed, 153 insertions(+), 25 deletions(-) create mode 100644 .claude/commands/compare-client-sdks.md diff --git a/.claude/commands/compare-client-sdks.md b/.claude/commands/compare-client-sdks.md new file mode 100644 index 00000000..1587a45f --- /dev/null +++ b/.claude/commands/compare-client-sdks.md @@ -0,0 +1,107 @@ +Generate or update a doc comparing user-exposed functionality and public behavioral differences between two SDKs fetched from git, in any language. Argument: $ARGUMENTS + +This command always fetches fresh clones from git — it never compares this repo's own `Skyflow/` vs `SkyflowFlowVault/` directories. For that, use `/sdk-behavioral-differences` instead. + +## Getting the two SDKs to compare + +$ARGUMENTS must contain **two git sources**, each as a URL (`https://...`, `git@...`, or anything `git clone` accepts), optionally followed by: +- a ref (branch, tag, or commit) — as `@` appended to the URL, or stated in words ("branch release/3.2") +- a subdirectory to treat as the SDK root within that repo (for a monorepo where the SDK isn't at the repo root) +- a short label to use in headings (e.g. "android", "js-v2", "legacy") + +If $ARGUMENTS contains fewer than two git sources, **stop and ask the user** for both — never fall back to comparing local paths in this repo, and never invent a URL. Never guess another SDK's shape or behavior from memory; only read what's actually cloned. + +### Cloning + +1. Create a scratch directory for this run (the session scratchpad if one is available, otherwise a fresh `mktemp -d`). +2. For each source: `git clone --depth 1 [--branch ] /