mirror of
https://github.com/paperclipai/paperclip.git
synced 2026-11-11 05:11:49 +01:00
Preserve consent gate service failures
Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
1 parent
9ec4f2a25a
commit
badd887aad
2 files changed
+34
-3
No files matched your search
@@ -1234,6 +1234,33 @@ describe("company skill mutation permissions", () => {
|
||||
expect(mockCompanySkillService.createLocalSkill).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("does not convert consent gate service failures into authorization denials", async () => {
|
||||
mockAccessService.decide.mockResolvedValue(denySkillChangeDecision(
|
||||
"deny_missing_consent",
|
||||
"Permission skills:suggest-changes requires accepted change consent before applying this mutation.",
|
||||
));
|
||||
mockReflectionCoachMutationGate.assertConsented.mockRejectedValue(new Error("database unavailable"));
|
||||
|
||||
const res = await request(await createApp({
|
||||
type: "agent",
|
||||
agentId: "reflection-coach",
|
||||
companyId: "company-1",
|
||||
runId: "run-apply",
|
||||
}))
|
||||
.post("/api/companies/company-1/skills")
|
||||
.send({ name: "Reflection Draft", slug: "reflection-draft", markdown: "# Draft" });
|
||||
|
||||
expect(res.status, JSON.stringify(res.body)).toBe(500);
|
||||
expect(res.body.error).toBe("Internal server error");
|
||||
expect(mockReflectionCoachMutationGate.assertConsented).toHaveBeenCalledWith({
|
||||
companyId: "company-1",
|
||||
actorAgentId: "reflection-coach",
|
||||
actorRunId: "run-apply",
|
||||
targetKeys: ["skill-slug:reflection-draft"],
|
||||
});
|
||||
expect(mockCompanySkillService.createLocalSkill).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("allows suggest-tier skill mutations after accepted change consent", async () => {
|
||||
mockAccessService.decide
|
||||
.mockResolvedValueOnce(denySkillChangeDecision(
|
||||
|
||||
@@ -31,9 +31,10 @@ import {
|
||||
listCatalogSkillsOrEmpty,
|
||||
readCatalogSkillFile,
|
||||
} from "../services/skills-catalog.js";
|
||||
import { forbidden } from "../errors.js";
|
||||
import { forbidden, HttpError } from "../errors.js";
|
||||
import { assertAuthenticated, assertCompanyAccess, getActorInfo } from "./authz.js";
|
||||
import { getTelemetryClient } from "../telemetry.js";
|
||||
import { authorizationDeniedDetails } from "../services/authorization.js";
|
||||
import {
|
||||
changeConsentGateService,
|
||||
skillChangeTargetKey,
|
||||
@@ -139,8 +140,11 @@ export function companySkillRoutes(db: Db) {
|
||||
actorRunId: req.actor.runId ?? null,
|
||||
targetKeys,
|
||||
});
|
||||
} catch {
|
||||
throw forbidden(decision.explanation, { reason: decision.reason });
|
||||
} catch (err) {
|
||||
if (err instanceof HttpError && err.status === 403) {
|
||||
throw forbidden(decision.explanation, authorizationDeniedDetails(decision));
|
||||
}
|
||||
throw err;
|
||||
}
|
||||
|
||||
const consentedDecision = await access.decide({
|
||||
|
||||
Reference in new issue
Block a user