Files
paperclip/packages/plugins/sdk/tests/testing-actions.test.ts
T
Dotta a1835cfa5e [codex] Harden plugin runtime invocation scope (#6547)
## Thinking Path

> - Paperclip orchestrates AI-agent companies through a company-scoped
control plane.
> - Plugins extend that control plane, but plugin workers still call
back into host APIs.
> - Those worker-to-host calls need the same company boundary guarantees
as normal API routes.
> - Plugin action handlers also need authenticated actor context from
the host instead of trusting caller-supplied params.
> - This pull request hardens plugin bridge/action scope and keeps
plugin operation issues out of normal issue surfaces.
> - The benefit is safer plugin execution with clearer authorization
boundaries and better test coverage.

## What Changed

- Added host-owned invocation context plumbing for nested plugin worker
calls.
- Added actor context to plugin `performAction` calls and test harness
helpers.
- Enforced company invocation scope on worker-to-host calls and filtered
company lists to the active invocation scope.
- Extended plugin action route tests for board and agent actor context,
spoofed company params, and cross-company rejection.
- Extended plugin worker manager coverage for invocation-scope
propagation.
- Filtered typed and legacy plugin operation issue origins from default
issue/inbox lists.

## Verification

- `pnpm --filter @paperclipai/plugin-sdk build`
- `NODE_ENV=test pnpm exec vitest run
packages/plugins/sdk/tests/host-client-factory.test.ts
packages/plugins/sdk/tests/testing-actions.test.ts
server/src/__tests__/plugin-routes-authz.test.ts
server/src/__tests__/plugin-worker-manager.test.ts
server/src/__tests__/issues-service.test.ts`

Note: embedded Postgres issue-service tests reported host-level Postgres
init skip for 47 tests; the non-embedded targeted tests passed.

## Risks

- Medium: plugin host authorization paths are sensitive, and external
plugins may rely on previously loose company params.
- Mitigation: the change only tightens calls when the host attached a
company invocation scope and includes explicit tests for board, agent,
and nested worker calls.

> 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 via `codex_local`, tool-enabled coding session;
exact context window not exposed by this runtime.

## 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 run tests locally and they pass
- [x] I have added or updated tests where applicable
- [x] If this change affects the UI, I have included before/after
screenshots
- [x] I have updated relevant documentation to reflect my changes
- [x] I have considered and documented any risks above
- [x] I will address all Greptile and reviewer comments before
requesting merge
2026-05-22 09:16:24 -05:00

75 lines
2.2 KiB
TypeScript

import { describe, expect, it } from "vitest";
import { createTestHarness } from "../src/testing.js";
import type { PaperclipPluginManifestV1 } from "../src/types.js";
const manifest = {
id: "paperclip.test-actions",
apiVersion: 1,
version: "1.0.0",
displayName: "Test Actions",
description: "Test plugin",
author: "Paperclip",
categories: ["automation"],
capabilities: [],
entrypoints: {},
} satisfies PaperclipPluginManifestV1;
describe("createTestHarness action context", () => {
it("passes immutable authenticated actor context and overrides caller company scope", async () => {
const harness = createTestHarness({ manifest });
harness.ctx.actions.register("inspect", async (params, context) => ({
paramsCompanyId: params.companyId,
actor: context.actor,
companyId: context.companyId,
contextFrozen: Object.isFrozen(context),
actorFrozen: Object.isFrozen(context.actor),
}));
const result = await harness.performAction<{
paramsCompanyId: unknown;
actor: {
type: string;
userId: string | null;
agentId: string | null;
runId: string | null;
companyId: string | null;
};
companyId: string | null;
contextFrozen: boolean;
actorFrozen: boolean;
}>(
"inspect",
{ companyId: "spoofed-company", value: true },
{
companyId: "host-company",
actor: {
type: "user",
userId: "board-user-1",
runId: "run-1",
},
},
);
expect(result.paramsCompanyId).toBe("host-company");
expect(result.companyId).toBe("host-company");
expect(result.actor).toEqual({
type: "user",
userId: "board-user-1",
agentId: null,
runId: "run-1",
companyId: "host-company",
});
expect(result.contextFrozen).toBe(true);
expect(result.actorFrozen).toBe(true);
});
it("keeps existing one-argument action handlers compatible", async () => {
const harness = createTestHarness({ manifest });
harness.ctx.actions.register("legacy", async (params) => ({ ok: params.ok }));
await expect(harness.performAction("legacy", { ok: true })).resolves.toEqual({ ok: true });
});
});