Skip to content

feat(sdk): publish-ready @sanction/sdk@0.6.0 — escalate loop + npm workflow#169

Merged
ericlovold merged 2 commits into
mainfrom
feat/publish-sdk
Jul 11, 2026
Merged

feat(sdk): publish-ready @sanction/sdk@0.6.0 — escalate loop + npm workflow#169
ericlovold merged 2 commits into
mainfrom
feat/publish-sdk

Conversation

@ericlovold

@ericlovold ericlovold commented Jul 10, 2026

Copy link
Copy Markdown
Owner

Summary

  • Makes @sanction/sdk@0.6.0 publish-ready: public package metadata, FSL-1.1-MIT LICENSE (matches COMMERCIAL-LICENSE), build/prepublishOnly, and a manual publish-sdk workflow (idempotent, same NPM_TOKEN pattern as MCP).
  • Closes the client escalate→grant loop: getAuthorization(requestId) polls for the one-use grant; sanctionTool accepts grantId on retry.
  • Updates README, commercial license table, framework-adapters docs, changelog, and roadmap Next note. Python adapters stay Next.

Before merging / publishing

  • Confirm the sanction npm org exists and NPM_TOKEN can publish @sanction/*
  • After merge: run Actions → Publish SDK (workflow_dispatch)

Test plan

  • cd sdk && npm run typecheck && npm run build
  • npx vitest run sdk/src (47/47)
  • Dry-run: cd sdk && npm pack --dry-run and confirm dist/, LICENSE, README.md are included
  • After publish: npm view @sanction/sdk version0.6.0

Made with Cursor

Summary by CodeRabbit

  • New Features

    • Added authorization status polling for escalated requests, including approval, denial, and grant details.
    • Added grant-aware tool execution with retry support after approval.
    • Published TypeScript SDK version 0.6.0 with ESM, type declarations, and framework adapter support.
    • Added clearer fail-closed behavior and credential-scoping guidance.
  • Documentation

    • Expanded SDK usage, management, error-handling, installation, and adapter documentation.
    • Clarified SDK licensing and commercial-use terms.
  • Chores

    • Added an automated, duplicate-safe SDK publishing workflow.

ericlovold and others added 2 commits July 10, 2026 10:43
…rkflow

Un-private the TypeScript SDK under FSL-1.1-MIT, close the escalate→grant
client loop (getAuthorization + sanctionTool grantId), and add a manual
publish-sdk workflow mirroring sanction-mcp. Python adapters stay Next.

Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
@cursor

cursor Bot commented Jul 10, 2026

Copy link
Copy Markdown

Bugbot is not enabled for your account, so this pull request was not reviewed.

Enable Bugbot in the Cursor dashboard to get automatic reviews on future PRs.

@vercel

vercel Bot commented Jul 10, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
sanction Ready Ready Preview, Comment Jul 10, 2026 3:45pm

@coderabbitai

coderabbitai Bot commented Jul 10, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

The SDK adds authorization polling and grant-based retry support, updates package metadata and licensing for version 0.6.0, and introduces a manual npm publishing workflow. Documentation, roadmap, changelog, and distribution guidance are updated accordingly.

Changes

SDK publish and authorization flow

Layer / File(s) Summary
Authorization polling and grant retry
sdk/src/types.ts, sdk/src/client.ts, sdk/src/adapters.ts, sdk/src/*.test.ts, sdk/README.md
Adds AuthorizationStatus, SanctionClient.getAuthorization, optional grantId forwarding in sanctionTool, tests for polling and retry, and corresponding SDK usage documentation.
Public package metadata and publishing workflow
sdk/package.json, sdk/LICENSE, .github/workflows/publish-sdk.yml, .gitignore
Prepares version 0.6.0 for public publication, includes FSL-1.1-MIT licensing, ignores generated SDK files, and adds build, test, duplicate-version checks, and conditional npm publishing.
Distribution and release documentation
README.md, docs/*, lib/changelog.ts, lib/roadmap.ts
Updates installation guidance, adapter references, licensing details, backlog status, roadmap text, and changelog content for the publish-ready SDK.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Tool as sanctionTool
  participant Client as SanctionClient
  participant API as Authorization API
  Tool->>Client: authorizeTool with optional grantId
  Client->>API: POST /authorize/tool
  API-->>Client: escalated requestId
  Client->>API: GET /authorize/{requestId}
  API-->>Client: approved status and grantId
  Tool->>Client: retry with grantId
  Client->>API: authorizeTool with grant_id
Loading

Possibly related PRs

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the SDK publish readiness, version bump, escalate loop completion, and npm workflow additions.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/publish-sdk

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In @.github/workflows/publish-sdk.yml:
- Around line 37-42: Add a workflow-level concurrency configuration to serialize
manual publishes for this package, using a stable group identifier such as the
workflow name and package/ref, and configure appropriate cancellation behavior
so concurrent runs cannot both reach the npm publish logic.

In `@sdk/README.md`:
- Around line 42-46: The README example’s “poll until approved” wording is
inconsistent with its single getAuthorization call. Update the example around
sanction.getAuthorization to either implement bounded polling with backoff until
approval or timeout, or clearly describe it as one status check and instruct
callers to schedule subsequent checks before retrying with status.grantId.

In `@sdk/src/adapters.test.ts`:
- Around line 116-117: The test tool’s execute implementation does not accept
the argument passed by t.execute({}). Update the sanctionTool call in the
relevant test to define execute with a compatible input parameter while
preserving its "ok" result, so the inferred tool type matches the invocation.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 45f9db87-c219-40c3-9836-b4a90b2e75a2

📥 Commits

Reviewing files that changed from the base of the PR and between 0f6409a and b3bbac4.

📒 Files selected for processing (15)
  • .github/workflows/publish-sdk.yml
  • .gitignore
  • README.md
  • docs/BACKLOG.md
  • docs/COMMERCIAL-LICENSE.md
  • docs/FRAMEWORK-ADAPTERS.md
  • lib/changelog.ts
  • lib/roadmap.ts
  • sdk/LICENSE
  • sdk/README.md
  • sdk/package.json
  • sdk/src/adapters.test.ts
  • sdk/src/adapters.ts
  • sdk/src/client.ts
  • sdk/src/types.ts

Comment on lines +37 to +42
VERSION=$(node -p "require('./package.json').version")
if npm view "@sanction/sdk@$VERSION" version >/dev/null 2>&1; then
echo "@sanction/sdk@$VERSION already on npm — skipping npm publish"
else
npm publish
fi

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

Serialize publishes for this package.

Two manual runs can both pass npm view; one then fails on npm publish after the other wins. This contradicts the workflow’s idempotency claim. Add a workflow-level concurrency group.

Proposed fix
 name: Publish SDK
+
+concurrency:
+  group: publish-sanction-sdk
+  cancel-in-progress: false
🧰 Tools
🪛 zizmor (1.26.1)

[info] 39-39: prefer trusted publishing for authentication (use-trusted-publishing): this command

(use-trusted-publishing)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/publish-sdk.yml around lines 37 - 42, Add a workflow-level
concurrency configuration to serialize manual publishes for this package, using
a stable group identifier such as the workflow name and package/ref, and
configure appropriate cancellation behavior so concurrent runs cannot both reach
the npm publish logic.

Comment thread sdk/README.md
Comment on lines +42 to +46
// Poll until the owner approves, then retry with the grant:
const status = await sanction.getAuthorization(decision.requestId)
if (status.grantId) {
// retry authorize / authorizeTool with grantId: status.grantId
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Implement polling or describe this as a single status check.

This calls getAuthorization only once. A pending response remains escalated, so the example never reaches a later owner approval or retry. Add bounded polling/backoff, or explicitly tell callers to schedule another check.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@sdk/README.md` around lines 42 - 46, The README example’s “poll until
approved” wording is inconsistent with its single getAuthorization call. Update
the example around sanction.getAuthorization to either implement bounded polling
with backoff until approval or timeout, or clearly describe it as one status
check and instruct callers to schedule subsequent checks before retrying with
status.grantId.

Comment thread sdk/src/adapters.test.ts
Comment on lines +116 to +117
const t = sanctionTool(c, "deploy", { execute: async () => "ok" }, { server: "ci", grantId: "grant_1" })
expect(await t.execute({})).toBe("ok")

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/usr/bin/env bash
rg -n -C2 'execute:\s*async\s*\(\)\s*=>\s*"ok"|await t\.execute\(\{\}\)' sdk/src/adapters.test.ts

Repository: ericlovold/sanction

Length of output: 565


🏁 Script executed:

#!/usr/bin/env bash
set -euo pipefail

printf '\n== Files ==\n'
git ls-files 'sdk/src/*' | sed -n '1,200p'

printf '\n== Locate sanctionTool ==\n'
rg -n 'function sanctionTool|const sanctionTool|export .*sanctionTool|sanctionTool\(' sdk/src -g '!**/*.map'

printf '\n== Type signatures around adapter tests ==\n'
ast-grep outline sdk/src/adapters.test.ts --view expanded || true

printf '\n== Read relevant sources ==\n'
for f in sdk/src/adapters.ts sdk/src/adapters.test.ts sdk/src/*.ts; do
  [ -f "$f" ] || continue
  echo "--- $f ($(wc -l < "$f") lines) ---"
  sed -n '1,240p' "$f"
done

Repository: ericlovold/sanction

Length of output: 50377


🏁 Script executed:

#!/usr/bin/env bash
set -euo pipefail

printf '\n== package manifests ==\n'
for f in package.json sdk/package.json pnpm-workspace.yaml tsconfig.json sdk/tsconfig.json; do
  if [ -f "$f" ]; then
    echo "--- $f ---"
    sed -n '1,220p' "$f"
  fi
done

printf '\n== tsconfig/test inclusion ==\n'
rg -n '"include"|"exclude"|vitest|typecheck|tsc|check' package.json sdk/package.json tsconfig.json sdk/tsconfig.json -n

Repository: ericlovold/sanction

Length of output: 5207


🏁 Script executed:

#!/usr/bin/env bash
set -euo pipefail

printf '\n== package manifests ==\n'
for f in package.json sdk/package.json pnpm-workspace.yaml tsconfig.json sdk/tsconfig.json; do
  if [ -f "$f" ]; then
    echo "--- $f ---"
    sed -n '1,220p' "$f"
  fi
done

printf '\n== tsconfig/test inclusion ==\n'
rg -n '"include"|"exclude"|vitest|typecheck|tsc|check' package.json sdk/package.json tsconfig.json sdk/tsconfig.json

Repository: ericlovold/sanction

Length of output: 5207


🏁 Script executed:

#!/usr/bin/env bash
set -euo pipefail

node - <<'JS'
let ts;
try {
  ts = require('typescript');
} catch (e) {
  console.log("typescript module unavailable:", e.message);
  process.exit(0);
}

const source = `
type AiSdkToolLike = {
  description?: string
  parameters?: unknown
  execute?: (args: unknown, options?: unknown) => Promise<unknown> | unknown
  [k: string]: unknown
}
declare function sanctionTool<T extends AiSdkToolLike>(
  client: unknown,
  name: string,
  aiTool: T,
  opts?: { server?: string; grantId?: string },
): T

const t = sanctionTool({}, "deploy", { execute: async () => "ok" }, { server: "ci", grantId: "grant_1" })
t.execute({})
`;

const fileName = "probe.ts";
const options = {
  strict: true,
  target: ts.ScriptTarget.ES2022,
  module: ts.ModuleKind.ESNext,
  lib: ["lib.es2022.d.ts", "lib.dom.d.ts"],
  noEmit: true,
};

const host = ts.createCompilerHost(options);
host.getSourceFile = (f, lang) => {
  if (f === fileName) return ts.createSourceFile(f, source, lang, true, ts.ScriptKind.TS);
  return ts.createSourceFile(f, "", lang, true);
};
host.readFile = (f) => (f === fileName ? source : "");
host.fileExists = (f) => f === fileName;
host.writeFile = () => {};

const program = ts.createProgram([fileName], options, host);
const diags = ts.getPreEmitDiagnostics(program);

console.log("typescript version:", ts.version);
console.log("diagnostics:");
for (const d of diags) {
  const msg = ts.flattenDiagnosticMessageText(d.messageText, "\n");
  console.log(`- TS${d.code}: ${msg}`);
}

const checker = program.getTypeChecker();
const sf = program.getSourceFile(fileName);
function findVar(name) {
  let found;
  ts.forEachChild(sf, function visit(node) {
    if (ts.isVariableDeclaration(node) && node.name.getText(sf) === name) found = node;
    ts.forEachChild(node, visit);
  });
  return found;
}
const decl = findVar("t");
if (decl) {
  const tType = checker.getTypeAtLocation(decl.name);
  const exec = tType.getProperty("execute");
  if (exec) {
    const execType = checker.getTypeOfSymbolAtLocation(exec, decl.name);
    console.log("t.execute type:", checker.typeToString(execType));
  }
}
JS

Repository: ericlovold/sanction

Length of output: 3285


Make the test tool accept the argument it invokes with. In sdk/src/adapters.test.ts:116-117, sanctionTool preserves T, so the zero-argument execute here does not match t.execute({}) and breaks typechecking.

🧰 Tools
🪛 GitHub Actions: CI / 0_build.txt

[error] 117-117: TypeScript (tsc) error TS2554: Expected 0 arguments, but got 1.

🪛 GitHub Actions: CI / build

[error] 117-117: TypeScript (tsc --noEmit) error TS2554: Expected 0 arguments, but got 1.

🪛 GitHub Check: build

[failure] 117-117:
Expected 0 arguments, but got 1.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@sdk/src/adapters.test.ts` around lines 116 - 117, The test tool’s execute
implementation does not accept the argument passed by t.execute({}). Update the
sanctionTool call in the relevant test to define execute with a compatible input
parameter while preserving its "ok" result, so the inferred tool type matches
the invocation.

Source: Linters/SAST tools

@ericlovold
ericlovold merged commit 985c88f into main Jul 11, 2026
5 of 7 checks passed
ericlovold added a commit that referenced this pull request Jul 12, 2026
…#193)

Bump 0.6.0 → 0.7.0 and drain the truth surfaces (truthsync folded into the
cut). The pack since v0.6.0: pay-per-crawl sanctionedFetch (#183),
@sanction/sdk publish-ready (#169), Sanction Local install package (#168,
#184), observe mode (#187) + observe digest & subtree rollup (#192),
email-lands-on-the-decision (#190).

Changelog gaps filled (verified against code, not the draft):
- v0.7.0 release header (theme: adoption — watch-first, install, drop-in).
- Observe mode entry (OBS-1 + OBS-2): the do-nothing on-ramp — real engine
  records the would-be outcome, blocks nothing, flip-to-enforce per pool with
  a revisioned timestamp; org roots read the whole subtree.
- Email-on-decision entry (#190): escalation notices deep-link to the exact
  pending request (approveUrlFor), not the generic inbox.

Roadmap rotated: Local install → shipped phrasing; observe-mode adoption
added to Now as shipped; arc comment updated. (Demo companies, consulting
page, and the skills work are infra/site/tooling — no product-changelog
entry, by design.)

Diff is package.json + changelog + roadmap only. Isolated branch — does not
touch the concurrent MCP-demo work.


Claude-Session: https://claude.ai/code/session_01UEw1RdHk5bmphKxr6N5mJY

Co-authored-by: Claude <noreply@anthropic.com>
@ericlovold
ericlovold deleted the feat/publish-sdk branch July 13, 2026 04:24
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant