Skip to content

Commit da6f3ab

Browse files
committed
feat(webapp): census exposes definite ever-enabled orgs and clear-path preserves the latch
1 parent dcf6e83 commit da6f3ab

5 files changed

Lines changed: 159 additions & 16 deletions

‎apps/webapp/app/routes/admin.api.v2.orgs.$organizationId.feature-flags.ts‎

Lines changed: 26 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import { invalidateSnapshotStoreOrgMode } from "~/v3/snapshotStoreMode.server";
1212
import { selectMintBaselineSource, stampMintKindFlip } from "~/v3/runOpsMigration/mintFlipGrace";
1313
import { flags as getGlobalFlags } from "~/v3/featureFlags.server";
1414
import {
15+
clearedOrgFlagsPreservingLatch,
1516
FEATURE_FLAG,
1617
stampSnapshotStoreOrgEverEnabled,
1718
validatePartialFeatureFlags,
@@ -114,17 +115,33 @@ export async function action({ request, params }: ActionFunctionArgs) {
114115
body === null ||
115116
(typeof body === "object" && !Array.isArray(body) && Object.keys(body).length === 0)
116117
) {
117-
// Clear all flags. No grace stamp (nothing to flip) and no read-then-write race.
118-
try {
119-
await prisma.organization.update({
118+
// Clear all flags, but preserve the one-way per-org residency latch so an ever-enabled org can
119+
// never drop out of the census. Locked read-then-write so a concurrent enabling save (which also
120+
// takes FOR UPDATE) can't slip a latch in between the read and the wipe.
121+
const updated = await prisma.$transaction(async (tx) => {
122+
const rows = await tx.$queryRaw<{ featureFlags: unknown }[]>`
123+
SELECT "featureFlags" FROM "Organization" WHERE "id" = ${organizationId} FOR UPDATE`;
124+
125+
if (rows.length === 0) {
126+
return false;
127+
}
128+
129+
const preserved = clearedOrgFlagsPreservingLatch(
130+
rows[0].featureFlags as Record<string, unknown> | null
131+
);
132+
133+
await tx.organization.update({
120134
where: { id: organizationId },
121-
data: { featureFlags: Prisma.JsonNull },
135+
data: {
136+
featureFlags: preserved === null ? Prisma.JsonNull : (preserved as Prisma.InputJsonValue),
137+
},
122138
});
123-
} catch (e) {
124-
if (e instanceof Prisma.PrismaClientKnownRequestError && e.code === "P2025") {
125-
throw new Response("Organization not found", { status: 404 });
126-
}
127-
throw e;
139+
140+
return true;
141+
});
142+
143+
if (!updated) {
144+
throw new Response("Organization not found", { status: 404 });
128145
}
129146

130147
controlPlaneResolver.invalidateOrganization(organizationId);

‎apps/webapp/app/v3/featureFlags.ts‎

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -309,6 +309,18 @@ export function stampSnapshotStoreOrgEverEnabled(
309309
return stamped;
310310
}
311311

312+
/**
313+
* The clear-all counterpart to the one-way per-org latch. Returns the blob to write when wiping an
314+
* org's flags: `{ snapshotStoreOrgEverEnabled: true }` if the org was ever enabled (so it stays in
315+
* the census), else null to wipe everything. Never resurrects a false or absent latch.
316+
*/
317+
export function clearedOrgFlagsPreservingLatch(
318+
existingFlags: Record<string, unknown> | null | undefined
319+
): Record<string, unknown> | null {
320+
const latched = (existingFlags ?? {})[FEATURE_FLAG.snapshotStoreOrgEverEnabled] === true;
321+
return latched ? { [FEATURE_FLAG.snapshotStoreOrgEverEnabled]: true } : null;
322+
}
323+
312324
/**
313325
* One-way global latch. Sets snapshotStoreGlobalModeEverEnabled true when the resulting global dial
314326
* is past `off`, and carries an already-set latch forward so a save back to `off` never clears it.

‎apps/webapp/app/v3/snapshotStoreOrgCensus.server.ts‎

Lines changed: 26 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import { cachedOrgModeFor, NO_OVERRIDE } from "~/v3/snapshotStoreMode.server";
1010
export type SnapshotStoreOrgCensusClient = {
1111
organization: {
1212
findMany(args: {
13-
where: { featureFlags: { path: string[]; not: typeof Prisma.DbNull } };
13+
where: { OR: Array<{ featureFlags: { path: string[]; not: typeof Prisma.DbNull } }> };
1414
select: { id: true; featureFlags: true };
1515
}): Promise<Array<{ id: string; featureFlags: unknown }>>;
1616
};
@@ -24,12 +24,16 @@ type OrgCensusSnapshot = {
2424
redisOnly: boolean;
2525
/** Orgs with any non-off override. */
2626
cohort: Set<string>;
27+
/** Orgs with the one-way per-org residency latch set true (may be off now, or latch-only). */
28+
everEnabled: Set<string>;
2729
};
2830

2931
export type SnapshotStoreOrgCensus = {
3032
anyOrgReadEnabled(): boolean;
3133
anyOrgRedisOnly(): boolean;
3234
isCohortMember(organizationId: string): boolean;
35+
/** DEFINITE never-enabled: census loaded AND the org is not in the ever-enabled set. */
36+
orgDefinitelyNeverEnabled(organizationId: string): boolean;
3337
/** Force one load and await it. For tests and boot; the interval drives it in production. */
3438
refresh(): Promise<void>;
3539
stop(): void;
@@ -38,17 +42,20 @@ export type SnapshotStoreOrgCensus = {
3842
/** Classifies each org's blob exactly as the per-org resolver does (via cachedOrgModeFor). */
3943
function classify(rows: Array<{ id: string; featureFlags: unknown }>): OrgCensusSnapshot {
4044
const cohort = new Set<string>();
45+
const everEnabled = new Set<string>();
4146
let readEnabled = false;
4247
let redisOnly = false;
4348
for (const row of rows) {
4449
const flags = row.featureFlags as Record<string, unknown> | null | undefined;
50+
// Strict: only an explicit true latches, matching stampSnapshotStoreOrgEverEnabled.
51+
if (flags?.[FEATURE_FLAG.snapshotStoreOrgEverEnabled] === true) everEnabled.add(row.id);
4552
const mode = cachedOrgModeFor(flags?.[FEATURE_FLAG.snapshotStoreOrgMode]);
4653
if (mode === NO_OVERRIDE || mode === "off") continue;
4754
cohort.add(row.id);
4855
if (mode === "redis-read" || mode === "redis-only") readEnabled = true;
4956
if (mode === "redis-only") redisOnly = true;
5057
}
51-
return { readEnabled, redisOnly, cohort };
58+
return { readEnabled, redisOnly, cohort, everEnabled };
5259
}
5360

5461
export function createSnapshotStoreOrgCensus(
@@ -61,12 +68,20 @@ export function createSnapshotStoreOrgCensus(
6168
intervalMs: opts?.intervalMs ?? env.GLOBAL_FLAGS_RELOAD_INTERVAL_MS,
6269
autoStart: opts?.autoStart ?? process.env.NODE_ENV !== "test",
6370
load: async () =>
64-
// WHERE bounds transfer to orgs that HAVE the key; cachedOrgModeFor still filters off /
65-
// NO_OVERRIDE out of the cohort in code, so classification stays identical to the resolver.
71+
// WHERE returns orgs with EITHER key, so an ever-enabled org that is now off (or holds only
72+
// the latch after a clear) still returns. Classification stays in code, identical to the resolver.
6673
classify(
6774
await client.organization.findMany({
6875
where: {
69-
featureFlags: { path: [FEATURE_FLAG.snapshotStoreOrgMode], not: Prisma.DbNull },
76+
OR: [
77+
{ featureFlags: { path: [FEATURE_FLAG.snapshotStoreOrgMode], not: Prisma.DbNull } },
78+
{
79+
featureFlags: {
80+
path: [FEATURE_FLAG.snapshotStoreOrgEverEnabled],
81+
not: Prisma.DbNull,
82+
},
83+
},
84+
],
7085
},
7186
select: { id: true, featureFlags: true },
7287
})
@@ -85,6 +100,12 @@ export function createSnapshotStoreOrgCensus(
85100
anyOrgReadEnabled: () => registry.current()?.readEnabled ?? true,
86101
anyOrgRedisOnly: () => registry.current()?.redisOnly ?? false,
87102
isCohortMember: (organizationId) => registry.current()?.cohort.has(organizationId) ?? false,
103+
// FALSE cold/never-loaded: not-definite means the caller (10c) must NOT skip. Only a loaded
104+
// census whose ever-enabled set excludes the org yields a definite "never enabled".
105+
orgDefinitelyNeverEnabled: (organizationId) => {
106+
const snapshot = registry.current();
107+
return snapshot ? !snapshot.everEnabled.has(organizationId) : false;
108+
},
88109
refresh: async () => {
89110
// A failed reload keeps the last-good snapshot; swallow so accessors stay safe.
90111
try {

‎apps/webapp/test/snapshotStoreOrgCensus.server.test.ts‎

Lines changed: 76 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,7 @@ describe("snapshot store org census", () => {
9191
expect(census.isCohortMember("org_a")).toBe(true);
9292
});
9393

94-
it("bounds the query to orgs that have the override key present", async () => {
94+
it("bounds the query to orgs that have EITHER the mode or the latch key present", async () => {
9595
const calls: FindManyArgs[] = [];
9696
const census = createSnapshotStoreOrgCensus(
9797
{
@@ -106,7 +106,10 @@ describe("snapshot store org census", () => {
106106
await census.refresh();
107107

108108
expect(calls).toHaveLength(1);
109-
expect(calls[0].where.featureFlags.path).toEqual(["snapshotStoreOrgMode"]);
109+
expect(calls[0].where.OR.map((clause) => clause.featureFlags.path)).toEqual([
110+
["snapshotStoreOrgMode"],
111+
["snapshotStoreOrgEverEnabled"],
112+
]);
110113
// The WHERE only bounds rows; classification is unaffected.
111114
expect(census.isCohortMember("org_a")).toBe(true);
112115
expect(census.anyOrgRedisOnly()).toBe(true);
@@ -122,3 +125,74 @@ describe("snapshot store org census", () => {
122125
expect(census.isCohortMember("org_a")).toBe(true);
123126
});
124127
});
128+
129+
describe("snapshot store org census — definite ever-enabled set", () => {
130+
// A: latched but dialled back to off. B: at redis-read and latched. C: has the mode key but off,
131+
// never latched. D: not returned by the query at all.
132+
const rows = [
133+
{
134+
id: "org_a",
135+
featureFlags: { snapshotStoreOrgMode: "off", snapshotStoreOrgEverEnabled: true },
136+
},
137+
{
138+
id: "org_b",
139+
featureFlags: { snapshotStoreOrgMode: "redis-read", snapshotStoreOrgEverEnabled: true },
140+
},
141+
{ id: "org_c", featureFlags: { snapshotStoreOrgMode: "off" } },
142+
];
143+
144+
it("marks only orgs outside the ever-enabled set as definitely never enabled", async () => {
145+
const census = build(rows);
146+
147+
await census.refresh();
148+
149+
expect(census.orgDefinitelyNeverEnabled("org_a")).toBe(false);
150+
expect(census.orgDefinitelyNeverEnabled("org_b")).toBe(false);
151+
expect(census.orgDefinitelyNeverEnabled("org_c")).toBe(true);
152+
// Never returned by the query, so it is not in the set: definitely never enabled.
153+
expect(census.orgDefinitelyNeverEnabled("org_d")).toBe(true);
154+
});
155+
156+
it("treats a latched-but-off org as ever-enabled yet outside the read cohort", async () => {
157+
const census = build(rows);
158+
159+
await census.refresh();
160+
161+
expect(census.orgDefinitelyNeverEnabled("org_a")).toBe(false);
162+
expect(census.isCohortMember("org_a")).toBe(false);
163+
// org_a is off; only org_b (redis-read) enables reads.
164+
expect(census.anyOrgReadEnabled()).toBe(true);
165+
expect(census.isCohortMember("org_b")).toBe(true);
166+
});
167+
168+
it("reports nobody as definitely-never before the first load (cold)", () => {
169+
const census = build(rows);
170+
171+
// Deliberately NOT refreshed: not-definite, so the caller must not skip anyone.
172+
expect(census.orgDefinitelyNeverEnabled("org_a")).toBe(false);
173+
expect(census.orgDefinitelyNeverEnabled("org_c")).toBe(false);
174+
expect(census.orgDefinitelyNeverEnabled("org_d")).toBe(false);
175+
});
176+
177+
it("keeps the last-good ever-enabled set when a later load fails", async () => {
178+
let calls = 0;
179+
const client: SnapshotStoreOrgCensusClient = {
180+
organization: {
181+
findMany: async () => {
182+
calls += 1;
183+
if (calls === 1) return rows;
184+
throw new Error("control plane unreachable");
185+
},
186+
},
187+
};
188+
const census = createSnapshotStoreOrgCensus({ replica: client }, { autoStart: false });
189+
190+
await census.refresh();
191+
expect(census.orgDefinitelyNeverEnabled("org_c")).toBe(true);
192+
expect(census.orgDefinitelyNeverEnabled("org_b")).toBe(false);
193+
194+
await expect(census.refresh()).resolves.toBeUndefined();
195+
expect(census.orgDefinitelyNeverEnabled("org_c")).toBe(true);
196+
expect(census.orgDefinitelyNeverEnabled("org_b")).toBe(false);
197+
});
198+
});

‎apps/webapp/test/snapshotStoreOrgEverEnabled.test.ts‎

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { describe, expect, it } from "vitest";
22
import {
3+
clearedOrgFlagsPreservingLatch,
34
FEATURE_FLAG,
45
stampSnapshotStoreOrgEverEnabled,
56
withoutOrgForbiddenSnapshotKeys,
@@ -44,6 +45,24 @@ describe("stampSnapshotStoreOrgEverEnabled (one-way per-org latch)", () => {
4445
});
4546
});
4647

48+
describe("clearedOrgFlagsPreservingLatch (clear-all keeps the one-way latch)", () => {
49+
it("preserves the latch when the org was ever enabled", () => {
50+
expect(clearedOrgFlagsPreservingLatch({ [LATCH]: true, [MODE]: "off" })).toEqual({
51+
[LATCH]: true,
52+
});
53+
});
54+
55+
it("wipes to null when the org never latched", () => {
56+
expect(clearedOrgFlagsPreservingLatch({ [MODE]: "off" })).toBeNull();
57+
expect(clearedOrgFlagsPreservingLatch({})).toBeNull();
58+
expect(clearedOrgFlagsPreservingLatch(null)).toBeNull();
59+
});
60+
61+
it("never treats a false latch as latched", () => {
62+
expect(clearedOrgFlagsPreservingLatch({ [LATCH]: false })).toBeNull();
63+
});
64+
});
65+
4766
describe("orgEverEnabled reader", () => {
4867
function clientReturning(flags: unknown) {
4968
return {

0 commit comments

Comments
 (0)