feat: per-ref policies on REST endpoints across all SDKs#30
Merged
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.
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`.
Reorder the policy documentation so the per-ref `refs` claim is presented as
the way to add branch protection. The repo-wide `ops` claim is now labeled
"legacy — do not use in new code" and explicitly redirects to the equivalent
`refs: [{ pattern: '*', ops: [...] }]` form. The example procedure (Mint a
Force-Push-Prevented Remote URL) and the JWT payload example are updated to
use `refs` so anyone copying from the skill writes the modern form.
The single-word `refs` SDK option name was confusing next to the JWT `refs` claim and the various `ref`/`refs/heads/...` strings in this code. Rename the SDK-surface field so it reads as "ref policies": - TypeScript: `PolicyOptions.refs` → `refPolicies` (on `GetRemoteURLOptions` and every option interface that extended `PolicyOptions`). Type alias `RefPolicies` kept. - Go: every struct field `Refs RefPolicies` → `RefPolicies RefPolicyList`. Type renamed from `RefPolicies` to `RefPolicyList` to avoid colliding with the new field name. - Python: every kwarg `refs: Optional[Refs]` → `ref_policies`. Type alias `Refs` kept. `_build_jwt_options` helper param renamed to match. The wire JWT claim remains `"refs"` on all three SDKs (verified in `encodeRefsClaim` / `encode_refs_claim` and the JWT-mint paths). No gateway-side change is needed. Also tidies em-dashes and clause-joining semicolons in deprecation comments and docstrings touched on this branch, and updates the code examples in `skills/code-storage/SKILL.md` to the new field name.
jcrosby
approved these changes
May 26, 2026
Contributor
Author
|
Merging as-is, happy to address post-merge feedback! |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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.Refs:
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)