fix: align permission management with integrated Worker and chat operations
This commit is contained in:
@@ -46,7 +46,11 @@ afterEach(() => {
|
||||
vi.unstubAllGlobals();
|
||||
sessionStorage.clear();
|
||||
});
|
||||
function harness(initial: Policy | null = null, canManage = true) {
|
||||
function harness(
|
||||
initial: Policy | null = null,
|
||||
canManage = true,
|
||||
operations = ops,
|
||||
) {
|
||||
let policy = initial;
|
||||
const requests: PolicyUpdate[] = [];
|
||||
const fetch = vi.fn(async (_path: string, init?: RequestInit) => {
|
||||
@@ -63,7 +67,7 @@ function harness(initial: Policy | null = null, canManage = true) {
|
||||
return Response.json({
|
||||
permissions: policy,
|
||||
can_manage: canManage,
|
||||
operations: ops,
|
||||
operations,
|
||||
});
|
||||
});
|
||||
vi.stubGlobal("fetch", fetch);
|
||||
@@ -262,3 +266,30 @@ test("deleted Subject is refused with no success indication", async () => {
|
||||
expect(screen.queryByRole("combobox")).toBeNull();
|
||||
expect(h.requests).toHaveLength(0);
|
||||
});
|
||||
test("current Worker operation use saves in both lanes without a target editor", async () => {
|
||||
const available = ops.map((o) =>
|
||||
o.operation === "worker.messaging"
|
||||
? {
|
||||
...o,
|
||||
resource_kinds: ["worker_operation"],
|
||||
availability: { kind: "available" },
|
||||
}
|
||||
: o
|
||||
);
|
||||
const h = harness(null, true, available);
|
||||
render(SubjectPermissions, props);
|
||||
await add("execution", "worker.messaging");
|
||||
await add("delegation", "worker.messaging");
|
||||
expect(screen.queryByRole("textbox", { name: "Worker identifier" }))
|
||||
.toBeNull();
|
||||
expect(screen.queryByRole("textbox", { name: "Runtime identifier" }))
|
||||
.toBeNull();
|
||||
await confirmSave();
|
||||
await screen.findByText("Saved and re-read from the current policy.");
|
||||
const permission = {
|
||||
operation: "worker.messaging",
|
||||
resource: { kind: "worker_operation", workspace_id: "w" },
|
||||
};
|
||||
expect(h.requests[0].execution).toEqual([permission]);
|
||||
expect(h.requests[0].delegation).toEqual([permission]);
|
||||
});
|
||||
|
||||
@@ -147,3 +147,38 @@ Deno.test("read permission projection uses the existing scoped endpoint and no f
|
||||
view.operations.length === 0,
|
||||
);
|
||||
});
|
||||
|
||||
Deno.test("Worker operation editor binds operation use without a target and preserves installed Plugin scopes", () => {
|
||||
const resource = makeResource("worker_operation", "w", "s", {}, false);
|
||||
assert(
|
||||
JSON.stringify(resource) ===
|
||||
JSON.stringify({ kind: "worker_operation", workspace_id: "w" }),
|
||||
);
|
||||
const view = parseView({
|
||||
permissions: {
|
||||
change_id: "saved",
|
||||
execution: [{ operation: "worker.messaging", resource }],
|
||||
delegation: [],
|
||||
},
|
||||
can_manage: true,
|
||||
operations: [{
|
||||
operation: "worker.messaging",
|
||||
label: "Message Workers",
|
||||
description: "Directional target grants remain separate",
|
||||
resource_kinds: ["worker_operation"],
|
||||
availability: { kind: "available" },
|
||||
}],
|
||||
});
|
||||
assert(
|
||||
view.operations[0].available &&
|
||||
view.permissions?.execution[0].resource.kind === "worker_operation",
|
||||
);
|
||||
const installed = makeResource("plugin_installation", "w", "s", {
|
||||
installation_id: "installed",
|
||||
}, false);
|
||||
assert(
|
||||
installed.kind === "plugin_installation" &&
|
||||
installed.workspace_id === "w" &&
|
||||
installed.installation_id === "installed",
|
||||
);
|
||||
});
|
||||
|
||||
@@ -27,6 +27,8 @@ export type Lane = "execution" | "delegation";
|
||||
const MAX_BYTES = 1_048_576;
|
||||
const fields = {
|
||||
workspace: ["workspace_id"],
|
||||
worker_operation: ["workspace_id"],
|
||||
plugin_installation: ["workspace_id", "installation_id"],
|
||||
ticket: ["workspace_id", "ticket_id"],
|
||||
objective: ["workspace_id", "objective_id"],
|
||||
merge_request: ["workspace_id", "merge_request_id"],
|
||||
@@ -195,6 +197,8 @@ export function policyDiff(
|
||||
export function resourceLabel(resource: PermissionResource): string {
|
||||
const labels: Record<string, string> = {
|
||||
workspace: "Current workspace",
|
||||
worker_operation: "Worker operation use (target control grant required)",
|
||||
plugin_installation: "Plugin installation",
|
||||
subject: "Subject",
|
||||
ticket: "Ticket",
|
||||
objective: "Objective",
|
||||
@@ -277,6 +281,7 @@ export function resourceFields(kind: PermissionResource["kind"]): string[] {
|
||||
);
|
||||
}
|
||||
export const fieldLabel: Record<string, string> = {
|
||||
installation_id: "Plugin installation identifier",
|
||||
ticket_id: "Ticket identifier",
|
||||
objective_id: "Objective identifier",
|
||||
merge_request_id: "Merge request identifier",
|
||||
|
||||
@@ -275,7 +275,6 @@ test("warns about an unknown create outcome without replaying the POST", async (
|
||||
test("renders a ready resident surface and committed Memory lifecycle states", async () => {
|
||||
render(SubjectPage, {
|
||||
data: {
|
||||
workspace: { permissions: { delete_subjects: false } },
|
||||
workspaceId: "workspace-1",
|
||||
workspace: memoryWorkspace(),
|
||||
subjectId: "subject-1",
|
||||
@@ -371,7 +370,6 @@ test("edits and clears user-managed behavior with CAS while distinguishing save
|
||||
vi.stubGlobal("fetch", fetchMock);
|
||||
render(SubjectPage, {
|
||||
data: {
|
||||
workspace: { permissions: { delete_subjects: false } },
|
||||
workspaceId: "workspace-1",
|
||||
workspace: memoryWorkspace(),
|
||||
subjectId: "subject-1",
|
||||
@@ -444,7 +442,6 @@ test.each([false, true])(
|
||||
|
||||
function subjectPageData(record = subject(), workspaceId = "workspace-1") {
|
||||
return {
|
||||
workspace: { permissions: { delete_subjects: false } },
|
||||
workspaceId,
|
||||
workspace: memoryWorkspace(workspaceId),
|
||||
subjectId: record.id,
|
||||
@@ -778,7 +775,6 @@ test("candidate-only Subjects show no committed Memories, not an inferred candid
|
||||
// the same empty committed list as a new Subject; candidates are not a page input.
|
||||
render(SubjectPage, {
|
||||
data: {
|
||||
workspace: { permissions: { delete_subjects: false } },
|
||||
workspaceId: "workspace-1",
|
||||
workspace: memoryWorkspace(),
|
||||
subjectId: "subject-1",
|
||||
@@ -863,7 +859,6 @@ test.each(["ready", "ungenerated", "stale", "failed"] as const)(
|
||||
|
||||
test("distinguishes ready-empty, stale, failed, unavailable, and request error surfaces", async () => {
|
||||
const base = {
|
||||
workspace: { permissions: { delete_subjects: false } },
|
||||
workspaceId: "workspace-1",
|
||||
workspace: memoryWorkspace(),
|
||||
subjectId: "subject-1",
|
||||
@@ -950,7 +945,6 @@ test("distinguishes ready-empty, stale, failed, unavailable, and request error s
|
||||
test("renders committed Memory detail, provenance, derivation, and immutable changes", async () => {
|
||||
render(MemoryDetailPage, {
|
||||
data: {
|
||||
workspace: { permissions: { delete_subjects: false } },
|
||||
workspaceId: "workspace-1",
|
||||
workspace: memoryWorkspace(),
|
||||
subjectId: "subject-1",
|
||||
|
||||
Reference in New Issue
Block a user