test: exercise Subject permission management in Chromium
This commit is contained in:
@@ -0,0 +1,187 @@
|
||||
/**
|
||||
* Synthetic HTTP state for T-802 browser UX only. NOT the real Backend,
|
||||
* authentication, authorization, operation catalog, or cancellation implementation.
|
||||
* Keep real Backend integration evidence separate from these browser fixtures.
|
||||
*/
|
||||
export const permissionFixtureSubjects = [
|
||||
["permissions-empty", "New Subject — permissions not configured"],
|
||||
["permissions-edit", "Release coordination"],
|
||||
["permissions-legacy", "Subject with preserved legacy permissions"],
|
||||
["permissions-reader", "Read-only Subject permissions"],
|
||||
["permissions-unavailable", "Unavailable Subject permissions"],
|
||||
] as const;
|
||||
|
||||
export type FixturePermission = {
|
||||
operation: string;
|
||||
resource: Record<string, string | boolean> & { kind: string };
|
||||
};
|
||||
export type FixturePolicy = {
|
||||
change_id: string;
|
||||
execution: FixturePermission[];
|
||||
delegation: FixturePermission[];
|
||||
};
|
||||
type FixtureUpdate = Omit<FixturePolicy, "change_id"> & {
|
||||
operation_id: string;
|
||||
expected_change_id: string | null;
|
||||
};
|
||||
|
||||
const workspaceId = "memory-review";
|
||||
const resource = { kind: "ticket", workspace_id: workspaceId, ticket_id: "T-802" };
|
||||
export const fixtureLegacyPermission: FixturePermission = {
|
||||
operation: "worker.notify",
|
||||
resource: { kind: "worker", runtime_id: "legacy-runtime", worker_id: "legacy-recipient" },
|
||||
};
|
||||
// Deliberately a small synthetic catalog, not a copy/second authority for production.
|
||||
export const fixturePermissionOperations: {
|
||||
operation: string;
|
||||
label: string;
|
||||
description: string;
|
||||
resource_kinds: string[];
|
||||
availability: { kind: "available" } | { kind: "unavailable"; reason: string };
|
||||
}[] = [
|
||||
{
|
||||
operation: "ticket.read",
|
||||
label: "Read ticket",
|
||||
description: "Read the selected ticket. Does not allow editing or closing it.",
|
||||
resource_kinds: ["ticket"],
|
||||
availability: { kind: "available" },
|
||||
},
|
||||
{
|
||||
operation: "ticket.comment",
|
||||
label: "Comment on ticket",
|
||||
description: "Append a comment to the selected ticket; no completion or assignment authority.",
|
||||
resource_kinds: ["ticket"],
|
||||
availability: { kind: "available" },
|
||||
},
|
||||
{
|
||||
operation: "worker.messaging",
|
||||
label: "Message a Worker (T-803)",
|
||||
description:
|
||||
"Operation use is separate from Backend directional target control grants and execution conditions.",
|
||||
resource_kinds: [],
|
||||
availability: {
|
||||
kind: "unavailable",
|
||||
reason: "T-803 is not implemented in this synthetic browser fixture.",
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
function json(value: unknown, status = 200): Response {
|
||||
return Response.json(value, {
|
||||
status,
|
||||
headers: { "x-yoi-evidence": "synthetic-http-not-real-backend" },
|
||||
});
|
||||
}
|
||||
function error(message: string, status: number): Response {
|
||||
return json({ error: "Synthetic fixture response", message }, status);
|
||||
}
|
||||
function isUpdate(value: unknown): value is FixtureUpdate {
|
||||
if (!value || typeof value !== "object") return false;
|
||||
const v = value as Record<string, unknown>;
|
||||
return typeof v.operation_id === "string" && v.operation_id.length > 0 &&
|
||||
(v.expected_change_id === null || typeof v.expected_change_id === "string") &&
|
||||
Array.isArray(v.execution) && Array.isArray(v.delegation) &&
|
||||
[...v.execution, ...v.delegation].every((p) =>
|
||||
p && typeof p.operation === "string" && p.resource && typeof p.resource.kind === "string"
|
||||
);
|
||||
}
|
||||
|
||||
export function createSubjectPermissionsFixture() {
|
||||
const policies = new Map<string, FixturePolicy | null>();
|
||||
const receipts = new Map<string, { input: string; permissions: FixturePolicy }>();
|
||||
const requests: { subject_id: string; request: FixtureUpdate; outcome: string }[] = [];
|
||||
let issuedChangeCount = 0;
|
||||
for (const [id] of permissionFixtureSubjects) {
|
||||
policies.set(
|
||||
id,
|
||||
id === "permissions-empty" ? null : {
|
||||
change_id: `fixture-initial-${id}`,
|
||||
execution: [{ operation: "ticket.read", resource: { ...resource } }],
|
||||
delegation: id === "permissions-legacy" ? [structuredClone(fixtureLegacyPermission)] : [],
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
return async (request: Request): Promise<Response | null> => {
|
||||
const url = new URL(request.url);
|
||||
if (url.pathname === "/api/auth/whoami") {
|
||||
// Exercise the real Web's admission gate with a synthetic HTTP session projection.
|
||||
// No cookies/tokens or real Backend session are created or bypassed.
|
||||
return json({
|
||||
actor: {
|
||||
user_id: "synthetic-owner",
|
||||
account_id: "owner",
|
||||
handle: "fixture-owner",
|
||||
display_name: "Synthetic browser owner",
|
||||
auth_method: "browser_session",
|
||||
},
|
||||
session: { kind: "authenticated", expires_at: "2099-01-01T00:00:00Z" },
|
||||
authentication_reference: "synthetic-http-not-real-authentication",
|
||||
});
|
||||
}
|
||||
if (url.pathname === "/fixture/permissions/evidence") {
|
||||
if (request.method !== "GET") return error("GET required", 405);
|
||||
return json({ authority: "synthetic-http-not-real-backend", requests });
|
||||
}
|
||||
const match = url.pathname.match(
|
||||
/^\/api\/w\/([^/]+)\/subjektiv\/subjects\/([^/]+)\/permissions$/,
|
||||
);
|
||||
if (!match) return null;
|
||||
const [, workspace, id] = match.map(decodeURIComponent);
|
||||
if (workspace !== workspaceId || !policies.has(id)) return error("Subject unavailable", 404);
|
||||
if (id === "permissions-unavailable") return error("Permission service unavailable", 503);
|
||||
const current = policies.get(id)!;
|
||||
if (request.method === "GET") {
|
||||
return json({
|
||||
permissions: current,
|
||||
can_manage: id !== "permissions-reader",
|
||||
operations: fixturePermissionOperations,
|
||||
});
|
||||
}
|
||||
if (request.method !== "POST") return error("POST required", 405);
|
||||
if (id === "permissions-reader") return error("Actor cannot manage permissions", 403);
|
||||
const payload: unknown = await request.json().catch(() => null);
|
||||
if (!isUpdate(payload)) return error("Invalid synthetic policy update", 400);
|
||||
const input = JSON.stringify(payload);
|
||||
const receiptKey = JSON.stringify([id, payload.operation_id]);
|
||||
const previous = receipts.get(receiptKey);
|
||||
if (previous) {
|
||||
requests.push({ subject_id: id, request: payload, outcome: "replay" });
|
||||
if (previous.input !== input) {
|
||||
return error("Operation identity reused with changed input", 409);
|
||||
}
|
||||
return json({ permissions: previous.permissions, interruptions: [] });
|
||||
}
|
||||
if (payload.expected_change_id !== (current?.change_id ?? null)) {
|
||||
requests.push({ subject_id: id, request: payload, outcome: "conflict" });
|
||||
return error("Policy changed in another browser; reload and review", 409);
|
||||
}
|
||||
// Reject only the combinations used by this fixture. This is NOT Backend validation evidence.
|
||||
for (const p of [...payload.execution, ...payload.delegation]) {
|
||||
const legacy = JSON.stringify(p) === JSON.stringify(fixtureLegacyPermission) &&
|
||||
id === "permissions-legacy";
|
||||
const supported = fixturePermissionOperations.some((o) =>
|
||||
o.availability.kind === "available" && o.operation === p.operation &&
|
||||
o.resource_kinds.includes(p.resource.kind)
|
||||
);
|
||||
if (!legacy && !supported) {
|
||||
return error("Unknown operation or invalid resource combination", 400);
|
||||
}
|
||||
if (
|
||||
p.resource.kind === "ticket" &&
|
||||
(p.resource.workspace_id !== workspaceId || !p.resource.ticket_id)
|
||||
) {
|
||||
return error("Invalid ticket target", 400);
|
||||
}
|
||||
}
|
||||
const saved: FixturePolicy = {
|
||||
change_id: `fixture-change-${++issuedChangeCount}`,
|
||||
execution: structuredClone(payload.execution),
|
||||
delegation: structuredClone(payload.delegation),
|
||||
};
|
||||
policies.set(id, saved);
|
||||
receipts.set(receiptKey, { input, permissions: structuredClone(saved) });
|
||||
requests.push({ subject_id: id, request: payload, outcome: "saved" });
|
||||
return json({ permissions: saved, interruptions: [] });
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user