mirror of
https://github.com/paperclipai/paperclip.git
synced 2026-10-23 17:33:51 +02:00
## Thinking Path > - Paperclip is the open source app people use to manage AI agents for work > - Connections is the subsystem that lets operators connect external apps and govern which subjects may use those credentials > - #9958 established the v3 schema foundation and #9981 adds the AppDefinition catalog layer > - The runtime still needs subject-aware authorization state, scoped key handling, and API/OpenAPI routes so connected apps can actually be granted and used safely > - This pull request adds the runtime grants/authorization behavior on top of the catalog branch, while keeping unrelated dependency and workflow sync commits out of the stack > - The benefit is a reviewable runtime layer that can land after the catalog PR, then unblock the wizard and orchestrator cutover work ## Linked Issues or Issue Description Refs #9958 and #9981. Refs #9981. No public GitHub issue exists for this branch. This is the runtime layer for the Connections v3 stack and is rebased onto `master` after #9981 landed. ## What Changed - Adds the connection user authorization state migration and schema wiring. - Adds shared runtime subject/grant types and validators. - Adds runtime grant and scoped key behavior in the tool-access service. - Adds runtime route coverage and registers the routes in OpenAPI. - Replays only the Connections runtime commits on top of the catalog branch, dropping unrelated sync/dependency history from the prior closed runtime PR. ## Verification - `pnpm run preflight:workspace-links` - `pnpm exec vitest run packages/shared/src/validators/tool-access.test.ts server/src/__tests__/tool-access-service.test.ts` ## Risks - Medium: runtime grant enforcement is security-sensitive and must fail closed for unknown key scopes. - Migration ordering depends on the schema and catalog layers already merged through #9958 and #9981. - This PR is rebased and retargeted to `master` with runtime-only commits. > For core feature work, check [`ROADMAP.md`](ROADMAP.md) first and discuss it in `#dev` before opening the PR. Feature PRs that overlap with planned core work may need to be redirected — check the roadmap first. See `CONTRIBUTING.md`. ## Model Used OpenAI GPT-5 Codex coding agent with repository tool use. ## Checklist - [x] I have included a thinking path that traces from project context to this change - [x] I have specified the model used (with version and capability details) - [x] I have checked ROADMAP.md and confirmed this PR does not duplicate planned core work - [x] I have searched GitHub for duplicate or related PRs and linked them above - [x] I have either (a) linked existing issues with `Fixes: #` / `Closes #` / `Refs #` OR (b) described the issue in-PR following the relevant issue template - [x] I have not referenced internal/instance-local Paperclip issues or links (only public GitHub `#NNN` / `github.com/paperclipai/paperclip` URLs) - [x] My branch name describes the change (e.g. `docs/...`, `fix/...`) and contains no internal Paperclip ticket id or instance-derived details - [x] I have run tests locally and they pass - [x] I have added or updated tests where applicable - [x] I have updated relevant documentation to reflect my changes - [x] I have considered and documented any risks above - [x] All Paperclip CI gates are green - [x] Greptile is 5/5 with no open P2s, recommendations, or follow-ups - [x] I will address all Greptile and reviewer comments before requesting merge --------- Co-authored-by: Paperclip <noreply@paperclip.ing>
79 lines
2.6 KiB
TypeScript
79 lines
2.6 KiB
TypeScript
import { describe, expect, it } from "vitest";
|
|
import {
|
|
connectionTokenRequestSchema,
|
|
createToolConnectionSchema,
|
|
startConnectionAuthorizationSchema,
|
|
toolCredentialSecretRefSchema,
|
|
toolRedactedValueSummarySchema,
|
|
toolTransportConfigSchema,
|
|
} from "./tool-access.js";
|
|
|
|
describe("tool access validators", () => {
|
|
it("defaults connection token subjects to app", () => {
|
|
expect(connectionTokenRequestSchema.parse({})).toEqual({ subject: { type: "app" } });
|
|
});
|
|
|
|
it("accepts user subjects, grant selection, and authorization input", () => {
|
|
const request = connectionTokenRequestSchema.parse({
|
|
subject: { type: "user", userId: "user-123" },
|
|
grantId: "11111111-1111-4111-8111-111111111111",
|
|
});
|
|
expect(request.subject).toEqual({ type: "user", userId: "user-123" });
|
|
expect(startConnectionAuthorizationSchema.parse({ subjectUserId: "user-123", scopes: ["read"] })).toEqual({
|
|
subjectUserId: "user-123",
|
|
scopes: ["read"],
|
|
});
|
|
});
|
|
|
|
it("accepts multi-key credential annotations", () => {
|
|
const parsed = toolCredentialSecretRefSchema.parse({
|
|
secretId: "11111111-1111-4111-8111-111111111111",
|
|
configPath: "credentials.apiKey",
|
|
keyScope: "production",
|
|
expiresAt: "2027-01-01T00:00:00Z",
|
|
});
|
|
expect(parsed.keyScope).toBe("production");
|
|
});
|
|
it("rejects raw credential-looking fields in transport config", () => {
|
|
const parsed = toolTransportConfigSchema.safeParse({
|
|
url: "https://example.test/mcp",
|
|
headers: {
|
|
Authorization: "Bearer raw-token",
|
|
},
|
|
});
|
|
|
|
expect(parsed.success).toBe(false);
|
|
if (!parsed.success) {
|
|
expect(parsed.error.issues[0]?.message).toContain("credentialSecretRefs");
|
|
}
|
|
});
|
|
|
|
it("accepts secret references for connection credentials", () => {
|
|
const parsed = createToolConnectionSchema.safeParse({
|
|
applicationId: "11111111-1111-4111-8111-111111111111",
|
|
name: "GitHub fixture",
|
|
connectionKind: "managed",
|
|
transportConfig: { url: "https://example.test/mcp" },
|
|
credentialSecretRefs: [
|
|
{
|
|
secretId: "22222222-2222-4222-8222-222222222222",
|
|
configPath: "headers.Authorization",
|
|
versionSelector: "latest",
|
|
},
|
|
],
|
|
});
|
|
|
|
expect(parsed.success).toBe(true);
|
|
});
|
|
|
|
it("keeps invocation payload summaries redacted and bounded", () => {
|
|
const parsed = toolRedactedValueSummarySchema.parse({
|
|
summary: "Redacted arguments: 2 fields omitted.",
|
|
sha256: "a".repeat(64),
|
|
redactedFields: ["headers.Authorization", "body.token"],
|
|
});
|
|
|
|
expect(parsed.redactedFields).toEqual(["headers.Authorization", "body.token"]);
|
|
});
|
|
});
|