feat(proxy): 1-proxy-per-account distribution planner (anti shared-IP)

planProxyDistribution() assigns one DISTINCT proxy per connection so no two
accounts of the same rotation group share an egress IP (the codex anomaly
trigger). Strict 1:1 by default — extras are left UNASSIGNED rather than
sharing an IP; allowSharing round-robins and flags sharingRisk. The healthy
.17 servers already had distinct proxies per account; this makes that the
enforced, auditable default once live proxies exist. TDD: 5 tests.
This commit is contained in:
diegosouzapw
2026-06-12 00:28:06 -03:00
parent f2d7df1f94
commit d03ed605cb
2 changed files with 126 additions and 1 deletions

View File

@@ -316,3 +316,73 @@ export async function validateProxyPool(deps?: {
return report;
}
export interface DistributionPlan {
assignments: Array<{ connectionId: string; account: string; proxyId: string }>;
unassigned: Array<{ connectionId: string; account: string }>;
sharingRisk: boolean;
note: string;
}
/**
* PURE: plan a 1-proxy-per-connection assignment so no two accounts of the same
* rotation group share an egress IP (the codex anomaly trigger). Default is
* strict 1:1 — extras are left UNASSIGNED (better unrouted than sharing an IP).
* allowSharing=true round-robins instead, flagging sharingRisk.
*/
export function planProxyDistribution(
connections: Array<{ id: string; account?: string }>,
liveProxyIds: string[],
opts: { allowSharing?: boolean } = {}
): DistributionPlan {
const assignments: DistributionPlan["assignments"] = [];
const unassigned: DistributionPlan["unassigned"] = [];
let sharingRisk = false;
connections.forEach((c, i) => {
const account = c.account || c.id.slice(0, 8);
if (liveProxyIds.length === 0) {
unassigned.push({ connectionId: c.id, account });
return;
}
if (opts.allowSharing) {
assignments.push({ connectionId: c.id, account, proxyId: liveProxyIds[i % liveProxyIds.length] });
} else if (i < liveProxyIds.length) {
assignments.push({ connectionId: c.id, account, proxyId: liveProxyIds[i] });
} else {
unassigned.push({ connectionId: c.id, account });
}
});
if (opts.allowSharing && liveProxyIds.length < connections.length) sharingRisk = true;
const note =
liveProxyIds.length === 0
? "No live proxies available — add working proxies before distributing."
: liveProxyIds.length < connections.length && !opts.allowSharing
? `Only ${liveProxyIds.length} live proxies for ${connections.length} accounts — ${unassigned.length} left unassigned (avoid shared-IP anomaly).`
: "1 distinct proxy per account.";
return { assignments, unassigned, sharingRisk, note };
}
/**
* Apply a distribution plan: assign each proxy to its connection (account scope).
*/
export async function applyProxyDistribution(
plan: DistributionPlan,
deps?: { assign?: (connectionId: string, proxyId: string) => Promise<void> }
): Promise<{ applied: number }> {
const assign =
deps?.assign ??
(async (connectionId: string, proxyId: string) => {
const { assignProxyToScope } = await import("./db/proxies");
await assignProxyToScope("account", connectionId, proxyId);
});
let applied = 0;
for (const a of plan.assignments) {
await assign(a.connectionId, a.proxyId);
applied++;
}
return { applied };
}

View File

@@ -24,7 +24,7 @@ const {
_setEgressProbeForTests: (fn: any) => void;
clearEgressCache: () => void;
};
const { validateProxyPool } = egress as any;
const { validateProxyPool, planProxyDistribution, applyProxyDistribution } = egress as any;
test("resolveEgressIp returns the probed IP and caches by proxy URL", async () => {
clearEgressCache();
@@ -133,3 +133,58 @@ test("validateProxyPool marks live proxies active and dead proxies error", async
_setEgressProbeForTests(null);
});
test("planProxyDistribution: strict 1:1, extras left unassigned (no shared IP)", () => {
const plan = planProxyDistribution(
[{ id: "c1", account: "a1" }, { id: "c2", account: "a2" }, { id: "c3", account: "a3" }],
["p1", "p2"]
);
assert.equal(plan.assignments.length, 2);
assert.deepEqual(plan.assignments.map((a: any) => a.proxyId), ["p1", "p2"]);
assert.equal(plan.unassigned.length, 1, "c3 has no proxy → unassigned, not sharing");
assert.equal(plan.unassigned[0].connectionId, "c3");
assert.equal(plan.sharingRisk, false);
});
test("planProxyDistribution: enough proxies → 1 distinct per account", () => {
const plan = planProxyDistribution(
[{ id: "c1", account: "a1" }, { id: "c2", account: "a2" }],
["p1", "p2", "p3"]
);
assert.equal(plan.assignments.length, 2);
assert.equal(plan.unassigned.length, 0);
assert.match(plan.note, /1 distinct proxy/);
});
test("planProxyDistribution: allowSharing round-robins and flags sharingRisk", () => {
const plan = planProxyDistribution(
[{ id: "c1", account: "a1" }, { id: "c2", account: "a2" }, { id: "c3", account: "a3" }],
["p1", "p2"],
{ allowSharing: true }
);
assert.equal(plan.assignments.length, 3);
assert.deepEqual(plan.assignments.map((a: any) => a.proxyId), ["p1", "p2", "p1"]);
assert.equal(plan.sharingRisk, true);
});
test("planProxyDistribution: no live proxies → all unassigned with guidance", () => {
const plan = planProxyDistribution([{ id: "c1", account: "a1" }], []);
assert.equal(plan.assignments.length, 0);
assert.equal(plan.unassigned.length, 1);
assert.match(plan.note, /No live proxies/);
});
test("applyProxyDistribution assigns each proxy to its connection", async () => {
const calls: Array<[string, string]> = [];
const plan = planProxyDistribution(
[{ id: "c1", account: "a1" }, { id: "c2", account: "a2" }],
["p1", "p2"]
);
const res = await applyProxyDistribution(plan, {
assign: async (connectionId: string, proxyId: string) => {
calls.push([connectionId, proxyId]);
},
});
assert.equal(res.applied, 2);
assert.deepEqual(calls, [["c1", "p1"], ["c2", "p2"]]);
});