fix: resolve feedback discovery and submission review findings

Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
DottaandPaperclip committed 2026-10-06 12:54:34 -05:00
1 parent 3dafe3a9a4
commit 169b7fc3f4
9 files changed
+46 -19

No files matched your search

@@ -294,9 +294,9 @@
"kind": "skill_heading",
"sourceAnchor": "skills/paperclip/SKILL.md#L754:incidental-feedback",
"heading": "Incidental feedback",
"primaryDisposition": "control_plane_owned",
"semanticOperation": "runtime_reconciliation",
"expectedMockState": "runtime_decision_record"
"primaryDisposition": "optional_agent_tool",
"semanticOperation": "scoped_discovery",
"expectedMockState": "operation_result"
},
{
"id": "skill:skills/paperclip/references/api-reference.md:1",
@@ -5,6 +5,6 @@ Generated by `scripts/generate-capability-contract.mjs`; do not edit generated f
- Skill/reference headings: 163
- Legacy MCP tools: 42
- Eval cases: 106 across 16 groups
- Deterministic content SHA-256: `464784a4473655cd5adee194d8b72964b2200848db6e19ae6b4e3f0cb0b4fe35`
- Deterministic content SHA-256: `3f4939bc27abd5311caf243044f0b2d4ec36a8d27d4eaf34fed7a0738c734c1d`
Every row has exactly one primary disposition, a source anchor, a semantic operation, and a mock-state expectation.
@@ -26,6 +26,7 @@ function sourceAnchor(path, line, heading) {
function classifyHeading(path, heading) {
const normalized = heading.toLowerCase();
if (normalized === "conversational confirmation answers") return "always_agent_tool";
if (normalized === "incidental feedback") return "optional_agent_tool";
if (/(authentication|identity|checkout|budget|error|wake|heartbeat|approval follow-up|activity|audit|release|terminology)/.test(normalized)) {
return "control_plane_owned";
}
@@ -39,6 +39,7 @@ const NAMESPACE: Readonly<Record<CapabilitySemanticOperationId, string>> = Objec
decide_approval: "governance", comment_on_approval: "governance",
get_workspace_runtime: "workspace", control_workspace_service: "workspace",
schedule_wake: "continuation", generic_api_request: "test_infrastructure",
submit_complaint: "feedback", submit_suggestion: "feedback",
});
export const CAPABILITY_DISCOVERY_NAMESPACES = Object.freeze([
@@ -48,6 +49,7 @@ export const CAPABILITY_DISCOVERY_NAMESPACES = Object.freeze([
{ name: "governance", description: "Read, request, discuss, and decide approvals." },
{ name: "workspace", description: "Inspect and control active-task workspace services." },
{ name: "continuation", description: "Schedule a bounded continuation wake." },
{ name: "feedback", description: "Submit internal complaints and improvement suggestions." },
]);
export const CAPABILITY_DISCOVERY_GATEWAY_DEFINITIONS = Object.freeze([{
@@ -165,6 +165,20 @@ describe("Capability semantic catalog and authorization", () => {
expect(JSON.stringify(found)).not.toContain("list_approvals");
});
it("supports unnamespaced discovery and finds both feedback tools", async () => {
const claims = ["discovery:agents:read"];
const adapter = await running(claims);
const dispatcher = new CapabilitySemanticDispatcher(adapter, {
scenario: { id: "feedback-discovery", claims }, explicitClaims: claims,
});
const agents = dispatcher.discoverTools(OPEN.identity.runId, "find company agents");
expect(agents.operations.map((tool) => tool.name)).toEqual(expect.arrayContaining(["list_agents", "get_agent"]));
for (const options of [{}, { namespace: "feedback" }]) {
const feedback = dispatcher.discoverTools(OPEN.identity.runId, "feedback", options);
expect(feedback.operations.map((tool) => tool.name)).toEqual(["submit_complaint", "submit_suggestion"]);
}
});
it("does not disclose optional tools that current authority cannot invoke", async () => {
const adapter = await running();
const dispatcher = new CapabilitySemanticDispatcher(adapter);
@@ -56,6 +56,8 @@ export type CapabilitySemanticOperationId =
| "decide_approval"
| "comment_on_approval"
| "schedule_wake"
| "submit_complaint"
| "submit_suggestion"
| "generic_api_request";
export interface CapabilityJsonSchema {
+19 -15
View File
@@ -20,18 +20,7 @@ import { ensureNativeCompletionContract } from "../src/services/native-runtime/c
import { redactSensitiveText } from "../src/redaction.js";
const root = await mkdtemp(join(tmpdir(), "paperclip-commentary-live-"));
const authSource = process.env.PAPERCLIP_LIVE_CODEX_HOME ?? process.env.CODEX_HOME ?? join(homedir(), ".codex");
const codexHome = join(root, "codex");
await mkdir(codexHome, { mode: 0o700 });
await copyFile(join(authSource, "auth.json"), join(codexHome, "auth.json"));
await writeFile(join(codexHome, "config.toml"), 'model_reasoning_effort = "low"\n');
process.env.PAPERCLIP_HOME = join(root, "instance");
process.env.PAPERCLIP_AGENT_JWT_SECRET = randomUUID();
process.env.PAPERCLIP_CODEX_AUTH_CACHE = "false";
process.env.PAPERCLIP_TELEMETRY_DISABLED = "1";
const server = await startRunnerApiTestServer();
process.env.PAPERCLIP_API_URL = server.apiUrl;
const evidence: unknown[] = [];
let server: Awaited<ReturnType<typeof startRunnerApiTestServer>> | undefined;
async function makeRemovable(path: string): Promise<void> {
const stat = await lstat(path);
if (stat.isSymbolicLink()) return;
@@ -41,6 +30,18 @@ async function makeRemovable(path: string): Promise<void> {
}
}
try {
const authSource = process.env.PAPERCLIP_LIVE_CODEX_HOME ?? process.env.CODEX_HOME ?? join(homedir(), ".codex");
const codexHome = join(root, "codex");
await mkdir(codexHome, { mode: 0o700 });
await copyFile(join(authSource, "auth.json"), join(codexHome, "auth.json"));
await writeFile(join(codexHome, "config.toml"), 'model_reasoning_effort = "low"\n');
process.env.PAPERCLIP_HOME = join(root, "instance");
process.env.PAPERCLIP_AGENT_JWT_SECRET = randomUUID();
process.env.PAPERCLIP_CODEX_AUTH_CACHE = "false";
process.env.PAPERCLIP_TELEMETRY_DISABLED = "1";
server = await startRunnerApiTestServer();
process.env.PAPERCLIP_API_URL = server.apiUrl;
const evidence: unknown[] = [];
for (const mode of ["legacy", "native"] as const) {
if (process.argv[2] && process.argv[2] !== mode) continue;
const f = await server.fixture({ disableWakeOnDemand: true });
@@ -104,7 +105,10 @@ try {
}
console.log(JSON.stringify({ verifiedAt: new Date().toISOString(), evidence }, null, 2));
} finally {
await server.close();
await makeRemovable(root);
await rm(root, { recursive: true, force: true });
try {
await server?.close();
} finally {
await makeRemovable(root);
await rm(root, { recursive: true, force: true });
}
}
@@ -226,6 +226,8 @@ describe("internal agent commentary through both transports", () => {
});
}
expect(await run(server.apiUrl)).toEqual({ code: 0, out: "Feedback stored.\n", err: "" });
expect(await run(server.apiUrl + "/api/")).toEqual({ code: 0, out: "Feedback stored.\n", err: "" });
expect(await rows(f)).toHaveLength(1);
expect((await rows(f))[0].body).toBe(body);
const failed = await run("http://127.0.0.1:1");
expect(failed).toMatchObject({ code: 0, out: "" });
@@ -18,6 +18,8 @@ def main():
if len(raw) > 4 * 524288 or not body.strip() or len(body.encode("utf-16-le")) // 2 > 524288:
raise ValueError()
api_url = os.environ["PAPERCLIP_API_URL"].rstrip("/")
if api_url.endswith("/api"):
api_url = api_url[:-4]
company_id = str(uuid.UUID(os.environ["PAPERCLIP_COMPANY_ID"]))
run_id = str(uuid.UUID(os.environ["PAPERCLIP_RUN_ID"]))
key = sys.argv[2] if len(sys.argv) > 2 else str(uuid.uuid4())