feat: per-ref policies on REST endpoints across all SDKs#30
Conversation
Extend the optional `ops`/`refs` JWT policy claims to every ref-mutating operation in the Go, TypeScript, and Python SDKs. Previously only the remote-URL token minting accepted ref policies; REST methods like create_branch, merge, commit, and notes silently dropped them. Adds `OP_NO_PUSH` / `OpNoPush` constant alongside the existing `OP_NO_FORCE_PUSH`, fixes a pre-existing inconsistency in the Python SDK where create_branch error responses didn't fall back to the `error` key, and verifies end-to-end against the acme staging tenant. Bumps: Go 0.8.0->0.9.0, TS 1.8.0->1.9.0, Python 1.9.0->1.10.0.
There was a problem hiding this comment.
Pull request overview
This PR extends JWT policy support so per-ref policy rules (refs claim) are carried through to ref-mutating REST endpoints across the Go, TypeScript, and Python SDKs (not just remote URL minting), adds a no-push policy constant in each SDK, updates docs, and bumps SDK versions accordingly.
Changes:
- Add
refs(per-ref, ordered “first match wins”) policy support and propagateops/refsthrough ref-mutating REST calls in Go/TS/Python SDKs. - Add
no-pushpolicy constant (OP_NO_PUSH/OpNoPush) and helpers to encoderefsinto the JWT claim shape. - Update skill/docs and bump package versions; add/extend unit tests plus optional live smoke scripts.
Reviewed changes
Copilot reviewed 25 out of 25 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
| skills/code-storage/SKILL.md | Documents repo-wide ops vs per-ref refs, adds no-push, and examples across SDKs. |
| packages/code-storage-typescript/tests/ref-policies-live.mjs | Adds a local-stack live smoke test script for ref policy enforcement. |
| packages/code-storage-typescript/tests/index.test.ts | Adds unit coverage asserting refs is encoded into JWTs and omitted when absent. |
| packages/code-storage-typescript/src/types.ts | Introduces OP_NO_PUSH, PolicyOptions, and per-ref policy types; threads options into ref-mutating APIs. |
| packages/code-storage-typescript/src/jwt_claims.ts | Adds helper to encode RefPolicies into the JWT refs claim tuple format. |
| packages/code-storage-typescript/src/index.ts | Propagates ops/refs into JWT minting for ref-mutating REST methods; emits refs claim when present. |
| packages/code-storage-typescript/package.json | Bumps TypeScript SDK version to 1.9.0. |
| packages/code-storage-python/tests/test_client.py | Adds JWT generation tests for refs claim inclusion/omission. |
| packages/code-storage-python/tests/ref_policies_live.py | Adds a local-stack live smoke test script for ref policy enforcement. |
| packages/code-storage-python/pyproject.toml | Bumps Python SDK version to 1.10.0. |
| packages/code-storage-python/pierre_storage/version.py | Bumps Python SDK internal version constant. |
| packages/code-storage-python/pierre_storage/types.py | Adds OP_NO_PUSH and typed structures for per-ref policy rules. |
| packages/code-storage-python/pierre_storage/repo.py | Threads ops/refs into ref-mutating methods and improves create_branch error message extraction. |
| packages/code-storage-python/pierre_storage/client.py | Plumbs refs through internal JWT generation path. |
| packages/code-storage-python/pierre_storage/auth.py | Adds encode_refs_claim and includes refs in JWT payload when provided. |
| packages/code-storage-python/pierre_storage/init.py | Exposes new constants/types and encode_refs_claim in the public package API. |
| packages/code-storage-go/version.go | Bumps Go SDK version to 0.9.0. |
| packages/code-storage-go/types.go | Adds OpNoPush and ref policy types; extends option structs to carry Ops/Refs. |
| packages/code-storage-go/repo.go | Propagates Ops/Refs into JWT generation for ref-mutating repo methods. |
| packages/code-storage-go/repo_test.go | Adds unit coverage asserting refs claim inclusion/omission in minted JWTs. |
| packages/code-storage-go/jwt_claims.go | Adds helper to encode ref policies into JWT refs claim shape. |
| packages/code-storage-go/jwt_claims_test.go | Adds unit coverage for encodeRefsClaim encoding behavior. |
| packages/code-storage-go/diff_commit.go | Propagates Ops/Refs into diff-commit JWT generation. |
| packages/code-storage-go/commit.go | Propagates Ops/Refs into commit builder JWT generation. |
| packages/code-storage-go/client.go | Emits refs claim when present during JWT generation. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| for i, rule := range refs { | ||
| opList := []string(rule.Ops) | ||
| if opList == nil { | ||
| opList = []string{} | ||
| } | ||
| out[i] = []any{rule.Pattern, opList} | ||
| } |
There was a problem hiding this comment.
Not a bug. Op is a Go type alias (type Op = string), not a defined type, so Ops is literally []string and []string(rule.Ops) is a no-op conversion that compiles fine. Verified by go build ./... && go test ./... green on this branch.
| t.Fatalf("unexpected pattern: %v", mainRule[0]) | ||
| } | ||
| mainOps, ok := mainRule[1].([]string) | ||
| if !ok || len(mainOps) != 1 || mainOps[0] != OpNoPush { |
There was a problem hiding this comment.
Same as the previous comment — Op is an alias (type Op = string), so mainOps[0] (type string) compared to OpNoPush (also string via the alias) is a same-type comparison. Tests pass.
| import { encodeRefsClaim } from '../src/jwt_claims.ts'; | ||
|
|
||
| const __dirname = path.dirname(fileURLToPath(import.meta.url)); | ||
|
|
There was a problem hiding this comment.
Good catch — fixed in d19009d. Now imports encodeRefsClaim from the dist barrel that loadGitStorage already loads, so the script runs under plain node.
Per-op REST options never had an `ops` field on main; my earlier commit added one alongside `refs`. Remove it — `ops` belongs on the URL minting path (RemoteURLOptions only), and per-op REST calls take `refs` only.
The live test's `import ... from '../src/jwt_claims.ts'` failed under plain node (no TS loader). Take encodeRefsClaim from the dist barrel that loadGitStorage already imports, so the script runs with `node`.
Summary
ops/refsJWT policy claims to every ref-mutating REST method in all three SDKs (Go, TypeScript, Python). Previously only remote-URL token minting accepted ref policies — REST methods likecreateBranch,merge,createCommit, notes, etc. silently dropped them despite the gateway enforcing the same claim on those endpoints.OP_NO_PUSH/OpNoPushconstant alongside the existingOP_NO_FORCE_PUSH.RepoImpl.create_branch(Python) where policy-denied responses were swallowed into a generic\"HTTP 409\"message; it now falls back to theerrorkey likecreate_tag/delete_tag/delete_branchalready did.0.8.0→0.9.0, TS1.8.0→1.9.0, Python1.9.0→1.10.0.End-to-end validation against acme staging
Verified by minting tokens via each SDK and exercising the gateway directly:
refsclaim correctly encodedcreate_branch feature/blockeddenied byrefs/heads/feature/*=no-pushcreate_tag v-blockeddenied byrefs/tags/*=no-pushcreate_branch topic/allowed(non-matching deny) succeedscreate_branch release/v1first-match-wins (explicit allow before*=no-push)Test plan
go build ./... && go test ./...pnpm run build && pnpm vitest run(178 tests passing)pytest tests/(159 tests passing)