From 9b9d61b84d476003cfd9d7c426e48012b3489990 Mon Sep 17 00:00:00 2001 From: Neelasha Bhattacharjee <46113280+Neelashab@users.noreply.github.com> Date: Wed, 15 Jul 2026 12:32:06 -0400 Subject: [PATCH] fix: flag-gate ABv1 edit block behind deprecation flags [ACTION-5731] MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The ABv1 edit gate (BuilderEditBlockedState + disabled canvas interactions) was enforced purely from the x-is-org-admin / x-can-edit-agent request headers. Those headers have no producer yet, so in any cloud session both resolve false and canEditAgent becomes false for everyone — including org admins — blocking all editing. Gate the enforcement behind the existing ABv2 deprecation flags (beforeDeprecationCutoffAbv2 / afterDeprecationCutoffAbv2). When neither flag is active, editing is always permitted so the block cannot engage unintentionally (e.g. before the header producer ships). Once a deprecation flag is enabled, the existing permission logic applies as before. Co-authored-by: Cursor --- src/ui/src/core/index.ts | 11 +++++ src/ui/src/tests/canEditAgent.spec.ts | 66 +++++++++++++++++++++++++++ src/ui/src/tests/mocks.ts | 4 ++ 3 files changed, 81 insertions(+) create mode 100644 src/ui/src/tests/canEditAgent.spec.ts diff --git a/src/ui/src/core/index.ts b/src/ui/src/core/index.ts index 33e406aef..18d20211e 100644 --- a/src/ui/src/core/index.ts +++ b/src/ui/src/core/index.ts @@ -148,8 +148,19 @@ export function generateCore() { const isWriterCloudApp = computed(() => Boolean(writerAppId.value || writerOrgId.value), ); + // ABv1 edit gating is only enforced while an ABv2 deprecation flag is + // active. Without a deprecation flag (e.g. the producer that sends the + // permission headers hasn't shipped, or the org is not in the deprecation + // rollout), editing is always permitted so the block cannot engage + // unintentionally. + const isAbv1EditGatingEnabled = computed( + () => + featureFlags.value.includes("beforeDeprecationCutoffAbv2") || + featureFlags.value.includes("afterDeprecationCutoffAbv2"), + ); const canEditAgent = computed( () => + !isAbv1EditGatingEnabled.value || !isWriterCloudApp.value || isOrganizationAdmin.value || Boolean(writerApplication.value?.canEdit), diff --git a/src/ui/src/tests/canEditAgent.spec.ts b/src/ui/src/tests/canEditAgent.spec.ts new file mode 100644 index 000000000..e44b24bad --- /dev/null +++ b/src/ui/src/tests/canEditAgent.spec.ts @@ -0,0 +1,66 @@ +import { describe, it, expect } from "vitest"; +import { buildMockCore } from "./mocks"; + +describe("canEditAgent gating", () => { + function setup() { + const { core, featureFlags, writerApplication } = buildMockCore(); + return { core, featureFlags, writerApplication }; + } + + it("allows editing outside of Writer cloud", () => { + const { core, featureFlags } = setup(); + featureFlags.value = ["beforeDeprecationCutoffAbv2"]; + expect(core.canEditAgent.value).toBe(true); + }); + + it("does not block editing when no deprecation flag is active", () => { + const { core, featureFlags, writerApplication } = setup(); + writerApplication.value = { + id: "app-1", + organizationId: "1", + isOrganizationAdmin: false, + canEdit: false, + }; + featureFlags.value = []; + expect(core.canEditAgent.value).toBe(true); + }); + + it.each(["beforeDeprecationCutoffAbv2", "afterDeprecationCutoffAbv2"])( + "blocks editing for a non-admin without edit access when %s is active", + (flag) => { + const { core, featureFlags, writerApplication } = setup(); + writerApplication.value = { + id: "app-1", + organizationId: "1", + isOrganizationAdmin: false, + canEdit: false, + }; + featureFlags.value = [flag]; + expect(core.canEditAgent.value).toBe(false); + }, + ); + + it("allows editing for an org admin when gating is active", () => { + const { core, featureFlags, writerApplication } = setup(); + writerApplication.value = { + id: "app-1", + organizationId: "1", + isOrganizationAdmin: true, + canEdit: false, + }; + featureFlags.value = ["beforeDeprecationCutoffAbv2"]; + expect(core.canEditAgent.value).toBe(true); + }); + + it("allows editing when canEdit is granted and gating is active", () => { + const { core, featureFlags, writerApplication } = setup(); + writerApplication.value = { + id: "app-1", + organizationId: "1", + isOrganizationAdmin: false, + canEdit: true, + }; + featureFlags.value = ["afterDeprecationCutoffAbv2"]; + expect(core.canEditAgent.value).toBe(true); + }); +}); diff --git a/src/ui/src/tests/mocks.ts b/src/ui/src/tests/mocks.ts index 1c6ca7bb8..095623b65 100644 --- a/src/ui/src/tests/mocks.ts +++ b/src/ui/src/tests/mocks.ts @@ -78,6 +78,10 @@ export function buildMockCore() { ); core.canEditAgent = computed( () => + !( + featureFlags.value.includes("beforeDeprecationCutoffAbv2") || + featureFlags.value.includes("afterDeprecationCutoffAbv2") + ) || !core.isWriterCloudApp.value || core.isOrganizationAdmin.value || Boolean(writerApplication.value?.canEdit),